IncludeCategories:
- Regex: '^"system.hpp"$'
Priority: 1
- - Regex: '^"third_party/'
- Priority: 3
+ - Regex: '^["<]third_party/'
+ Priority: 4
+ # System headers:
+ - Regex: '\.h>$'
+ Priority: 5
+ # C++ headers:
+ - Regex: '^<[^.]+>$'
+ Priority: 6
- Regex: '^"'
Priority: 2
- Regex: '.*'
- Priority: 4
+ Priority: 3
IndentPPDirectives: AfterHash
KeepEmptyLinesAtTheStartOfBlocks: false
PointerAlignment: Left
+++ /dev/null
-# Configuration for probot-no-response - https://github.com/probot/no-response
-
-# Number of days of inactivity before an issue is closed for lack of response.
-daysUntilClose: 30
-# Label requiring a response.
-responseRequiredLabel: awaiting feedback
-# Comment to post when closing an issue for lack of response. Set to `false` to
-# disable.
-closeComment: >
- This issue has been automatically closed due to no response to our request for
- more information from the original author. Please feel free to reopen the
- issue or leave a comment if you have more information.
run:
shell: bash
+permissions:
+ contents: read
+
jobs:
build_and_test:
env:
fail-fast: false
matrix:
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
+ - os: ubuntu-20.04
compiler: gcc
version: "8"
- - os: ubuntu-18.04
+ - os: ubuntu-20.04
compiler: gcc
version: "9"
compiler: gcc
version: "10"
- - os: ubuntu-16.04
- compiler: clang
- version: "5.0"
-
- - os: ubuntu-16.04
- compiler: clang
- version: "6.0"
-
- - os: ubuntu-18.04
+ - os: ubuntu-20.04
compiler: clang
- version: "7"
+ version: "9"
- - os: ubuntu-18.04
+ - os: ubuntu-20.04
compiler: clang
- version: "8"
+ version: "10"
- os: ubuntu-20.04
compiler: clang
- version: "9"
+ version: "11"
- os: ubuntu-20.04
compiler: clang
- version: "10"
+ version: "12"
- - os: macOS-latest
- compiler: xcode
- version: "10.3"
+ - os: ubuntu-22.04
+ compiler: gcc
+ version: "11"
+
+ - os: ubuntu-22.04
+ compiler: gcc
+ version: "12"
- - os: macOS-latest
+ - os: macOS-11
compiler: xcode
version: "11.7"
- - os: macOS-latest
+ - os: macOS-11
compiler: xcode
- version: "12.2"
+ version: "12.4"
steps:
- name: Install dependencies
run: |
if [ "${{ runner.os }}" = "Linux" ]; then
sudo apt-get update
- # Install ld.gold (binutils) and ld.lld on different runs.
- if [ "${{ matrix.config.os }}" = "ubuntu-16.04" ]; then
- sudo apt-get install -y ninja-build elfutils libzstd1-dev binutils
+ packages="elfutils libhiredis-dev libzstd-dev ninja-build pkg-config python3 redis-server redis-tools"
+ # Install ld.gold (binutils) and ld.lld (lld) on different runs.
+ if [ "${{ matrix.config.os }}" = "ubuntu-22.04" ]; then
+ sudo apt-get install -y $packages binutils
else
- sudo apt-get install -y ninja-build elfutils libzstd-dev lld
+ sudo apt-get install -y $packages lld
fi
if [ "${{ matrix.config.compiler }}" = "gcc" ]; then
echo "CXX=g++-${{ matrix.config.version }}" >> $GITHUB_ENV
sudo apt install -y g++-${{ matrix.config.version }} g++-${{ matrix.config.version }}-multilib
+ if [ "${{ matrix.config.version }}" = 8 ]; then
+ # The compilation test in StdFilesystem.cmake doesn't work when
+ # GCC 9 is installed as well, so need to force linking with
+ # libstdc++fs for GCC 8. Note: This requires using -fuse-ld=lld to
+ # work.
+ echo "LDFLAGS=-lstdc++fs" >> $GITHUB_ENV
+ fi
else
echo "CC=clang-${{ matrix.config.version }}" >> $GITHUB_ENV
echo "CXX=clang++-${{ matrix.config.version }}" >> $GITHUB_ENV
- sudo apt install -y clang-${{ matrix.config.version }} g++-multilib
+ sudo apt install -y clang-${{ matrix.config.version }} g++-multilib lld-${{ matrix.config.version }}
fi
elif [ "${{ runner.os }}" = "macOS" ]; then
HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 \
- brew install ninja
+ brew install ninja pkg-config hiredis redis
if [ "${{ matrix.config.compiler }}" = "gcc" ]; then
brew install gcc@${{ matrix.config.version }}
name: ${{ matrix.config.os }}-${{ matrix.config.compiler }}-${{ matrix.config.version }}-testdir.tar.xz
path: testdir.tar.xz
+ build_and_test_msys:
+ runs-on: windows-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ config:
+ - sys: mingw64
+ env: x86_64
+ compiler: gcc
+
+ - sys: mingw64
+ env: x86_64
+ compiler: clang
+
+ name: 'Windows ${{ matrix.config.sys }} ${{ matrix.config.compiler }}'
+ defaults:
+ run:
+ shell: msys2 {0}
+ steps:
+ - name: '${{ matrix.config.sys }} Setup MSYS2'
+ uses: msys2/setup-msys2@v2
+ with:
+ msystem: ${{matrix.config.sys}}
+ update: true
+ install: >-
+ git
+ diffutils
+ tar
+ mingw-w64-${{matrix.config.env}}-toolchain
+ mingw-w64-${{matrix.config.env}}-cmake
+ mingw-w64-${{matrix.config.env}}-ninja
+ mingw-w64-${{matrix.config.env}}-hiredis
+ mingw-w64-${{matrix.config.env}}-lld
+ mingw-w64-${{matrix.config.env}}-${{matrix.config.compiler}}
+
+ - name: setup env
+ run: |
+ if [ "${{ matrix.config.compiler }}" = "gcc" ]; then
+ echo "CC=gcc" >> $GITHUB_ENV
+ echo "CXX=g++" >> $GITHUB_ENV
+ else
+ echo "CC=clang" >> $GITHUB_ENV
+ echo "CXX=clang++" >> $GITHUB_ENV
+ fi
+
+ - name: Get source
+ uses: actions/checkout@v2
+
+ - name: Build and test
+ run: ci/build
+ continue-on-error: ${{ matrix.config.allow_test_failures == true &&
+ steps.build-and-test.outputs.exit_status == 8 }}
+ env:
+ ENABLE_CACHE_CLEANUP_TESTS: 1
+ CMAKE_GENERATOR: Ninja
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI
+ TEST_CC: gcc
+
+ - name: Collect testdir from failed tests
+ if: failure()
+ run: ci/collect-testdir
+
+ - name: Upload testdir from failed tests
+ if: failure()
+ uses: actions/upload-artifact@v2
+ with:
+ name: ${{ matrix.config.sys}}-${{ matrix.config.env }}-${{ matrix.config.compiler }}-testdir.tar.xz
+ path: testdir.tar.xz
+
specific_tests:
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
fail-fast: false
matrix:
config:
- - name: Linux GCC debug + C++14 + in source + tracing
- os: ubuntu-18.04
+ - name: Linux GCC debug + in source + tracing
+ os: ubuntu-20.04
CC: gcc
CXX: g++
ENABLE_CACHE_CLEANUP_TESTS: 1
BUILDDIR: .
CCACHE_LOC: .
- CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=Debug -DENABLE_TRACING=1 -DCMAKE_CXX_STANDARD=14
- apt_get: elfutils libzstd-dev
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=Debug -DENABLE_TRACING=1
+ apt_get: elfutils libzstd-dev pkg-config libhiredis-dev
- name: Linux GCC 32-bit
- os: ubuntu-18.04
+ os: ubuntu-20.04
CC: gcc
CXX: g++
CFLAGS: -m32 -g -O2
CXXFLAGS: -m32 -g -O2
LDFLAGS: -m32
- CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON -DHIREDIS_FROM_INTERNET=ON
ENABLE_CACHE_CLEANUP_TESTS: 1
- apt_get: elfutils gcc-multilib g++-multilib lib32stdc++-5-dev
+ apt_get: elfutils gcc-multilib g++-multilib lib32stdc++-10-dev
- name: Linux GCC CUDA
- os: ubuntu-18.04
+ os: ubuntu-20.04
CC: gcc
CXX: g++
- CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON -DHIREDIS_FROM_INTERNET=ON
ENABLE_CACHE_CLEANUP_TESTS: 1
- CUDA: 10.1.243-1
+ CUDA: 11.7.0-1
apt_get: elfutils libzstd-dev
- name: Linux MinGW 32-bit
- os: ubuntu-18.04
+ os: ubuntu-20.04
CC: i686-w64-mingw32-gcc-posix
CXX: i686-w64-mingw32-g++-posix
- 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 -DHIREDIS_FROM_INTERNET=ON
RUN_TESTS: none
apt_get: elfutils mingw-w64
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 -DHIREDIS_FROM_INTERNET=ON
RUN_TESTS: unittest-in-wine
apt_get: elfutils mingw-w64 wine
- - name: Windows VS2019 32-bit
+ - name: Windows VS2019 32-bit Ninja
os: windows-2019
msvc_arch: x64_x86
- allow_test_failures: true # For now, don't fail the build on failure
CC: cl
CXX: cl
ENABLE_CACHE_CLEANUP_TESTS: 1
CMAKE_GENERATOR: Ninja
- CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON -DHIREDIS_FROM_INTERNET=ON
+ TEST_CC: clang -target i686-pc-windows-msvc
+
+ - name: Windows VS2019 32-bit MSBuild
+ os: windows-2019
+ msvc_arch: x64_x86
+ allow_test_failures: true # For now, don't fail the build on failure
+ CC: cl
+ CXX: cl
+ ENABLE_CACHE_CLEANUP_TESTS: 1
+ CMAKE_GENERATOR: Visual Studio 16 2019
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON -DHIREDIS_FROM_INTERNET=ON -A Win32
TEST_CC: clang -target i686-pc-windows-msvc
- - name: Windows VS2019 64-bit
+ - name: Windows VS2019 64-bit Ninja
+ os: windows-2019
+ msvc_arch: x64
+ CC: cl
+ CXX: cl
+ ENABLE_CACHE_CLEANUP_TESTS: 1
+ CMAKE_GENERATOR: Ninja
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON -DHIREDIS_FROM_INTERNET=ON
+ TEST_CC: clang -target x86_64-pc-windows-msvc
+
+ - name: Windows VS2019 64-bit MSBuild
os: windows-2019
msvc_arch: x64
allow_test_failures: true # For now, don't fail the build on failure
CC: cl
CXX: cl
ENABLE_CACHE_CLEANUP_TESTS: 1
+ CMAKE_GENERATOR: Visual Studio 16 2019
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON -DHIREDIS_FROM_INTERNET=ON -A x64
+ TEST_CC: clang -target x86_64-pc-windows-msvc
+
+ - name: Windows VS2022 32-bit Ninja
+ os: windows-2022
+ msvc_arch: x64_x86
+ allow_test_failures: true # For now, don't fail the build on failure
+ CC: cl
+ CXX: cl
+ ENABLE_CACHE_CLEANUP_TESTS: 1
+ CMAKE_GENERATOR: Ninja
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON -DHIREDIS_FROM_INTERNET=ON
+ TEST_CC: clang -target i686-pc-windows-msvc
+
+ - name: Windows VS2022 32-bit MSBuild
+ os: windows-2022
+ msvc_arch: x64_x86
+ allow_test_failures: true # For now, don't fail the build on failure
+ CC: cl
+ CXX: cl
+ ENABLE_CACHE_CLEANUP_TESTS: 1
+ CMAKE_GENERATOR: Visual Studio 17 2022
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON -DHIREDIS_FROM_INTERNET=ON -A Win32
+ TEST_CC: clang -target i686-pc-windows-msvc
+
+ - name: Windows VS2022 64-bit Ninja
+ os: windows-2022
+ msvc_arch: x64
+ allow_test_failures: true # For now, don't fail the build on failure
+ CC: cl
+ CXX: cl
+ ENABLE_CACHE_CLEANUP_TESTS: 1
CMAKE_GENERATOR: Ninja
- CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON -DHIREDIS_FROM_INTERNET=ON
+ TEST_CC: clang -target x86_64-pc-windows-msvc
+
+ - name: Windows VS2022 64-bit MSBuild
+ os: windows-2022
+ msvc_arch: x64
+ allow_test_failures: true # For now, don't fail the build on failure
+ CC: cl
+ CXX: cl
+ ENABLE_CACHE_CLEANUP_TESTS: 1
+ CMAKE_GENERATOR: Visual Studio 17 2022
+ CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON -DHIREDIS_FROM_INTERNET=ON -A x64
TEST_CC: clang -target x86_64-pc-windows-msvc
- name: Clang address & UB sanitizer
ENABLE_CACHE_CLEANUP_TESTS: 1
CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DENABLE_SANITIZER_ADDRESS=ON -DENABLE_SANITIZER_UNDEFINED_BEHAVIOR=ON
ASAN_OPTIONS: detect_leaks=0
- apt_get: elfutils libzstd-dev
+ apt_get: elfutils libzstd-dev pkg-config libhiredis-dev
- name: Clang static analyzer
os: ubuntu-20.04
ENABLE_CACHE_CLEANUP_TESTS: 1
CMAKE_PREFIX: scan-build
RUN_TESTS: none
- apt_get: libzstd-dev
+ apt_get: libzstd-dev pkg-config libhiredis-dev
- name: Linux binary
os: ubuntu-20.04
CXX: g++
SPECIAL: build-and-verify-package
CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=Release
- apt_get: elfutils libzstd-dev ninja-build
+ apt_get: elfutils libzstd-dev pkg-config libhiredis-dev ninja-build
- name: Source package
os: ubuntu-20.04
CC: gcc
CXX: g++
SPECIAL: build-and-verify-source-package
- apt_get: elfutils libzstd-dev ninja-build asciidoc xsltproc docbook-xml docbook-xsl
+ apt_get: elfutils libzstd-dev pkg-config libhiredis-dev ninja-build asciidoctor
- name: HTML documentation
- os: ubuntu-18.04
+ os: ubuntu-20.04
EXTRA_CMAKE_BUILD_FLAGS: --target doc-html
RUN_TESTS: none
- apt_get: libzstd-dev asciidoc docbook-xml docbook-xsl
+ apt_get: libzstd-dev pkg-config libhiredis-dev asciidoctor
- name: Manual page
- os: ubuntu-18.04
+ os: ubuntu-20.04
EXTRA_CMAKE_BUILD_FLAGS: --target doc-man-page
RUN_TESTS: none
- apt_get: libzstd-dev asciidoc xsltproc docbook-xml docbook-xsl
+ apt_get: libzstd-dev pkg-config libhiredis-dev asciidoctor
- name: Clang-Tidy
- os: ubuntu-18.04
- CC: clang-9
- CXX: clang++-9
+ os: ubuntu-20.04
+ CC: clang-12
+ CXX: clang++-12
RUN_TESTS: none
- CMAKE_PARAMS: -DENABLE_CLANG_TIDY=ON -DCLANGTIDY=/usr/bin/clang-tidy-9
- apt_get: libzstd-dev clang-9 clang-tidy-9
+ CMAKE_PARAMS: -DENABLE_CLANG_TIDY=ON -DCLANGTIDY=/usr/bin/clang-tidy-12
+ apt_get: libzstd-dev pkg-config libhiredis-dev clang-12 clang-tidy-12
steps:
- name: Get source
run: |
rc=0
ci/build || rc=$?
- echo "::set-output name=exit_status::$rc"
+ echo "exit_status=$rc" >> $GITHUB_OUTPUT
exit $rc
# CTest exits with return code 8 on test failure.
continue-on-error: ${{ matrix.config.allow_test_failures == true &&
uses: actions/checkout@v2
- name: Install codespell
- run: sudo apt-get update && sudo apt-get install codespell
+ run: |
+ sudo apt-get update
+ sudo apt-get install python3-pip
+ pip3 install codespell==2.1.0
- name: Run codespell
- run: codespell -q 7 -S ".git,LICENSE.adoc,./src/third_party/*" -I misc/codespell-allowlist.txt
+ run: codespell -q 7 -S ".git,build*,./src/third_party/*" -I misc/codespell-allowlist.txt
# Full scan once a week
- cron: '0 14 * * 3'
+permissions:
+ contents: read
+
jobs:
analyze:
+ permissions:
+ actions: read # for github/codeql-action/init to get workflow details
+ contents: read # for actions/checkout to fetch code
+ security-events: write # for github/codeql-action/analyze to upload SARIF results
name: Analyze
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-20.04
steps:
- name: Checkout repository
fetch-depth: 2
- name: Install dependencies
- run: sudo apt-get update && sudo apt-get install ninja-build elfutils libzstd-dev
+ run: sudo apt-get update && sudo apt-get install ninja-build elfutils libzstd-dev pkg-config libhiredis-dev
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v2
with:
languages: cpp
queries: +security-and-quality
env:
RUN_TESTS: none
CMAKE_GENERATOR: Ninja
+ EXTRA_CMAKE_BUILD_FLAGS: --target ccache
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v2
# Typical build directories
/build*/
+# CLion
+/.idea/
+/cmake-build-*/
+
# Downloaded tools
misc/.clang-format-exe
Clemens Rabe <clemens.rabe@gmail.com> <crabe@gmx.de>
Doug Anderson <dianders@disordat.com>
Erik Flodin <erik@ejohansson.se>
+Florin Trofin <florint@gmail.com>
Hongli Lai <hongli@phusion.nl>
+Jacob Young <jacobly0@users.noreply.github.com>
Jonny Yu <yingshen.yu@gmail.com>
Ka Ho Ng <khng300@gmail.com>
Kona Blend <kona8lend@gmail.com>
Leanid Chaika <leanid.chaika@gmail.com>
+Louis Caron <caron_louis@yahoo.fr>
Luboš Luňák <l.lunak@centrum.cz> <l.lunak@suse.cz>
Martin Ettl <ettl.martin78@gmail.com>
Mizuha Himuraki <mocha.java.cchip@gmail.com>
+Oleg Sidorkin <olegsidorkin@users.noreply.github.com>
Paul Bunch <paulbunc@gmail.com>
Pawel Krysiak <pawel_krysiak@interia.pl>
Per Nordlöw <per.nordlow@autoliv.com>
Ramiro Polla <ramiro.polla@gmail.com>
Ramiro Polla <ramiro.polla@gmail.com> <ramiro@mac-vbox-ubuntu910.(none)>
Ryan Brown <ryb@ableton.com>
+Ryan Burns <52847440+r-burns@users.noreply.github.com>
Thomas Otto <39962140+totph@users.noreply.github.com>
Thomas Röfer <Thomas.Roefer@dfki.de>
Tor Arne Vestbø <tor.arne.vestbo@qt.io> <torarnv@gmail.com>
+Vili Väinölä <vilivainola@gmail.com>
Ville Skyttä <ville.skytta@iki.fi>
Xavier René-Corail <xavier.renecorail@gmail.com>
--- /dev/null
+# Ccache architecture
+
+## Code structure
+
+### Top-level directories
+
+* `ci`: Utility scripts used in CI.
+* `cmake`: CMake scripts.
+* `doc`: Documentation.
+* `dockerfiles`: Dockerfiles that specify different environments of interest for
+ ccache.
+* `misc`: Miscellaneous utility scripts, example files, etc.
+* `src`: Source code. See below.
+* `test`: Integration test suite which tests the ccache binary in different
+ scenarios.
+* `unittest`: Unit test suite which typically tests individual functions.
+
+### Subdirectories of `src`
+
+This section describes the directory structure that the project aims to
+transform the `src` directory into in the long run to make the code base easier
+to understand and work with. In other words, this is work in progress.
+
+* `compiler`: Knowledge about things like compiler options, compiler behavior,
+ preprocessor output format, etc. Ideally this code should in the future be
+ refactored into compiler-specific frontends, such as GCC, Clang, NVCC, MSVC,
+ etc.
+* `core`: Everything not part of other directories.
+* `storage`: Storage backends.
+* `storage/local`: Code for the local storage backend.
+* `storage/remote`: Code for remote storage backends.
+* `third_party`: Bundled third party code.
+* `util`: Generic utility functionality that does not depend on ccache-specific
+ things.
-cmake_minimum_required(VERSION 3.4.3)
+cmake_minimum_required(VERSION 3.15)
-project(ccache LANGUAGES C CXX)
+project(ccache LANGUAGES C CXX ASM ASM_MASM)
if(MSVC)
enable_language(ASM_MASM)
else()
endif()
set(CMAKE_PROJECT_DESCRIPTION "a fast C/C++ compiler cache")
-if(NOT "${CMAKE_CXX_STANDARD}")
- set(CMAKE_CXX_STANDARD 11)
-endif()
+set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
# 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)
+if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.8)
+ OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5)
OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0))
message(
FATAL_ERROR
"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))
+if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4)
+ OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6))
message(
WARNING
"The compiler you are using is rather old.\n"
#
include(CcacheVersion)
-if("${CCACHE_VERSION_ORIGIN}" STREQUAL git OR DEFINED ENV{CI})
- set(CCACHE_DEV_MODE ON)
-else()
- set(CCACHE_DEV_MODE OFF)
+if(NOT DEFINED CCACHE_DEV_MODE)
+ if("${CCACHE_VERSION_ORIGIN}" STREQUAL git OR DEFINED ENV{CI})
+ set(CCACHE_DEV_MODE ON)
+ else()
+ set(CCACHE_DEV_MODE OFF)
+ endif()
endif()
message(STATUS "Ccache dev mode: ${CCACHE_DEV_MODE}")
-include(UseCcache)
-if(NOT MSVC)
- include(UseFastestLinker)
+option(ENABLE_IPO "Enable interprocedural (link time, LTO) optimization" OFF)
+if(ENABLE_IPO AND NOT MINGW)
+ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
endif()
+
+include(UseFastestLinker)
include(StandardSettings)
include(StandardWarnings)
include(CIBuildType)
include(DefaultBuildType)
-if(NOT ${CMAKE_VERSION} VERSION_LESS "3.9")
- cmake_policy(SET CMP0069 NEW)
- option(ENABLE_IPO "Enable interprocedural (link time, LTO) optimization" OFF)
- if(ENABLE_IPO)
- set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
- endif()
-endif()
-
#
# Configuration
#
-include(GNUInstallDirs)
+
+include(InstallDirs)
include(GenerateConfigurationFile)
include(GenerateVersionFile)
-if(HAVE_SYS_MMAN_H AND HAVE_PTHREAD_MUTEXATTR_SETPSHARED)
- set(INODE_CACHE_SUPPORTED 1)
+#
+# Static link configuration
+#
+
+set(STATIC_LINK_DEFAULT OFF)
+if(WIN32)
+ set(STATIC_LINK_DEFAULT ON)
+endif()
+
+option(STATIC_LINK "Prefer linking libraries statically" ${STATIC_LINK_DEFAULT})
+
+if(STATIC_LINK)
+ list(INSERT CMAKE_FIND_LIBRARY_SUFFIXES 0 "${CMAKE_STATIC_LIBRARY_SUFFIX}")
+ set(CMAKE_LINK_SEARCH_START_STATIC ON)
+
+ # Link MSVC runtime statically.
+ if(MSVC)
+ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
+ # Link MINGW runtime statically.
+ elseif(WIN32)
+ if((CMAKE_CXX_COMPILER_ID STREQUAL GNU) OR (CMAKE_CXX_COMPILER_ID STREQUAL Clang))
+ list(APPEND CCACHE_EXTRA_LIBS -static-libgcc -static-libstdc++ -static -lwinpthread -dynamic)
+ endif()
+ if(CMAKE_CXX_COMPILER_ID STREQUAL Clang)
+ list(APPEND CCACHE_EXTRA_LIBS -fuse-ld=lld)
+ endif()
+ endif()
endif()
#
# Third party
#
-set(ZSTD_FROM_INTERNET_DEFAULT OFF)
+option(ZSTD_FROM_INTERNET
+ "Download and use libzstd from the Internet if not available" ON)
+option(HIREDIS_FROM_INTERNET
+ "Download and use libhiredis from the Internet if not available" ON)
-# Default to downloading deps for Visual Studio, unless using a package manager.
-if(MSVC AND NOT CMAKE_TOOLCHAIN_FILE MATCHES "vcpkg|conan")
- set(ZSTD_FROM_INTERNET_DEFAULT ON)
-endif()
+find_package(zstd 1.1.2 MODULE REQUIRED)
-option(ZSTD_FROM_INTERNET "Download and use libzstd from the Internet" ${ZSTD_FROM_INTERNET_DEFAULT})
-find_package(zstd 1.1.2 REQUIRED)
+option(REDIS_STORAGE_BACKEND "Enable Redis remote storage" ON)
+
+if(REDIS_STORAGE_BACKEND)
+ find_package(hiredis 0.13.3 MODULE REQUIRED)
+endif()
#
# Special flags
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" ON)
-endif()
-
#
# Source code
#
# ccache executable
#
add_executable(ccache src/main.cpp)
-target_link_libraries(ccache PRIVATE standard_settings standard_warnings ccache_lib)
+target_link_libraries(ccache PRIVATE standard_settings standard_warnings ccache_framework)
#
# Documentation
Here are some hints to make the process smoother:
+* Have a look in `ARCHITECTURE.md` for an overview of the source code tree.
* If you plan to implement major changes it is wise to open an issue on GitHub
(or ask in the Gitter room, or send a mail to the mailing list) asking for
comments on your plans before doing the bulk of the work. That way you can
for merging yet but you want early comments and CI test results? Then create a
draft pull request as described in [this Github blog
post](https://github.blog/2019-02-14-introducing-draft-pull-requests/).
+* Please add test cases for your new changes if applicable.
* Please follow the ccache's code style (see the section below).
-* Consider [A Note About Git Commit
- Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
- when writing commit messages.
-## Code style
+## Commit message conventions
+
+It is preferable, but not mandatory, to format commit messages in the spirit of
+[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). The
+commit message subject should look like this:
+
+ <type>: <description>
+ <type>(<scope>): <description>
+
+`<description>` is a succinct description of the change:
-Ccache was written in C99 until 2019 when it started being converted to C++11.
-The conversion is a slow work in progress, which is why there is some C-style
-code left. Please refrain from doing large C to C++ conversions; do it little by
-little.
+* Use the imperative, present tense: "Change", not "Changed" nor "Changes".
+* Capitalize the first letter.
+* No dot (`.`) at the end.
+
+Here is a summary of types used for ccache:
+
+| Type | Explanation |
+| ------------ | ----------- |
+| **build** | A change of the build system or build configuration. |
+| **bump** | An increase of the version of an external dependency or an update of a bundled third party package. |
+| **chore** | A change that doesn't match any other type. |
+| **ci** | A change of CI scripts or configuration. |
+| **docs** | A change of documentation only. |
+| **enhance** | An enhancement of the code without adding a user-visible feature, for example adding a new utility class to be used by a future feature or refactoring. |
+| **feat** | An addition or improvement of a user-visible feature. |
+| **fix** | A bug fix (not necessarily user-visible). |
+| **perf** | A performance improvement. |
+| **refactor** | A restructuring of the existing code without changing its external behavior. |
+| **style** | A change of code style. |
+| **test** | An addition or modification of tests or test framework. |
+
+## Code style
Source code formatting is defined by `.clang-format` in the root directory. The
format is loosely based on [LLVM's code formatting
Please follow these conventions:
-* Use `UpperCamelCase` for types (e.g. classes and structs) and namespaces.
-* Use `UPPER_CASE` names for macros and (non-class )enum values.
-* Use `snake_case` for other names (functions, variables, enum class values,
- etc.).
+* Use `UpperCamelCase` for types (e.g. classes and structs).
+* Use `UPPER_CASE` names for macros and (non-class) enum values.
+* Use `snake_case` for other names (namespaces, functions, variables, enum class
+ values, etc.). (Namespaces used to be in `UpperCamelCase`; transition is work
+ in progress.)
* Use an `m_` prefix for non-public member variables.
* Use a `g_` prefix for global mutable variables.
* Use a `k_` prefix for global constants.
-Ccache copyright and license
-============================
+= Ccache copyright and license
-Overall license
----------------
+== Overall license
The license for ccache as a whole is as follows:
--------------------------------------------------------------------------------
+----
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
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
--------------------------------------------------------------------------------
+----
The full license text can be found in GPL-3.0.txt and at
https://www.gnu.org/licenses/gpl-3.0.html.
-Copyright and authors
----------------------
+== Copyright and authors
Ccache is a collective work with contributions from many people, listed in
AUTHORS.adoc and at https://ccache.dev/credits.html. Subsequent additions by
The copyright for ccache as a whole is as follows:
--------------------------------------------------------------------------------
+----
Copyright (C) 2002-2007 Andrew Tridgell
-Copyright (C) 2009-2021 Joel Rosdahl and other contributors
--------------------------------------------------------------------------------
+Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+----
-Files derived from other sources
---------------------------------
+== Files derived from other sources
The ccache distribution contain some files from other sources and some have
been modified for use in ccache. These files all carry attribution notices, and
under less restrictive terms.
-src/third_party/base32hex.*
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/base32hex.*
This base32hex implementation comes from
<https://github.com/pmconrad/tinydnssec>.
--------------------------------------------------------------------------------
+----
(C) 2012 Peter Conrad <conrad@quisquis.de>
This program is free software: you can redistribute it and/or modify
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
--------------------------------------------------------------------------------
+----
-src/third_party/blake3/blake3_*
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/blake3/blake3_*
-This is a subset of https://github.com/BLAKE3-team/BLAKE3[BLAKE3] 0.3.7 with
+This is a subset of https://github.com/BLAKE3-team/BLAKE3[BLAKE3] 1.3.1 with
the following license:
--------------------------------------------------------------------------------
+----
This work is released into the public domain with CC0 1.0. Alternatively, it is
licensed under the Apache License 2.0.
--------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Creative Commons Legal Code
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
--------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Apache License
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--------------------------------------------------------------------------------
+----
-src/third_party/doctest.h
-~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/doctest.h
This is the single header version of https://github.com/onqtam/doctest[doctest]
-2.4.6 with the following license:
+2.4.8 with the following license:
--------------------------------------------------------------------------------
+----
The MIT License (MIT)
Copyright (c) 2016-2021 Viktor Kirilov
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------------------------------------------------------------------
+----
-src/third_party/fmt/*.h and src/third_party/format.cpp
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/fmt/*.h and src/third_party/format.cpp
-This is a subset of https://fmt.dev[fmt] 7.1.3 with the following license:
+This is a subset of https://fmt.dev[fmt] 8.1.1 with the following license:
--------------------------------------------------------------------------------
+----
Formatting library for C++
Copyright (c) 2012 - present, Victor Zverovich
of this Software are embedded into a machine-executable object form of such
source code, you may redistribute such embedded portions in such object form
without including the above copyright and permission notices.
--------------------------------------------------------------------------------
+----
-src/third_party/getopt_long.*
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/getopt_long.*
This implementation of `getopt_long()` was copied from
https://www.postgresql.org[PostgreSQL] and has the following license text:
--------------------------------------------------------------------------------
+----
Portions Copyright (c) 1987, 1993, 1994
The Regents of the University of California. All rights reserved.
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
--------------------------------------------------------------------------------
+----
+
+
+=== src/third_party/httplib.*
+
+cpp-httplib - A C++11 cross-platform HTTP/HTTPS library. Copied from cpp-httplib
+v0.11.1 downloaded from https://github.com/yhirose/cpp-httplib[cpp-httplib]. The
+library has the following license:
+
+----
+The MIT License (MIT)
+
+Copyright (c) 2022 yhirose
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+----
-src/third_party/minitrace.*
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/minitrace.*
A library for producing JSON traces suitable for Chrome's built-in trace viewer
(chrome://tracing). Downloaded from <https://github.com/hrydgard/minitrace>.
--------------------------------------------------------------------------------
+----
The MIT License (MIT)
Copyright (c) 2014 Henrik Rydgård
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------------------------------------------------------------------
+----
-src/third_party/nonstd/optional.hpp
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/nonstd/expected.hpp
This is the single header version of
-https://github.com/martinmoene/optional-lite[optional-lite] 3.4.0 with the
+https://github.com/martinmoene/expected-lite[expected-lite] 0.6.2 with the
following license:
--------------------------------------------------------------------------------
-Copyright (c) 2014-2018 Martin Moene
+----
+Copyright (c) 2016-2020 Martin Moene
Boost Software License - Version 1.0 - August 17th, 2003
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
+----
-src/third_party/nonstd/string_view.hpp
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/nonstd/span.hpp
-This alternative implementation of `std::string_view` was downloaded from
-<https://github.com/martinmoene/string-view-lite> and has the following license
-text:
+This is the single header version of
+https://github.com/martinmoene/span-lite[expected-lite] 0.10.3 with the
+following license:
--------------------------------------------------------------------------------
-Copyright 2017-2020 by Martin Moene
+----
+Copyright (c) 2018-2021 Martin Moene
Boost Software License - Version 1.0 - August 17th, 2003
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
+----
+
+
+=== src/third_party/url.*
+
+CxxUrl - A simple C++ URL class. Copied from CxxUrl v0.3 downloaded from
+<https://github.com/chmike/CxxUrl>. It has the following license text:
+
+----
+The MIT License (MIT)
+
+Copyright (c) 2015 Christophe Meessen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+----
-src/third_party/win32/getopt.*
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/win32/getopt.*
This implementation of `getopt_long()` for Win32 was taken from
https://www.codeproject.com/Articles/157001/Full-getopt-Port-for-Unicode-and-Multibyte-Microso
https://www.gnu.org/licenses/lgpl-3.0.html.
-src/third_party/win32/mktemp.*
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/win32/mktemp.*
This implementation of `mkstemp()` for Win32 was adapted from
<https://github.com/openbsd/src/blob/99b791d14c0f1858d87a0c33b55880fb9b00be66/lib/libc/stdio/mktemp.c>
-and has the folowing license text:
+and has the following license text:
--------------------------------------------------------------------------------
+----
Copyright (c) 1996-1998, 2008 Theo de Raadt
Copyright (c) 1997, 2008-2009 Todd C. Miller
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--------------------------------------------------------------------------------
+----
+
-src/third_party/win32/winerror_to_errno.h
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/win32/winerror_to_errno.h
The implementation of `winerror_to_errno()` was adapted from
<https://github.com/python/cpython/blob/1a79785e3e8fea80bcf6a800b45a04e06c787480/PC/errmap.h>
-and has the folowing license text:
+and has the following license text:
--------------------------------------------------------------------------------
+----
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
1. This LICENSE AGREEMENT is between the Python Software Foundation
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
--------------------------------------------------------------------------------
+----
+
-src/third_party/xxh*
-~~~~~~~~~~~~~~~~~~~~
+=== src/third_party/xxh*
-xxHash - Extremely Fast Hash algorithm. Copied from xxHash v0.8.0 downloaded
+xxHash - Extremely Fast Hash algorithm. Copied from xxHash v0.8.1 downloaded
from <https://github.com/Cyan4973/xxHash/releases>.
--------------------------------------------------------------------------------
+----
Copyright (c) 2012-2020 Yann Collet
All rights reserved.
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
+----
[](https://github.com/ccache/ccache/actions?query=workflow%3A%22Build%22)
[](https://lgtm.com/projects/g/ccache/ccache/context:cpp)
[](https://lgtm.com/projects/g/ccache/ccache/alerts)
+[](https://bestpractices.coreinfrastructure.org/projects/5057)
[](https://gitter.im/ccache/ccache)
-Ccache (or “ccache”) is a compiler cache. It [speeds up
+Ccache is a compiler cache. It [speeds up
recompilation](https://ccache.dev/performance.html) by caching previous
compilations and detecting when the same compilation is being done again.
set -eu
-if [ -n "${VERBOSE:-}" ]; then
+# Set default values.
+: ${BUILDDIR:=build}
+: ${CCACHE_LOC:=..}
+: ${CMAKE_PARAMS:=}
+: ${CMAKE_PREFIX:=}
+: ${EXTRA_CMAKE_BUILD_FLAGS:=}
+: ${JOBS:=$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)}
+: ${SPECIAL:=}
+: ${TEST_CC:=${CC:-}}
+: ${VERBOSE:=}
+
+if [ -n "${VERBOSE}" ]; then
set -x
fi
-if [ -n "${SPECIAL:-}" ]; then
+if [ -n "${SPECIAL}" ]; then
exec "ci/$SPECIAL"
else
- [ -z ${JOBS:+x} ] && JOBS=$(getconf _NPROCESSORS_ONLN 2>/dev/null)
- [ -z ${JOBS:+x} ] && JOBS=2
+ mkdir -p "${BUILDDIR}"
+ cd "${BUILDDIR}"
+ ${CMAKE_PREFIX} cmake ${CMAKE_PARAMS} ${CCACHE_LOC}
+
+ case "${CMAKE_GENERATOR}" in
+ [Vv]isual" "[Ss]tudio*) # MSBuild, use all CPUs.
+ ${CMAKE_PREFIX} cmake --build . ${EXTRA_CMAKE_BUILD_FLAGS} -- -m
+ ;;
+ *) # Ninja automatically uses all available CPUs.
+ ${CMAKE_PREFIX} cmake --build . ${EXTRA_CMAKE_BUILD_FLAGS}
+ ;;
+ esac
- mkdir -p ${BUILDDIR:-build}
- cd ${BUILDDIR:-build}
- ${CMAKE_PREFIX:-} cmake ${CMAKE_PARAMS:-} ${CCACHE_LOC:-..}
- ${CMAKE_PREFIX:-} cmake --build . ${EXTRA_CMAKE_BUILD_FLAGS:-} -- -j$JOBS
case "${RUN_TESTS:-all}" in
all)
- CC=${TEST_CC:-${CC}} ctest --output-on-failure -j$JOBS "$@"
+ CC="${TEST_CC}" ctest --output-on-failure -j"${JOBS}" "$@"
;;
unittest-in-wine)
wine ccache.exe --version
testdir=build/testdir
else
echo "No testdir found" >&2
- exit 1
+ exit 0
fi
tar -caf testdir.tar.xz $testdir
echo "Installing CUDA support"
-retry wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_${CUDA}_amd64.deb
-retry sudo dpkg -i cuda-repo-ubuntu1804_${CUDA}_amd64.deb
-retry sudo apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub
+retry wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-keyring_1.0-1_all.deb
+sudo dpkg -i cuda-keyring_1.0-1_all.deb
retry sudo apt-get update -qq
cuda_prefix=${CUDA:0:4}
# Note: This is part of CMakeLists.txt file, not to be confused with
# CPackConfig.cmake.
-if(${CMAKE_VERSION} VERSION_LESS "3.9")
- set(CPACK_PACKAGE_DESCRIPTION "${CMAKE_PROJECT_DESCRIPTION}")
-endif()
-
# From CcacheVersion.cmake.
set(CPACK_PACKAGE_VERSION ${CCACHE_VERSION})
# CCACHE_VERSION_ORIGIN is set to "archive" in scenario 1 and "git" in scenario
# 3.
-set(version_info "2c13bce2609a02b70c92e84cdd177a6072f44d5e HEAD, tag: v4.3, origin/master, origin/HEAD, master")
+set(version_info "1527040bc2a278b9d3d51badb732ecf5841d8bb5 HEAD, tag: v4.7.4, origin/master, origin/HEAD, master")
+set(CCACHE_VERSION "unknown")
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.
find_package(Git QUIET)
if(NOT GIT_FOUND)
- set(CCACHE_VERSION "unknown")
message(WARNING "Could not find git")
else()
macro(git)
execute_process(
COMMAND "${GIT_EXECUTABLE}" ${ARGN}
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE git_stdout OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_VARIABLE git_stderr ERROR_STRIP_TRAILING_WHITESPACE)
endmacro()
endif()
endif()
-if(CCACHE_VERSION STREQUAL "")
+if("${CCACHE_VERSION}" STREQUAL "unknown")
# Scenario 2 or unexpected error.
- message(SEND_ERROR "Cannot determine Ccache version")
+ message(WARNING "Could not determine ccache version")
endif()
message(STATUS "Ccache version: ${CCACHE_VERSION}")
option(ENABLE_CPPCHECK "Enable static analysis with Cppcheck" OFF)
if(ENABLE_CPPCHECK)
- if(${CMAKE_VERSION} VERSION_LESS "3.10")
- message(WARNING "Cppcheck requires CMake 3.10")
+ find_program(CPPCHECK_EXE cppcheck)
+ mark_as_advanced(CPPCHECK_EXE) # Don't show in CMake UIs
+ if(CPPCHECK_EXE)
+ set(CMAKE_CXX_CPPCHECK
+ ${CPPCHECK_EXE}
+ --suppressions-list=${CMAKE_SOURCE_DIR}/misc/cppcheck-suppressions.txt
+ --inline-suppr
+ -q
+ --enable=all
+ --force
+ --std=c++17
+ -I ${CMAKE_SOURCE_DIR}
+ --template="cppcheck: warning: {id}:{file}:{line}: {message}"
+ -i src/third_party)
else()
- find_program(CPPCHECK_EXE cppcheck)
- mark_as_advanced(CPPCHECK_EXE) # Don't show in CMake UIs
- if(CPPCHECK_EXE)
- set(CMAKE_CXX_CPPCHECK
- ${CPPCHECK_EXE}
- --suppressions-list=${CMAKE_SOURCE_DIR}/misc/cppcheck-suppressions.txt
- --inline-suppr
- -q
- --enable=all
- --force
- --std=c++11
- -I ${CMAKE_SOURCE_DIR}
- --template="cppcheck: warning: {id}:{file}:{line}: {message}"
- -i src/third_party)
- else()
- message(WARNING "Cppcheck requested but executable not found")
- endif()
+ message(WARNING "Cppcheck requested but executable not found")
endif()
endif()
option(ENABLE_CLANG_TIDY "Enable static analysis with Clang-Tidy" OFF)
if(ENABLE_CLANG_TIDY)
- if(${CMAKE_VERSION} VERSION_LESS "3.6")
- message(WARNING "Clang-Tidy requires CMake 3.6")
+ find_program(CLANGTIDY clang-tidy)
+ if(CLANGTIDY)
+ set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY})
else()
- find_program(CLANGTIDY clang-tidy)
- if(CLANGTIDY)
- set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY})
- else()
- message(SEND_ERROR "Clang-Tidy requested but executable not found")
- endif()
+ message(SEND_ERROR "Clang-Tidy requested but executable not found")
endif()
endif()
CCACHE_COMPILER_WARNINGS "-Wno-zero-as-null-pointer-constant")
add_compile_flag_if_supported(
CCACHE_COMPILER_WARNINGS "-Wno-undefined-func-template")
- add_compile_flag_if_supported(
- CCACHE_COMPILER_WARNINGS "-Wno-return-std-move-in-c++11")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(
APPEND
# -Wduplicated-branches
# -Wuseless-cast
)
-
- # TODO: Exact version or reason unknown, discovered in Ubuntu 14 Docker test
- # with GCC 4.8.4
- if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8.5)
- add_compile_flag_if_supported(
- CCACHE_COMPILER_WARNINGS "-Wno-missing-field-initializers")
- add_compile_flag_if_supported(
- CCACHE_COMPILER_WARNINGS "-Wno-unused-variable")
- endif()
elseif(MSVC)
# Remove any warning level flags added by CMake.
string(REGEX REPLACE "/W[0-4]" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
--- /dev/null
+if(hiredis_FOUND)
+ return()
+endif()
+
+set(hiredis_FOUND FALSE)
+
+find_package(PkgConfig)
+if(PKG_CONFIG_FOUND)
+ pkg_check_modules(HIREDIS hiredis>=${hiredis_FIND_VERSION})
+ find_library(HIREDIS_LIBRARY ${HIREDIS_LIBRARIES} HINTS ${HIREDIS_LIBDIR})
+ find_path(HIREDIS_INCLUDE_DIR hiredis/hiredis.h HINTS ${HIREDIS_PREFIX}/include)
+else()
+ find_library(HIREDIS_LIBRARY hiredis)
+ find_path(HIREDIS_INCLUDE_DIR hiredis/hiredis.h)
+endif()
+
+if(HIREDIS_INCLUDE_DIR AND HIREDIS_LIBRARY)
+ mark_as_advanced(HIREDIS_INCLUDE_DIR HIREDIS_LIBRARY)
+
+ add_library(HIREDIS::HIREDIS UNKNOWN IMPORTED)
+ set_target_properties(
+ HIREDIS::HIREDIS
+ PROPERTIES
+ IMPORTED_LOCATION "${HIREDIS_LIBRARY}"
+ INTERFACE_COMPILE_OPTIONS "${HIREDIS_CFLAGS_OTHER}"
+ INTERFACE_INCLUDE_DIRECTORIES "${HIREDIS_INCLUDE_DIR}")
+ if(WIN32 AND STATIC_LINK)
+ target_link_libraries(HIREDIS::HIREDIS INTERFACE ws2_32)
+ endif()
+
+ set(hiredis_FOUND TRUE)
+ set(target HIREDIS::HIREDIS)
+elseif(HIREDIS_FROM_INTERNET)
+ message(STATUS "*** WARNING ***: Using hiredis from the internet because it was NOT found and HIREDIS_FROM_INTERNET is TRUE")
+
+ set(hiredis_version "1.1.0")
+
+ set(hiredis_dir ${CMAKE_BINARY_DIR}/hiredis-${hiredis_version})
+ set(hiredis_build ${CMAKE_BINARY_DIR}/hiredis-build)
+
+ include(FetchContent)
+
+ FetchContent_Declare(
+ hiredis
+ URL https://github.com/redis/hiredis/archive/v${hiredis_version}.tar.gz
+ URL_HASH SHA256=fe6d21741ec7f3fc9df409d921f47dfc73a4d8ff64f4ac6f1d95f951bf7f53d6
+ SOURCE_DIR ${hiredis_dir}
+ BINARY_DIR ${hiredis_build}
+ )
+
+ FetchContent_GetProperties(hiredis)
+
+ if(NOT hiredis_POPULATED)
+ FetchContent_Populate(hiredis)
+ endif()
+
+ set(
+ hiredis_sources
+ "${hiredis_dir}/alloc.c"
+ "${hiredis_dir}/async.c"
+ "${hiredis_dir}/dict.c"
+ "${hiredis_dir}/hiredis.c"
+ "${hiredis_dir}/net.c"
+ "${hiredis_dir}/read.c"
+ "${hiredis_dir}/sds.c"
+ "${hiredis_dir}/sockcompat.c"
+ )
+ add_library(libhiredis_static STATIC EXCLUDE_FROM_ALL ${hiredis_sources})
+ add_library(HIREDIS::HIREDIS ALIAS libhiredis_static)
+
+ if(WIN32)
+ target_compile_definitions(libhiredis_static PRIVATE _CRT_SECURE_NO_WARNINGS)
+ endif()
+
+ make_directory("${hiredis_dir}/include")
+ make_directory("${hiredis_dir}/include/hiredis")
+ file(GLOB hiredis_headers "${hiredis_dir}/*.h")
+ file(COPY ${hiredis_headers} DESTINATION "${hiredis_dir}/include/hiredis")
+ set_target_properties(
+ libhiredis_static
+ PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${hiredis_dir}/include>")
+
+ set(hiredis_FOUND TRUE)
+ set(target libhiredis_static)
+endif()
+
+if(WIN32 AND hiredis_FOUND)
+ target_link_libraries(${target} INTERFACE ws2_32)
+endif()
+unset(target)
+
+include(FeatureSummary)
+set_package_properties(
+ hiredis
+ PROPERTIES
+ URL "https://github.com/redis/hiredis"
+ DESCRIPTION "Hiredis is a minimalistic C client library for the Redis database"
+)
return()
endif()
-if(ZSTD_FROM_INTERNET)
+set(zstd_FOUND FALSE)
+
+find_package(PkgConfig)
+if(PKG_CONFIG_FOUND)
+ pkg_search_module(PC_ZSTD libzstd)
+ find_library(ZSTD_LIBRARY zstd HINTS ${PC_ZSTD_LIBDIR} ${PC_ZSTD_LIBRARY_DIRS})
+ find_path(ZSTD_INCLUDE_DIR zstd.h HINTS ${PC_ZSTD_INCLUDEDIR} ${PC_ZSTD_INCLUDE_DIRS})
+else()
+ find_library(ZSTD_LIBRARY zstd)
+ find_path(ZSTD_INCLUDE_DIR zstd.h)
+endif()
+
+if(ZSTD_LIBRARY AND ZSTD_INCLUDE_DIR)
+ mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY)
+
+ add_library(ZSTD::ZSTD UNKNOWN IMPORTED)
+ set_target_properties(
+ ZSTD::ZSTD
+ PROPERTIES
+ IMPORTED_LOCATION "${ZSTD_LIBRARY}"
+ INTERFACE_INCLUDE_DIRECTORIES "${ZSTD_INCLUDE_DIR}")
+
+ set(zstd_FOUND TRUE)
+elseif(ZSTD_FROM_INTERNET)
+ message(STATUS "*** WARNING ***: Using zstd from the internet because it was NOT found and ZSTD_FROM_INTERNET is TRUE")
+
# Although ${zstd_FIND_VERSION} was requested, let's download a newer version.
# Note: The directory structure has changed in 1.3.0; we only support 1.3.0
# and newer.
- set(zstd_version "1.4.9")
- set(zstd_url https://github.com/facebook/zstd/archive/v${zstd_version}.tar.gz)
+ set(zstd_version "1.5.2")
- set(zstd_dir ${CMAKE_BINARY_DIR}/zstd-${zstd_version})
+ set(zstd_dir ${CMAKE_BINARY_DIR}/zstd-${zstd_version})
set(zstd_build ${CMAKE_BINARY_DIR}/zstd-build)
- if(NOT EXISTS "${zstd_dir}.tar.gz")
- file(DOWNLOAD "${zstd_url}" "${zstd_dir}.tar.gz" STATUS download_status)
- list(GET download_status 0 error_code)
- if(error_code)
- file(REMOVE "${zstd_dir}.tar.gz")
- list(GET download_status 1 error_message)
- message(FATAL "Failed to download zstd: ${error_message}")
- endif()
- endif()
+ include(FetchContent)
+
+ FetchContent_Declare(
+ zstd
+ URL https://github.com/facebook/zstd/archive/v${zstd_version}.tar.gz
+ URL_HASH SHA256=f7de13462f7a82c29ab865820149e778cbfe01087b3a55b5332707abf9db4a6e
+ SOURCE_DIR ${zstd_dir}
+ BINARY_DIR ${zstd_build}
+ )
+
+ FetchContent_GetProperties(zstd)
- execute_process(
- COMMAND tar xf "${zstd_dir}.tar.gz"
- WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
- RESULT_VARIABLE tar_error)
- if(NOT tar_error EQUAL 0)
- message(FATAL "extracting ${zstd_dir}.tar.gz failed")
+ if(NOT zstd_POPULATED)
+ FetchContent_Populate(zstd)
endif()
set(ZSTD_BUILD_SHARED OFF)
set_target_properties(
libzstd_static
PROPERTIES
- INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${zstd_dir}/lib>")
+ INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${zstd_dir}/lib>"
+ )
set(zstd_FOUND TRUE)
-else()
- find_library(ZSTD_LIBRARY zstd)
- find_path(ZSTD_INCLUDE_DIR zstd.h)
-
- include(FindPackageHandleStandardArgs)
- find_package_handle_standard_args(
- zstd "please install libzstd or use -DZSTD_FROM_INTERNET=ON"
- ZSTD_INCLUDE_DIR ZSTD_LIBRARY)
- mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY)
-
- add_library(ZSTD::ZSTD UNKNOWN IMPORTED)
- set_target_properties(
- ZSTD::ZSTD
- PROPERTIES
- IMPORTED_LOCATION "${ZSTD_LIBRARY}"
- INTERFACE_INCLUDE_DIRECTORIES "${ZSTD_INCLUDE_DIR}")
endif()
include(FeatureSummary)
include(CheckFunctionExists)
set(functions
asctime_r
- geteuid
getopt_long
getpwuid
- gettimeofday
posix_fallocate
realpath
setenv
strndup
syslog
unsetenv
- utimes)
+ utimensat
+ utimes
+)
foreach(func IN ITEMS ${functions})
string(TOUPPER ${func} func_var)
set(func_var HAVE_${func_var})
check_function_exists(${func} ${func_var})
endforeach()
-include(CheckCSourceCompiles)
+include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_FLAGS -pthread)
-check_c_source_compiles(
+check_cxx_source_compiles(
[=[
#include <pthread.h>
int main()
set(CMAKE_REQUIRED_FLAGS)
include(CheckStructHasMember)
+check_struct_has_member("struct stat" st_atim sys/stat.h
+ HAVE_STRUCT_STAT_ST_ATIM LANGUAGE CXX)
check_struct_has_member("struct stat" st_ctim sys/stat.h
- HAVE_STRUCT_STAT_ST_CTIM)
+ HAVE_STRUCT_STAT_ST_CTIM LANGUAGE CXX)
check_struct_has_member("struct stat" st_mtim sys/stat.h
- HAVE_STRUCT_STAT_ST_MTIM)
+ HAVE_STRUCT_STAT_ST_MTIM LANGUAGE CXX)
+check_struct_has_member("struct stat" st_atimespec sys/stat.h
+ HAVE_STRUCT_STAT_ST_ATIMESPEC LANGUAGE CXX)
+check_struct_has_member("struct stat" st_ctimespec sys/stat.h
+ HAVE_STRUCT_STAT_ST_CTIMESPEC LANGUAGE CXX)
+check_struct_has_member("struct stat" st_mtimespec sys/stat.h
+ HAVE_STRUCT_STAT_ST_MTIMESPEC LANGUAGE CXX)
check_struct_has_member("struct statfs" f_fstypename sys/mount.h
- HAVE_STRUCT_STATFS_F_FSTYPENAME)
+ HAVE_STRUCT_STATFS_F_FSTYPENAME LANGUAGE CXX)
include(CheckCXXSourceCompiles)
check_cxx_source_compiles(
[=[
#include <immintrin.h>
+ #ifndef _MSC_VER // MSVC does not need explicit enabling of AVX2.
void func() __attribute__((target("avx2")));
+ #endif
void func() { _mm256_abs_epi8(_mm256_set1_epi32(42)); }
int main()
{
]=]
HAVE_AVX2)
-list(APPEND CMAKE_REQUIRED_LIBRARIES ws2_32)
-list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES ws2_32)
-
-include(CheckTypeSize)
-check_type_size("long long" HAVE_LONG_LONG)
-
if(WIN32)
set(_WIN32_WINNT 0x0600)
endif()
# alias
set(MTR_ENABLED "${ENABLE_TRACING}")
+if(HAVE_SYS_MMAN_H
+ AND HAVE_PTHREAD_MUTEXATTR_SETPSHARED
+ AND (HAVE_STRUCT_STAT_ST_MTIM OR HAVE_STRUCT_STAT_ST_MTIMESPEC)
+ AND (HAVE_LINUX_FS_H OR HAVE_STRUCT_STATFS_F_FSTYPENAME))
+ set(INODE_CACHE_SUPPORTED 1)
+endif()
+
+# Escape backslashes in SYSCONFDIR for C.
+file(TO_NATIVE_PATH "${CMAKE_INSTALL_FULL_SYSCONFDIR}" CONFIG_SYSCONFDIR_C_ESCAPED)
+string(REPLACE "\\" "\\\\" CONFIG_SYSCONFDIR_C_ESCAPED "${CONFIG_SYSCONFDIR_C_ESCAPED}")
+
configure_file(${CMAKE_SOURCE_DIR}/cmake/config.h.in
${CMAKE_BINARY_DIR}/config.h @ONLY)
--- /dev/null
+if(WIN32)
+ if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(program_files "$ENV{ProgramFiles}")
+
+ # For 32 bit builds.
+ if(CMAKE_SIZEOF_VOID_P EQUAL 4 AND ENV{ProgramFiles\(x86\)})
+ set(program_files "$ENV{ProgramFiles\(x86\)}")
+ endif()
+
+ if(NOT program_files)
+ if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
+ set(program_files "/Program Files")
+ else()
+ set(program_files "/Program Files (x86)")
+ endif()
+ endif()
+
+ file(TO_CMAKE_PATH "${program_files}/ccache" CMAKE_INSTALL_PREFIX)
+
+ set(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" CACHE STRING "System-wide installation prefix" FORCE)
+ endif()
+
+ if(NOT CMAKE_INSTALL_SYSCONFDIR)
+ set(program_data "$ENV{ALLUSERSPROFILE}")
+
+ if(NOT program_data)
+ set(program_data "/ProgramData")
+ endif()
+
+ file(TO_CMAKE_PATH "${program_data}/ccache" CMAKE_INSTALL_SYSCONFDIR)
+
+ set(CMAKE_INSTALL_SYSCONFDIR "${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH "System-wide config file location" FORCE)
+ endif()
+
+ set(CMAKE_INSTALL_BINDIR "" CACHE PATH "user executables directory" FORCE)
+ set(CMAKE_INSTALL_SBINDIR "" CACHE PATH "system administration executables directory" FORCE)
+ set(CMAKE_INSTALL_LIBEXECDIR "" CACHE PATH "program executables directory" FORCE)
+ set(CMAKE_INSTALL_LIBDIR "" CACHE PATH "object libraries directory" FORCE)
+endif()
+
+include(GNUInstallDirs)
add_library(standard_settings INTERFACE)
-# Not supported in CMake 3.4: target_compile_features(project_options INTERFACE
-# c_std_11 cxx_std_11)
+if(MSVC)
+ target_compile_options(standard_settings INTERFACE "/FI${CMAKE_BINARY_DIR}/config.h")
+else()
+ target_compile_options(
+ standard_settings
+ INTERFACE -include ${CMAKE_BINARY_DIR}/config.h
+ )
+endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|(Apple)?Clang$")
option(ENABLE_COVERAGE "Enable coverage reporting for GCC/Clang" FALSE)
endforeach()
include(StdAtomic)
+ include(StdFilesystem)
elseif(MSVC)
- target_compile_options(standard_settings INTERFACE /std:c++latest /Zc:preprocessor /Zc:__cplusplus /D_CRT_SECURE_NO_WARNINGS)
+ target_compile_options(
+ standard_settings
+ INTERFACE /Zc:preprocessor /Zc:__cplusplus /D_CRT_SECURE_NO_WARNINGS
+ )
+endif()
+
+if(WIN32)
+ target_compile_definitions(
+ standard_settings
+ INTERFACE WIN32_LEAN_AND_MEAN
+ )
endif()
include(CheckCXXSourceCompiles)
-set(CMAKE_REQUIRED_FLAGS ${CMAKE_CXX11_STANDARD_COMPILE_OPTION})
set(
check_std_atomic_source_code
[=[
target_link_libraries(standard_settings INTERFACE atomic)
endif()
endif()
-
-set(CMAKE_REQUIRED_FLAGS)
--- /dev/null
+# Check if std::filesystem needs -lstdc++fs
+
+include(CheckCXXSourceCompiles)
+
+set(
+ check_std_filesystem_source_code
+ [=[
+ #include <filesystem>
+ int main(void)
+ {
+ return std::filesystem::is_regular_file(\"/\") ? 0 : 1;
+ }
+ ]=])
+
+check_cxx_source_compiles("${check_std_filesystem_source_code}" std_filesystem_without_libfs)
+
+if(NOT std_filesystem_without_libfs)
+ set(CMAKE_REQUIRED_LIBRARIES stdc++fs)
+ check_cxx_source_compiles("${check_std_filesystem_source_code}" std_filesystem_with_libfs)
+ set(CMAKE_REQUIRED_LIBRARIES)
+ if(NOT std_filesystem_with_libfs)
+ message(FATAL_ERROR "Toolchain doesn't support std::filesystem with nor without -lstdc++fs")
+ else()
+ target_link_libraries(standard_settings INTERFACE stdc++fs)
+ endif()
+endif()
+++ /dev/null
-# Note: Compiling ccache via ccache is fine because the ccache version installed
-# in the system is used.
-
-# Calls `message(VERBOSE msg)` if and only if VERBOSE is available (since CMake
-# 3.15). Call CMake with --log-level=VERBOSE to view verbose messages.
-function(message_verbose msg)
- if(NOT ${CMAKE_VERSION} VERSION_LESS "3.15")
- message(VERBOSE ${msg})
- endif()
-endfunction()
-
-function(use_ccache)
- if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
- message(WARNING "use_ccache() disabled, as it is not called from the project top level")
- return()
- endif()
-
- find_program(CCACHE_PROGRAM ccache)
- if(NOT CCACHE_PROGRAM)
- message_verbose("Ccache program not found, not enabling ccache for faster recompilation")
- return()
- endif()
-
- message_verbose("Ccache enabled for faster recompilation")
-
- # Note: This will override any config and environment settings.
- set(ccache_env
- # Another option would be CMAKE_BINARY_DIR, but currently only one base
- # directory is supported.
- CCACHE_BASEDIR=${CMAKE_SOURCE_DIR}
-
- # In case of very old ccache versions (pre 3.3).
- CCACHE_CPP2=true
- )
-
- if(CMAKE_GENERATOR MATCHES "Ninja|Makefiles")
- find_program(ENV_PROGRAM env)
- if(ENV_PROGRAM)
- set(env_program ${ENV_PROGRAM}) # faster than "cmake -E env"
- else()
- set(env_program ${CMAKE_COMMAND} -E env)
- endif()
- foreach(lang IN ITEMS C CXX OBJC OBJCXX CUDA)
- set(CMAKE_${lang}_COMPILER_LAUNCHER
- ${env_program} ${ccache_env} ${CCACHE_PROGRAM}
- PARENT_SCOPE)
- endforeach()
- elseif(CMAKE_GENERATOR STREQUAL Xcode)
- foreach(lang IN ITEMS C CXX)
- set(launcher ${CMAKE_BINARY_DIR}/launch-${lang})
- file(WRITE ${launcher} "#!/bin/bash\n\n")
- foreach(key_val IN LISTS ccache_env)
- file(APPEND ${launcher} "export ${key_val}\n")
- endforeach()
- file(APPEND ${launcher}
- "exec \"${CCACHE_PROGRAM}\" \"${CMAKE_${lang}_COMPILER}\" \"$@\"\n")
- execute_process(COMMAND chmod a+rx ${launcher})
- endforeach()
- set(CMAKE_XCODE_ATTRIBUTE_CC ${CMAKE_BINARY_DIR}/launch-C PARENT_SCOPE)
- set(CMAKE_XCODE_ATTRIBUTE_CXX ${CMAKE_BINARY_DIR}/launch-CXX PARENT_SCOPE)
- set(CMAKE_XCODE_ATTRIBUTE_LD ${CMAKE_BINARY_DIR}/launch-C PARENT_SCOPE)
- set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS ${CMAKE_BINARY_DIR}/launch-CXX PARENT_SCOPE)
- endif()
-endfunction()
-
-if(MSVC)
- # Ccache does not support cl.exe-style arguments at this time.
- return()
-endif()
-
-option(USE_CCACHE "Use ccache to speed up recompilation time" TRUE)
-if(USE_CCACHE)
- use_ccache()
-endif()
+if(NOT CCACHE_DEV_MODE)
+ # For ccache, using a faster linker is in practice only relevant to reduce the
+ # compile-link-test cycle for developers, so use the standard linker for
+ # non-developer builds.
+ return()
+endif()
+
+if(DISABLE_FASTEST_LINKER)
+ message(STATUS "Not probing for fastest linker")
+ return()
+endif()
+
+if(MSVC)
+ message(STATUS "Using standard linker for MSVC")
+ return()
+endif()
+
+if(ENABLE_IPO)
+ message(STATUS "Using standard linker for IPO")
+ return()
+endif()
+
+if(NOT CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64)
+ # Be conservative and only probe for a faster linker on platforms that likely
+ # don't have toolchain bugs. See for example
+ # <https://www.sourceware.org/bugzilla/show_bug.cgi?id=22838>.
+ message(STATUS "Not probing for faster linker on ${CMAKE_SYSTEM_PROCESSOR}")
+ return()
+endif()
+
function(check_linker linker)
string(TOUPPER ${linker} upper_linker)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/CMakefiles/CMakeTmp/main.c" "int main() { return 0; }")
return()
endif()
- set(use_default_linker 1)
+ # prefer an lld that matches the clang version
+ if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION MATCHES "^([0-9]*)\\.")
+ check_linker(lld-${CMAKE_MATCH_1})
+ if(HAVE_LD_LLD-${CMAKE_MATCH_1})
+ link_libraries("-fuse-ld=lld-${CMAKE_MATCH_1}")
+ message(STATUS "Using lld-${CMAKE_MATCH_1} linker")
+ return()
+ endif()
+ endif()
+
check_linker(lld)
if(HAVE_LD_LLD)
link_libraries("-fuse-ld=lld")
- set(use_default_linker 0)
message(STATUS "Using lld linker")
- else()
- check_linker(gold)
- if(HAVE_LD_GOLD)
- link_libraries("-fuse-ld=gold")
- set(use_default_linker 0)
- message(STATUS "Using gold linker")
- endif()
+ return()
endif()
- if(use_default_linker)
- message(STATUS "Using default linker")
+
+ check_linker(gold)
+ if(HAVE_LD_GOLD)
+ link_libraries("-fuse-ld=gold")
+ message(STATUS "Using gold linker")
+ return()
endif()
+
+ message(STATUS "Using default linker")
endfunction()
option(USE_FASTER_LINKER "Use the lld or gold linker instead of the default for faster linking" TRUE)
+// This file is included by all compilation units, including those in
+// src/third_party. It should only contain macros and typedefs.
+
#pragma once
#ifdef __clang__
# pragma clang diagnostic push
# endif
#endif
+#ifdef __MINGW32__
+# define __USE_MINGW_ANSI_STDIO 1
+# define __STDC_FORMAT_MACROS 1
+#endif
+
// For example for vasprintf under i686-w64-mingw32-g++-posix. The later
// definition of _XOPEN_SOURCE disables certain features on Linux, so we need
// _GNU_SOURCE to re-enable them (makedev, tm_zone).
#cmakedefine _WIN32_WINNT @_WIN32_WINNT@
// clang-format on
-#define SYSCONFDIR "@CMAKE_INSTALL_FULL_SYSCONFDIR@"
-
#ifdef __clang__
# pragma clang diagnostic pop
#endif
// Define if your compiler supports AVX2.
#cmakedefine HAVE_AVX2
-// Define if you have the "geteuid" function.
-#cmakedefine HAVE_GETEUID
-
// Define if you have the "getopt_long" function.
#cmakedefine HAVE_GETOPT_LONG
// Define if you have the "getpwuid" function.
#cmakedefine HAVE_GETPWUID
-// Define if you have the "gettimeofday" function.
-#cmakedefine HAVE_GETTIMEOFDAY
-
// Define if you have the <linux/fs.h> header file.
#cmakedefine HAVE_LINUX_FS_H
// Define if "f_fstypename" is a member of "struct statfs".
#cmakedefine HAVE_STRUCT_STATFS_F_FSTYPENAME
+// Define if "st_atim" is a member of "struct stat".
+#cmakedefine HAVE_STRUCT_STAT_ST_ATIM
+
// Define if "st_ctim" is a member of "struct stat".
#cmakedefine HAVE_STRUCT_STAT_ST_CTIM
// Define if "st_mtim" is a member of "struct stat".
#cmakedefine HAVE_STRUCT_STAT_ST_MTIM
+// Define if "st_atimespec" is a member of "struct stat".
+#cmakedefine HAVE_STRUCT_STAT_ST_ATIMESPEC
+
+// Define if "st_ctimespec" is a member of "struct stat".
+#cmakedefine HAVE_STRUCT_STAT_ST_CTIMESPEC
+
+// Define if "st_mtimespec" is a member of "struct stat".
+#cmakedefine HAVE_STRUCT_STAT_ST_MTIMESPEC
+
// Define if you have the "syslog" function.
#cmakedefine HAVE_SYSLOG
// Define if you have the "unsetenv" function.
#cmakedefine HAVE_UNSETENV
+// Define if you have the "utimensat" function.
+#cmakedefine HAVE_UTIMENSAT
+
// Define if you have the "utimes" function.
#cmakedefine HAVE_UTIMES
# undef HAVE_STRUCT_STAT_ST_CTIM
# undef HAVE_STRUCT_STAT_ST_MTIM
#endif
+
+// Typedefs that make it possible to use common types in ccache header files
+// without including core/wincompat.hpp.
+#ifdef _WIN32
+# ifdef _MSC_VER
+typedef unsigned __int32 mode_t;
+typedef int pid_t;
+# endif // _MSC_VER
+#endif // _WIN32
+
+// O_BINARY is needed when reading binary data on Windows, so use it everywhere
+// with a compatibility define for Unix platforms.
+#if !defined(_WIN32) && !defined(O_BINARY)
+# define O_BINARY 0
+#endif
+#if !defined(_WIN32) && !defined(O_TEXT)
+# define O_TEXT 0
+#endif
+
+#ifndef ESTALE
+# define ESTALE -1
+#endif
+
+#define SYSCONFDIR "@CONFIG_SYSCONFDIR_C_ESCAPED@"
+
+#cmakedefine INODE_CACHE_SUPPORTED
+
+// Buffer size for I/O operations. Should be a multiple of 4 KiB.
+#define CCACHE_READ_BUFFER_SIZE 65536
-Ccache authors
-==============
+= Ccache authors
Ccache was originally written by Andrew Tridgell and is currently developed and
maintained by Joel Rosdahl.
* Abubakar Nur Khalil
* Aleksander Salwa
+* Alexander Falkenstern
* Alexander Korsunsky
* Alexander Lanin
+* Alexey Sheplyakov
+* Alexey Telishev
* Alexey Tourbin
* Alfred Landrum
* Anders F Björklund
* Chris Burr
* Clemens Rabe
* Cristian Adam
+* Daniel Richtmann
* David Givone
* Deepak Yadav
* Doug Anderson
* Enrico Sorichetti
* Erik Flodin
* Evangelos Foutras
+* Florin Trofin
* Francois Marier
* Gabriel Scherer
* Geert Bosch
* Igor Pylypiv
* Ivan Vaigult
* Ivan Volnov
+* Jacob Young
* Jiang Jiang
* Joel Galenson
* Joel Rosdahl
* Ka Ho Ng
* Karl Chen
* Khem Raj
+* Kira Bruneau
* Kona Blend
* Kovarththanan Rajaratnam
* Lalit Chhabra
* Lars Gustäbel
* Leanid Chaika
* Loïc Yhuel
+* Louis Caron
* Luboš Luňák
-* luzpaz
* Maarten Maathuis
+* Marius Zwicker
* Mark Starovoytov
* Martin Ettl
* Martin Pool
* Matthias Kretz
* Matt Whitlock
* Melven Roehrig-Zoellner
+* Michael Kruse
* Michael Marineau
* Michael Meeks
* Michał Mirosław
* Nick Schultz
* Norbert Lange
* Oded Shimon
+* Oleg Sidorkin
* Olle Liljenzin
* Orgad Shaneh
* Orion Poplawski
* Peter Steinberger
* Petr Štetiar
* Philippe Proulx
+* Philipp Gortan
* Philipp Storz
* Rafael Kitover
+* Raihaan Shouhell
* Ramiro Polla
* Robert Yang
* Robin H. Johnson
* Rolf Bjarne Kvinge
-* RW
+* Rosen Penev
+* R. Voggenauer
* Ryan Brown
+* Ryan Burns
* Ryan Egesdahl
* Sam Gross
* Sergei Trofimovich
+* Sergey Semushin
* Steffen Dettmer
+* Steve Mokris
* Stuart Henderson
* Sumit Jamgade
* Thomas Otto
* Tim Potter
* Tomasz Miąsko
* Tom Hughes
+* Tom Stellard
* Tor Arne Vestbø
* Vadim Petrochenkov
+* Varun Sharma
+* Vili Väinölä
* Ville Skyttä
* William S Fulton
* Wilson Snyder
-find_program(ASCIIDOC_EXE asciidoc)
-mark_as_advanced(ASCIIDOC_EXE) # Don't show in CMake UIs
+find_program(ASCIIDOCTOR_EXE asciidoctor)
+mark_as_advanced(ASCIIDOCTOR_EXE) # Don't show in CMake UIs
-if(NOT ASCIIDOC_EXE)
- message(WARNING "Could not find asciidoc; documentation will not be generated")
+if(NOT ASCIIDOCTOR_EXE)
+ message(WARNING "Could not find asciidoctor; documentation will not be generated")
else()
- #
- # HTML documentation
- #
- function(generate_html adoc_file)
+ function(generate_doc backend adoc_file output_file)
get_filename_component(base_name "${adoc_file}" NAME_WE)
- set(html_file "${base_name}.html")
add_custom_command(
- OUTPUT "${html_file}"
+ OUTPUT "${output_file}"
COMMAND
- ${ASCIIDOC_EXE}
- -o "${html_file}"
+ ${ASCIIDOCTOR_EXE}
+ -o "${output_file}"
-a revnumber="${CCACHE_VERSION}"
- -a toc
- -b xhtml11
+ -a icons=font
+ -a toc=left
+ -a sectanchors
+ -a stylesheet="${CMAKE_CURRENT_SOURCE_DIR}/ccache-doc.css"
+ -b "${backend}"
"${CMAKE_SOURCE_DIR}/${adoc_file}"
MAIN_DEPENDENCY "${CMAKE_SOURCE_DIR}/${adoc_file}"
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/ccache-doc.css"
)
- set(html_files "${html_files}" "${html_file}" PARENT_SCOPE)
+ set(doc_files "${doc_files}" "${output_file}" PARENT_SCOPE)
endfunction()
- generate_html(LICENSE.adoc)
- generate_html(doc/AUTHORS.adoc)
- generate_html(doc/MANUAL.adoc)
- generate_html(doc/NEWS.adoc)
-
- add_custom_target(doc-html DEPENDS "${html_files}")
- set(doc_files "${html_files}")
+ #
+ # HTML documentation
+ #
+ generate_doc(html LICENSE.adoc LICENSE.html)
+ generate_doc(html doc/AUTHORS.adoc AUTHORS.html)
+ generate_doc(html doc/MANUAL.adoc MANUAL.html)
+ generate_doc(html doc/NEWS.adoc NEWS.html)
+ add_custom_target(doc-html DEPENDS "${doc_files}")
#
# Man page
#
- find_program(A2X_EXE a2x)
- mark_as_advanced(A2X_EXE) # Don't show in CMake UIs
- if(NOT A2X_EXE)
- message(WARNING "Could not find a2x; man page will not be generated")
- else()
- # MANUAL.adoc -> MANUAL.xml -> man page
- add_custom_command(
- OUTPUT MANUAL.xml
- COMMAND
- ${ASCIIDOC_EXE}
- -o -
- -a revnumber=${CCACHE_VERSION}
- -d manpage
- -b docbook "${CMAKE_SOURCE_DIR}/doc/MANUAL.adoc"
- | perl -pe 's!<literal>\(.*?\)</literal>!<emphasis role="strong">\\1</emphasis>!g'
- >MANUAL.xml
- MAIN_DEPENDENCY "${CMAKE_SOURCE_DIR}/doc/MANUAL.adoc"
- )
- add_custom_command(
- OUTPUT ccache.1
- COMMAND ${A2X_EXE} --doctype manpage --format manpage MANUAL.xml
- MAIN_DEPENDENCY MANUAL.xml
- )
- add_custom_target(doc-man-page DEPENDS ccache.1)
- install(
- FILES "${CMAKE_CURRENT_BINARY_DIR}/ccache.1"
- DESTINATION "${CMAKE_INSTALL_MANDIR}/man1"
- )
- set(doc_files "${doc_files}" ccache.1)
- endif()
+ generate_doc(manpage doc/MANUAL.adoc ccache.1.tmp)
+ add_custom_command(
+ OUTPUT ccache.1
+ # Convert monospace to bold since that's typically rendered better when
+ # viewing the man page.
+ COMMAND perl -pe "'s!\\\\f\\(CR(.*?)\\\\fP!\\\\fB\\1\\\\fP!g'" ccache.1.tmp >ccache.1
+ MAIN_DEPENDENCY ccache.1.tmp
+ )
+ install(
+ FILES "${CMAKE_CURRENT_BINARY_DIR}/ccache.1"
+ DESTINATION "${CMAKE_INSTALL_MANDIR}/man1"
+ )
+ add_custom_target(doc-man-page DEPENDS ccache.1)
- add_custom_target(doc ALL DEPENDS "${doc_files}")
+ add_custom_target(doc ALL DEPENDS doc-html doc-man-page)
endif()
To build ccache you need:
-- CMake 3.4.3 or newer.
-- A C++11 compiler. See [Supported platforms, compilers and
+- CMake 3.15 or newer.
+- A C++17 compiler. See [Supported platforms, compilers and
languages](https://ccache.dev/platform-compiler-language-support.html) for
details.
- A C99 compiler.
-- [libzstd](http://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
- two options:
- 1. Install zstd in a custom path and set `CMAKE_PREFIX_PATH` to it, e.g.
- by passing `-DCMAKE_PREFIX_PATH=/some/custom/path` to `cmake`, or
- 2. Pass `-DZSTD_FROM_INTERNET=ON` to `cmake` to make it download libzstd
- from the Internet and unpack it in the local binary tree. Ccache will
- then be linked statically to the locally built libzstd.
+- [libzstd](http://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, it will be
+ automatically downloaded, built and linked statically as part of the build
+ process. To disable this, pass `-DZSTD_FROM_INTERNET=OFF` to `cmake`. You can
+ also install zstd in a custom path and pass
+ `-DCMAKE_PREFIX_PATH=/some/custom/path` to `cmake`.
- To link libzstd statically you can use `-DZSTD_LIBRARY=/path/to/libzstd.a`.
+ To link libzstd statically (and you have a static libzstd available), pass
+ `-DSTATIC_LINK=ON` to `cmake`. This is the default on Windows. Alternatively,
+ use `-DZSTD_LIBRARY=/path/to/libzstd.a`.
Optional:
+- [hiredis](https://github.com/redis/hiredis) for the Redis storage backend. If
+ you don't have libhiredis installed and can't or don't want to install it in a
+ standard system location, it will be automatically downloaded, built and
+ linked statically as part of the build process. To disable this, pass
+ `-DHIREDIS_FROM_INTERNET=OFF` to cmake. You can also install hiredis in a
+ custom path and pass `-DCMAKE_PREFIX_PATH=/some/custom/path` to `cmake`.
+
+ To link libhiredis statically (and you have a static libhiredis available),
+ pass `-DSTATIC_LINK=ON` to `cmake`. This is the default on Windows.
+ Alternatively, use `-DHIREDIS_LIBRARY=/path/to/libhiredis.a`.
- 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.
+- [Asciidoctor](https://asciidoctor.org) to build the HTML documentation.
- [Python](https://www.python.org) to debug and run the performance test suite.
+Reference configurations:
+
+- See [CI configurations](../.github/workflows/build.yaml) for a selection of
+ regularly tested build setups, including cross-compiling and explicit
+ dependencies required in Debian/Ubuntu environment.
Installation
------------
Here is the typical way to build and install ccache:
- mkdir build
- cd build
- cmake -DCMAKE_BUILD_TYPE=Release ..
- make
- make install
+```bash
+mkdir build
+cd build
+cmake -DCMAKE_BUILD_TYPE=Release ..
+make
+make install
+```
You can set the installation directory to e.g. `/usr` by adding
`-DCMAKE_INSTALL_PREFIX=/usr` to the `cmake` command. You can set the directory
-where the secondary configuration file should be located to e.g. `/etc` by
-adding `-DCMAKE_INSTALL_SYSCONFDIR=/etc`.
-
-There are two ways to use ccache. You can either prefix your compilation
-commands with `ccache` or you can create a symbolic link (named as your
-compiler) to ccache. The first method is most convenient if you just want to
-try out ccache or wish to use it for some specific projects. The second method
-is most useful for when you wish to use ccache for all your compilations.
-
-To install for usage by the first method just copy ccache to somewhere in your
-path.
-
-To install for the second method, do something like this:
-
- cp ccache /usr/local/bin/
- ln -s ccache /usr/local/bin/gcc
- ln -s ccache /usr/local/bin/g++
- ln -s ccache /usr/local/bin/cc
- ln -s ccache /usr/local/bin/c++
-
-And so forth. This will work as long as `/usr/local/bin` comes before the path
-to the compiler (which is usually in `/usr/bin`). After installing you may wish
-to run `which gcc` to make sure that the correct link is being used.
-
-NOTE: Do not use a hard link, use a symbolic link. A hard link will cause
-"interesting" problems.
+where the system configuration file should be located to e.g. `/etc` by adding
+`-DCMAKE_INSTALL_SYSCONFDIR=/etc`.
+
+There are two different ways to use ccache to cache a compilation:
+
+1. Prefix your compilation command with `ccache`. This method is most convenient
+ if you just want to try out ccache or wish to use it for some specific
+ projects.
+2. Let ccache masquerade as the compiler. This method is most useful when you
+ wish to use ccache for all your compilations. To do this, create a symbolic
+ link to ccache named as the compiler. For example, here is how to set up
+ ccache to masquerade as `gcc` and `g++`:
+
+ ```bash
+ cp ccache /usr/local/bin/
+ ln -s ccache /usr/local/bin/gcc
+ ln -s ccache /usr/local/bin/g++
+ ```
+
+ On platforms that don't support symbolic links you can simply copy ccache to the
+ compiler name instead for a similar effect:
+
+ ```bash
+ cp ccache /usr/local/bin/gcc
+ cp ccache /usr/local/bin/g++
+ ```
+
+ And so forth. This will work as long as the directory with symbolic links or
+ ccache copies comes before the directory with the compiler (typically
+ `/usr/bin`) in `PATH`.
-CCACHE(1)
-=========
-:man source: ccache
-:man version: {revnumber}
-:man manual: ccache Manual
+= ccache(1)
+:mansource: Ccache {revnumber}
-
-Name
-----
+== Name
ccache - a fast C/C++ compiler cache
-Synopsis
---------
+== Synopsis
[verse]
*ccache* [_options_]
*ccache* _compiler_ [_compiler options_]
-_compiler_ [_compiler options_] (via symbolic link)
+_compiler_ [_compiler options_] (ccache masquerading as the compiler)
-Description
------------
+== Description
Ccache is a compiler cache. It speeds up recompilation by caching the result of
previous compilations and detecting when the same compilation is being done
Ccache has been carefully written to always produce exactly the same compiler
output that you would get without the cache. The only way you should be able to
tell that you are using ccache is the speed. Currently known exceptions to this
-goal are listed under <<_caveats,CAVEATS>>. If you discover an undocumented case
-where ccache changes the output of your compiler, please let us know.
-
+goal are listed under _<<Caveats>>_. If you discover an undocumented case where
+ccache changes the output of your compiler, please let us know.
-Run modes
----------
-There are two ways to use ccache. You can either prefix your compilation
-commands with *ccache* or you can let ccache masquerade as the compiler by
-creating a symbolic link (named as the compiler) to ccache. The first method is
-most convenient if you just want to try out ccache or wish to use it for some
-specific projects. The second method is most useful for when you wish to use
-ccache for all your compilations.
+== Run modes
-To use the first method, just make sure that *ccache* is in your *PATH*.
-
-To use the symlinks method, do something like this:
+There are two different ways to use ccache to cache a compilation:
+1. Prefix your compilation command with `ccache`. This method is most convenient
+ if you just want to try out ccache or wish to use it for some specific
+ projects. Example:
++
+-------------------------------------------------------------------------------
+ccache gcc -c example.c
+-------------------------------------------------------------------------------
++
+2. Let ccache masquerade as the compiler. This method is most useful when you
+ wish to use ccache for all your compilations. To do this, create a symbolic
+ link to ccache named as the compiler. For example, here is set up ccache to
+ masquerade as `gcc` and `g++`:
++
-------------------------------------------------------------------------------
cp ccache /usr/local/bin/
ln -s ccache /usr/local/bin/gcc
ln -s ccache /usr/local/bin/g++
-ln -s ccache /usr/local/bin/cc
-ln -s ccache /usr/local/bin/c++
-------------------------------------------------------------------------------
-
-And so forth. This will work as long as the directory with symlinks comes
-before the path to the compiler (which is usually in `/usr/bin`). After
-installing you may wish to run ``which gcc'' to make sure that the correct link
-is being used.
-
++
+On platforms that don't support symbolic links you can simply copy ccache to the
+compiler name instead for a similar effect:
++
+-------------------------------------------------------------------------------
+cp ccache /usr/local/bin/gcc
+cp ccache /usr/local/bin/g++
+-------------------------------------------------------------------------------
++
+And so forth. This will work as long as the directory with symbolic links or
+ccache copies comes before the directory with the compiler (typically
+`/usr/bin`) in `PATH`.
++
WARNING: The technique of letting ccache masquerade as the compiler works well,
-but currently doesn't interact well with other tools that do the same thing.
-See _<<_using_ccache_with_other_compiler_wrappers,Using ccache with other
-compiler wrappers>>_.
+but currently doesn't interact well with other tools that do the same thing. See
+_<<Using ccache with other compiler wrappers>>_.
-WARNING: Use a symbolic links for masquerading, not hard links.
-Command line options
---------------------
+== Command line options
-These command line options only apply when you invoke ccache as ``ccache''.
-When invoked as a compiler (via a symlink as described in the previous
-section), the normal compiler options apply and you should refer to the
-compiler's documentation.
+These command line options only apply when you invoke ccache as "`ccache`". When
+ccache masquerades as a compiler (as described in the previous section), the
+normal compiler options apply and you should refer to the compiler's
+documentation.
-Common options
-~~~~~~~~~~~~~~
+=== Common options
-*`-c`*, *`--cleanup`*::
+*-c*, *--cleanup*::
Clean up the cache by removing old cached files until the specified file
number and cache size limits are not exceeded. This also recalculates the
cleanup is mostly useful if you manually modify the cache contents or
believe that the cache size statistics may be inaccurate.
-*`-C`*, *`--clear`*::
+*-C*, *--clear*::
Clear the entire cache, removing all cached files, but keeping the
configuration file.
-*`--config-path`* _PATH_::
+*--config-path* _PATH_::
- 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.
+ Let the command line options operate on configuration file _PATH_ instead of
+ the default. Using this option has the same effect as setting (overriding)
+ the environment variable `CCACHE_CONFIGPATH` temporarily.
-*`-d`*, *`--directory`* _PATH_::
+*-d*, *--dir* _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.
+ Let the 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_::
+*--evict-namespace* _NAMESPACE_::
+
+ Remove files created in the given <<config_namespace,*namespace*>> from the
+ cache.
+
+*--evict-older-than* _AGE_::
Remove files older than _AGE_ from the cache. _AGE_ should be an unsigned
- integer with a `d` (days) or `s` (seconds) suffix.
+ integer with a `d` (days) or `s` (seconds) suffix. If combined with
+ `--evict-namespace`, only remove old files within that namespace.
-*`-h`*, *`--help`*::
+*-h*, *--help*::
Print a summary of command line options.
-*`-F`* _NUM_, *`--max-files`* _NUM_::
+*-F* _NUM_, *--max-files* _NUM_::
Set the maximum number of files allowed in the cache to _NUM_. Use 0 for no
limit. The value is stored in a configuration file in the cache directory
and applies to all future compilations.
-*`-M`* _SIZE_, *`--max-size`* _SIZE_::
+*-M* _SIZE_, *--max-size* _SIZE_::
Set the maximum size of the files stored in the cache. _SIZE_ should be a
number followed by an optional suffix: k, M, G, T (decimal), Ki, Mi, Gi or
stored in a configuration file in the cache directory and applies to all
future compilations.
-*`-X`* _LEVEL_, *`--recompress`* _LEVEL_::
+*-X* _LEVEL_, *--recompress* _LEVEL_::
Recompress the cache to level _LEVEL_ using the Zstandard algorithm. The
level can be an integer, with the same semantics as the
- <<config_compression_level,*compression_level*>> configuration option), or
+ <<config_compression_level,*compression_level*>> configuration option, or
the special value *uncompressed* for no compression. See
- _<<_cache_compression,Cache compression>>_ for more information. This can
- potentionally take a long time since all files in the cache need to be
- visited. Only files that are currently compressed with a different level
- than _LEVEL_ will be recompressed.
+ _<<Cache compression>>_ for more information. This can potentionally take a
+ long time since all files in the cache need to be visited. Only files that
+ are currently compressed with a different level than _LEVEL_ will be
+ recompressed.
-*`-o`* _KEY=VALUE_, *`--set-config`* _KEY_=_VALUE_::
+*-o* _KEY=VALUE_, *--set-config* _KEY_=_VALUE_::
- Set configuration option _KEY_ to _VALUE_. See
- _<<_configuration,Configuration>>_ for more information.
+ Set configuration option _KEY_ to _VALUE_. See _<<Configuration>>_ for more
+ information.
-*`-x`*, *`--show-compression`*::
+*-x*, *--show-compression*::
- Print cache compression statistics. See _<<_cache_compression,Cache
- compression>>_ for more information. This can potentionally take a long
- time since all files in the cache need to be visited.
+ Print cache compression statistics. See _<<Cache compression>>_ for more
+ information. This can potentionally take a long time since all files in the
+ cache need to be visited.
-*`-p`*, *`--show-config`*::
+*-p*, *--show-config*::
Print current configuration options and from where they originate
(environment variable, configuration file or compile-time default) in
human-readable format.
-*`-s`*, *`--show-stats`*::
+*--show-log-stats*::
+
+ Print statistics counters from the stats log in human-readable format. See
+ <<config_stats_log,*stats_log*>>. Use `-v`/`--verbose` once or twice for
+ more details.
+
+*-s*, *--show-stats*::
Print a summary of configuration and statistics counters in human-readable
- format.
+ format. Use `-v`/`--verbose` once or twice for more details.
+
+*-v*, *--verbose*::
-*`-V`*, *`--version`*::
+ Increase verbosity. The option can be given multiple times.
+
+*-V*, *--version*::
Print version and copyright information.
-*`-z`*, *`--zero-stats`*::
+*-z*, *--zero-stats*::
Zero the cache statistics (but not the configuration options).
-Options for scripting or debugging
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Options for remote file-based storage
+
+*--trim-dir* _PATH_::
+
+ Remove old files from directory _PATH_ until it is at most the size
+ specified by `--trim-max-size`.
++
+WARNING: Don't use this option to trim the local cache. To trim the local cache
+directory to a certain size, use `CCACHE_MAXSIZE=_SIZE_ ccache -c`.
+
+*--trim-max-size* _SIZE_::
+
+ Specify the maximum size for `--trim-dir`. _SIZE_ should be a number
+ followed by an optional suffix: k, M, G, T (decimal), Ki, Mi, Gi or Ti
+ (binary). The default suffix is G.
-*`--checksum-file`* _PATH_::
+*--trim-method* _METHOD_::
+
+ Specify the method to trim a directory with `--trim-dir`. Possible values
+ are:
++
+--
+*atime*::
+ LRU (least recently used) using the file access timestamp. This is the
+ default.
+*mtime*::
+ LRU (least recently used) using the file modification timestamp.
+--
- Print the checksum (64 bit XXH3) of the file at _PATH_.
+=== Options for scripting or debugging
-*`--dump-manifest`* _PATH_::
+*--checksum-file* _PATH_::
- Dump manifest file at _PATH_ in text format to standard output. This is
- only useful when debugging ccache and its behavior.
+ Print the checksum (128 bit XXH3) of the file at _PATH_ (`-` for standard
+ input).
-*`--dump-result`* _PATH_::
+*--extract-result* _PATH_::
- Dump result file at _PATH_ in text format to standard output. This is only
- useful when debugging ccache and its behavior.
+ Extract data stored in the result file at _PATH_ (`-` for standard input).
+ The data will be written to `ccache-result.*` files in to the current
+ working directory. This option is only useful when debugging ccache and its
+ behavior.
-*`--extract-result`* _PATH_::
+*-k* _KEY_, *--get-config* _KEY_::
- Extract data stored in the result file at _PATH_. The data will be written
- to *ccache-result.** files in to the current working directory. This is
- only useful when debugging ccache and its behavior.
+ Print the value of configuration option _KEY_. See _<<Configuration>>_ for
+ more information.
-*`-k`* _KEY_, *`--get-config`* _KEY_::
+*--hash-file* _PATH_::
- Print the value of configuration option _KEY_. See
- _<<_configuration,Configuration>>_ for more information.
+ Print the hash (160 bit BLAKE3) of the file at _PATH_ (`-` for standard
+ input). This is only useful when debugging ccache and its behavior.
-*`--hash-file`* _PATH_::
+*--inspect* _PATH_::
- Print the hash (160 bit BLAKE3) of the file at _PATH_. This is only useful
- when debugging ccache and its behavior.
+ Print the content of a result or manifest file at _PATH_ (`-` for standard
+ input) to standard output in human-readable format. File content embedded in
+ a result file will however not be printed; use `--extract-result` to extract
+ the file content. This option is only useful when debugging ccache and its
+ behavior.
-*`--print-stats`*::
+*--print-stats*::
Print statistics counter IDs and corresponding values in machine-parsable
(tab-separated) format.
-Extra options
-~~~~~~~~~~~~~
+=== Extra options
When run as a compiler, ccache usually just takes the same command line options
as the compiler you are using. The only exception to this is the option
-*--ccache-skip*. That option can be used to tell ccache to avoid interpreting
+`--ccache-skip`. That option can be used to tell ccache to avoid interpreting
the next option in any way and to pass it along to the compiler as-is.
-NOTE: *--ccache-skip* currently only tells ccache not to interpret the next
+NOTE: `--ccache-skip` currently only tells ccache not to interpret the next
option as a special compiler option -- the option will still be included in the
direct mode hash.
it needs the input filename to determine the name of the resulting object file
(among other things). The heuristic ccache uses when parsing the command line
is that any argument that exists as a file is treated as an input file name. By
-using *--ccache-skip* you can force an option to not be treated as an input
+using `--ccache-skip` you can force an option to not be treated as an input
file name and instead be passed along to the compiler as a command line option.
-Another case where *--ccache-skip* can be useful is if ccache interprets an
+Another case where `--ccache-skip` can be useful is if ccache interprets an
option specially but shouldn't, since the option has another meaning for your
compiler than what ccache thinks.
-Configuration
--------------
+== Configuration
-ccache's default behavior can be overridden by options in configuration files,
+Ccache's default behavior can be overridden by options in configuration files,
which in turn can be overridden by environment variables with names starting
-with *CCACHE_*. Ccache normally reads configuration from two files: first a
+with `CCACHE_`. Ccache normally reads configuration from two files: first a
system-level configuration file and secondly a cache-specific configuration
file. The priorities of configuration options are as follows (where 1 is
highest):
1. Environment variables.
-2. The primary (cache-specific) configuration file (see below).
-3. The secondary (system-wide read-only) configuration file
- *_<sysconfdir>_/ccache.conf* (typically */etc/ccache.conf* or
- */usr/local/etc/ccache.conf*).
+2. The cache-specific configuration file (see below).
+3. The system (read-only) configuration file `<sysconfdir>/ccache.conf`
+ (typically `/etc/ccache.conf` or `/usr/local/etc/ccache.conf`).
4. Compile-time defaults.
-As a special case, if the the environment variable *CCACHE_CONFIGPATH* is set
-it specifies the primary configuration file and the secondary (system-wide)
-configuration file won't be read.
+As a special case, if the environment variable `CCACHE_CONFIGPATH` is set it
+specifies the configuration file, and the system configuration file won't be
+read.
-Location of the primary configuration file
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Location of the configuration file
-The location of the primary (cache-specific) configuration is determined like
-this:
+The location of the cache-specific configuration file is determined like this on
+non-Windows systems:
-1. If *CCACHE_CONFIGPATH* is set, use that path.
-2. Otherwise, if the environment variable *CCACHE_DIR* is set then use
- *$CCACHE_DIR/ccache.conf*.
-3. Otherwise, if <<config_cache_dir,*cache_dir*>> is set in the secondary
- (system-wide) configuration file then use *<cache_dir>/ccache.conf*.
-4. Otherwise, if there is a legacy *$HOME/.ccache* directory then use
- *$HOME/.ccache/ccache.conf*.
-5. Otherwise, if *XDG_CONFIG_HOME* is set then use
- *$XDG_CONFIG_HOME/ccache/ccache.conf*.
-6. Otherwise, use *%APPDATA%/ccache/ccache.conf* (Windows),
- *$HOME/Library/Preferences/ccache/ccache.conf* (macOS) or
- *$HOME/.config/ccache/ccache.conf* (other systems).
+1. If `CCACHE_CONFIGPATH` is set, use that path.
+2. Otherwise, if the environment variable `CCACHE_DIR` is set then use
+ `$CCACHE_DIR/ccache.conf`.
+3. Otherwise, if <<config_cache_dir,*cache_dir*>> is set in the system
+ configuration file then use `<cache_dir>/ccache.conf`.
+4. Otherwise, if there is a legacy `$HOME/.ccache` directory then use
+ `$HOME/.ccache/ccache.conf`.
+5. Otherwise, if `XDG_CONFIG_HOME` is set then use
+ `$XDG_CONFIG_HOME/ccache/ccache.conf`.
+6. Otherwise, use
+ `$HOME/Library/Preferences/ccache/ccache.conf` (macOS) or
+ `$HOME/.config/ccache/ccache.conf` (other systems).
+On Windows, this is the method used to find the configuration file:
-Configuration file syntax
-~~~~~~~~~~~~~~~~~~~~~~~~~
+1. If `CCACHE_CONFIGPATH` is set, use that path.
+2. Otherwise, if the environment variable `CCACHE_DIR` is set then use
+ `%CCACHE_DIR%/ccache.conf`.
+3. Otherwise, if <<config_cache_dir,*cache_dir*>> is set in the system
+ configuration file then use `<cache_dir>\ccache.conf`. The
+ system-wide configuration on Windows is
+ `%ALLUSERSPROFILE%\ccache\ccache.conf` by default. The `ALLUSERSPROFILE`
+ environment variable is usually `C:\ProgramData`.
+4. Otherwise, if there is a legacy `%USERPROFILE%\.ccache` directory then use
+ `%USERPROFILE%\.ccache\ccache.conf`.
+5. Otherwise, use `%LOCALAPPDATA%\ccache\ccache.conf` if it exists.
+6. Otherwise, use `%APPDATA%\ccache\ccache.conf`.
-Configuration files are in a simple ``key = value'' format, one option per
+See also the <<config_cache_dir,*cache_dir*>> configuration option for how the
+cache directory location is determined.
+
+
+=== Configuration file syntax
+
+Configuration files are in a simple "`key = value`" format, one option per
line. Lines starting with a hash sign are comments. Blank lines are ignored, as
is whitespace surrounding keys and values. Example:
max_size = 10G
-------------------------------------------------------------------------------
-Boolean values
-~~~~~~~~~~~~~~
+
+=== Boolean values
Some configuration options are boolean values (i.e. truth values). In a
configuration file, such values must be set to the string *true* or *false*.
For the corresponding environment variables, the semantics are a bit different:
-* A set environment variable means ``true'' (even if set to the empty string).
+* A set environment variable means "`true`" (even if set to the empty string).
* The following case-insensitive negative values are considered an error
(instead of surprising the user): *0*, *false*, *disable* and *no*.
-* An unset environment variable means ``false''.
+* An unset environment variable means "`false`".
Each boolean environment variable also has a negated form starting with
-*CCACHE_NO*. For example, *CCACHE_COMPRESS* can be set to force compression and
-*CCACHE_NOCOMPRESS* can be set to force no compression.
+`CCACHE_NO`. For example, `CCACHE_COMPRESS` can be set to force compression and
+`CCACHE_NOCOMPRESS` can be set to force no compression.
-Configuration options
-~~~~~~~~~~~~~~~~~~~~~~
+=== Configuration options
Below is a list of available configuration options. The corresponding
environment variable name is indicated in parentheses after each configuration
option key.
-[[config_absolute_paths_in_stderr]] *absolute_paths_in_stderr* (*CCACHE_ABSSTDERR*)::
+[#config_absolute_paths_in_stderr]
+*absolute_paths_in_stderr* (*CCACHE_ABSSTDERR*)::
This option specifies whether ccache should rewrite relative paths in the
compiler's standard error output to absolute paths. This can be useful if
working directory, which makes relative paths in compiler errors or
warnings incorrect. The default is false.
-[[config_base_dir]] *base_dir* (*CCACHE_BASEDIR*)::
+[#config_base_dir]
+*base_dir* (*CCACHE_BASEDIR*)::
This option should be an absolute path to a directory. If set, ccache will
- rewrite absolute paths into paths relative to the current working
- directory, but only absolute paths that begin with *base_dir*. Cache
- results can then be shared for compilations in different directories even
- if the project uses absolute paths in the compiler command line. See also
- the discussion under _<<_compiling_in_different_directories,Compiling in
- different directories>>_. If set to the empty string (which is the
- default), no rewriting is done.
+ rewrite absolute paths into paths relative to the current working directory,
+ but only absolute paths that begin with *base_dir*. Cache results can then
+ be shared for compilations in different directories even if the project uses
+ absolute paths in the compiler command line. See also the discussion under
+ _<<Compiling in different directories>>_. If set to the empty string (which
+ is the default), no rewriting is done.
+
A typical path to use as *base_dir* is your home directory or another directory
that is a parent of your project directories. Don't use `/` as the base
relative path to `/usr/include/example` will be different. With *base_dir* set
to `/home/bob/stuff/project1` there will a cache miss since the path to
project2 will be a different absolute path.
++
+WARNING: Rewriting absolute paths to relative is kind of a brittle hack. It
+works OK in many cases, but there might be cases where things break. One known
+issue is that absolute paths are not reproduced in dependency files, which can
+mess up dependency detection in tools like Make and Ninja. If possible, use
+relative paths in the first place instead instead of using *base_dir*.
-[[config_cache_dir]] *cache_dir* (*CCACHE_DIR*)::
+[#config_cache_dir]
+*cache_dir* (*CCACHE_DIR*)::
This option specifies where ccache will keep its cached compiler outputs.
- The default is *$XDG_CACHE_HOME/ccache* if *XDG_CACHE_HOME* is set,
- otherwise *$HOME/.cache/ccache*. Exception: If the legacy directory
- *$HOME/.ccache* exists then that directory is the default.
+
-See also _<<_location_of_the_primary_configuration_file,Location of the primary
-configuration file>>_.
+On non-Windows systems, the default is `$HOME/.ccache` if such a directory
+exists, otherwise `$XDG_CACHE_HOME/ccache` if `XDG_CACHE_HOME` is set, otherwise
+`$HOME/Library/Caches/ccache` (macOS) or `$HOME/.config/ccache` (other systems).
+
-If you want to use another *CCACHE_DIR* value temporarily for one ccache
-invocation you can use the `-d/--directory` command line option instead.
+On Windows, the default is `%USERPROFILE%\.ccache` if such a directory exists,
+otherwise `%LOCALAPPDATA%\ccache`.
++
+WARNING: Previous ccache versions defaulted to storing the cache in
+`%APPDATA%\ccache` on Windows. This can result in large network file transfers
+of the cache in domain environments and similar problems. Please check this
+directory for cache directories and either delete them or the whole directory,
+or move them to the `%LOCALAPPDATA%\ccache` directory.
++
+See also _<<Location of the configuration file>>_.
-[[config_compiler]] *compiler* (*CCACHE_COMPILER* or (deprecated) *CCACHE_CC*)::
+[#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
the empty string (which is the default), ccache works it out from the
command line.
-[[config_compiler_check]] *compiler_check* (*CCACHE_COMPILERCHECK*)::
+[#config_compiler_check]
+*compiler_check* (*CCACHE_COMPILERCHECK*)::
- By default, ccache includes the modification time (``mtime'') and size of
+ By default, ccache includes the modification time ("`mtime`") and size of
the compiler in the hash to ensure that results retrieved from the cache
- are accurate. This option can be used to select another strategy. Possible
- values are:
+ are accurate. If compiler plugins are used, these plugins will also be
+ added to the hash. This option can be used to select another strategy.
+ Possible values are:
+
--
*content*::
path to the compiler. Several commands can be specified with semicolon as
separator. Examples:
+
---
-
----
%compiler% -v
----
-
++
----
%compiler% -dumpmachine; %compiler% -dumpversion
----
-
++
You should make sure that the specified command is as fast as possible since it
will be run once for each ccache invocation.
-
++
Identifying the compiler using a command is useful if you want to avoid cache
misses when the compiler has been rebuilt but not changed.
-
++
Another case is when the compiler (as seen by ccache) actually isn't the real
compiler but another compiler wrapper -- in that case, the default *mtime*
method will hash the mtime and size of the other compiler wrapper, which means
-that ccache won't be able to detect a compiler upgrade. Using a suitable
-command to identify the compiler is thus safer, but it's also slower, so you
-should consider continue using the *mtime* method in combination with the
+that ccache won't be able to detect a compiler upgrade. Using a suitable command
+to identify the compiler is thus safer, but it's also slower, so you should
+consider continue using the *mtime* method in combination with the
*prefix_command* option if possible. See
-_<<_using_ccache_with_other_compiler_wrappers,Using ccache with other compiler
-wrappers>>_.
---
+_<<Using ccache with other compiler wrappers>>_.
--
-[[config_compiler_type]] *compiler_type* (*CCACHE_COMPILERTYPE*)::
+[#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
symlinks). This is the default.
*clang*::
Clang-based compiler.
+*clang-cl*::
+ clang-cl.
*gcc*::
GCC-based compiler.
+*icl*::
+ Intel compiler on Windows.
+*msvc*::
+ Microsoft Visual C++ (MSVC).
*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)::
+[#config_compression]
+*compression* (*CCACHE_COMPRESS* or *CCACHE_NOCOMPRESS*, see _<<Boolean values>>_ above)::
If true, ccache will compress data it puts in the cache. However, this
option has no effect on how files are retrieved from the cache; compressed
<<config_file_clone,*file_clone*>> option) or hard linking (the
<<config_hard_link,*hard_link*>> option) is enabled.
-[[config_compression_level]] *compression_level* (*CCACHE_COMPRESSLEVEL*)::
+[#config_compression_level]
+*compression_level* (*CCACHE_COMPRESSLEVEL*)::
This option determines the level at which ccache will compress object files
using the real-time compression algorithm Zstandard. It only has effect if
essentially the same for all levels. As a rule of thumb, use level 5 or
lower since higher levels may slow down compilations noticeably. Higher
levels are however useful when recompressing the cache with command line
- option *-X/--recompress*.
+ option `-X`/`--recompress`.
*< 0*::
- A negative value corresponds to Zstandard's ``ultra-fast'' compression
+ A negative value corresponds to Zstandard's "`ultra-fast`" compression
levels, which are even faster than level 1 but with less good compression
- ratios. For instance, level *-3* corresponds to ``--fast=3'' for the *zstd*
+ ratios. For instance, level *-3* corresponds to `--fast=3` for the `zstd`
command line tool. In practice, there is little use for levels lower than
*-5* or so.
*0* (default)::
+
See the http://zstd.net[Zstandard documentation] for more information.
-[[config_cpp_extension]] *cpp_extension* (*CCACHE_EXTENSION*)::
+[#config_cpp_extension]
+*cpp_extension* (*CCACHE_EXTENSION*)::
This option can be used to force a certain extension for the intermediate
preprocessed file. The default is to automatically determine the extension
to use for intermediate preprocessor files based on the type of file being
compiled, but that sometimes doesn't work. For example, when using the
- ``aCC'' compiler on HP-UX, set the cpp extension to *i*.
+ "`aCC`" compiler on HP-UX, set the cpp extension to *i*.
-[[config_debug]] *debug* (*CCACHE_DEBUG* or *CCACHE_NODEBUG*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_debug]
+*debug* (*CCACHE_DEBUG* or *CCACHE_NODEBUG*, see _<<Boolean values>>_ above)::
If true, enable the debug mode. The debug mode creates per-object debug
files that are helpful when debugging unexpected cache misses. Note however
- that ccache performance will be reduced slightly. See
- _<<_cache_debugging,Cache debugging>>_ for more information. The default is
- false.
+ that ccache performance will be reduced slightly. See _<<Cache debugging>>_
+ for more information. The default is false.
-[[config_debug_dir]] *debug_dir* (*CCACHE_DEBUGDIR*)::
+[#config_debug_dir]
+*debug_dir* (*CCACHE_DEBUGDIR*)::
- Specifies where to write per-object debug files if the _<<config_debug,debug
- mode>>_ is enabled. If set to the empty string, the files will be written
+ Specifies where to write per-object debug files if the <<config_debug,debug
+ mode>> is enabled. If set to the empty string, the files will be written
next to the object file. If set to a directory, the debug files will be
written with full absolute paths in that directory, creating it if needed.
The default is the empty string.
For example, if *debug_dir* is set to `/example`, the current working directory
is `/home/user` and the object file is `build/output.o` then the debug log will
be written to `/example/home/user/build/output.o.ccache-log`. See also
-_<<_cache_debugging,Cache debugging>>_.
+_<<Cache debugging>>_.
-[[config_depend_mode]] *depend_mode* (*CCACHE_DEPEND* or *CCACHE_NODEPEND*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_depend_mode]
+*depend_mode* (*CCACHE_DEPEND* or *CCACHE_NODEPEND*, see _<<Boolean values>>_ above)::
If true, the depend mode will be used. The default is false. See
- _<<_the_depend_mode,The depend mode>>_.
+ _<<The depend mode>>_.
-[[config_direct_mode]] *direct_mode* (*CCACHE_DIRECT* or *CCACHE_NODIRECT*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_direct_mode]
+*direct_mode* (*CCACHE_DIRECT* or *CCACHE_NODIRECT*, see _<<Boolean values>>_ above)::
If true, the direct mode will be used. The default is true. See
- _<<_the_direct_mode,The direct mode>>_.
+ _<<The direct mode>>_.
-[[config_disable]] *disable* (*CCACHE_DISABLE* or *CCACHE_NODISABLE*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_disable]
+*disable* (*CCACHE_DISABLE* or *CCACHE_NODISABLE*, see _<<Boolean values>>_ above)::
When true, ccache will just call the real compiler, bypassing the cache
completely. The default is false.
-[[config_extra_files_to_hash]] *extra_files_to_hash* (*CCACHE_EXTRAFILES*)::
+[#config_extra_files_to_hash]
+*extra_files_to_hash* (*CCACHE_EXTRAFILES*)::
This option is a list of paths to files that ccache will include in the the
hash sum that identifies the build. The list separator is semicolon on
Windows systems and colon on other systems.
-[[config_file_clone]] *file_clone* (*CCACHE_FILECLONE* or *CCACHE_NOFILECLONE*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_file_clone]
+*file_clone* (*CCACHE_FILECLONE* or *CCACHE_NOFILECLONE*, see _<<Boolean values>>_ above)::
- If true, ccache will attempt to use file cloning (also known as ``copy on
- write'', ``CoW'' or ``reflinks'') to store and fetch cached compiler results.
- *file_clone* has priority over <<config_hard_link,*hard_link*>>. The
- default is false.
+ If true, ccache will attempt to use file cloning (also known as "`copy on
+ write`", "`CoW`" or "`reflinks`") to store and fetch cached compiler
+ results. *file_clone* has priority over <<config_hard_link,*hard_link*>>.
+ The default is false.
+
Files stored by cloning cannot be compressed, so the cache size will likely be
significantly larger if this option is enabled. However, performance may be
systems, ccache will fall back to use plain copying (or hard links if
<<config_hard_link,*hard_link*>> is enabled).
-[[config_hard_link]] *hard_link* (*CCACHE_HARDLINK* or *CCACHE_NOHARDLINK*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_hard_link]
+*hard_link* (*CCACHE_HARDLINK* or *CCACHE_NOHARDLINK*, see _<<Boolean values>>_ above)::
If true, ccache will attempt to use hard links to store and fetch cached
object files. The default is false.
* If the resulting file is modified, the file in the cache will also be
modified since they share content, which corrupts the cache entry. As of
version 4.0, ccache makes stored and fetched object files read-only as a
- safety measure guard. Furthermore, a simple integrity check is made for
- cached object files by verifying that their sizes are correct. This means
- that mistakes like `strip file.o` or `echo >file.o` will be detected even if
- the object file is made writeable, but a modification that doesn't change the
- file size will not.
+ safety measure. Furthermore, a simple integrity check is made for cached
+ object files by verifying that their sizes are correct. This means that
+ mistakes like `strip file.o` or `echo >file.o` will be detected even if the
+ object file is made writeable, but a modification that doesn't change the file
+ size will not.
* Programs that don't expect that files from two different identical
compilations are hard links to each other can fail.
-* Programs that rely on modification times (like ``make'') can be confused if
+* Programs that rely on modification times (like `make`) can be confused if
several users (or one user with several build trees) use the same cache
directory. The reason for this is that the object files share i-nodes and
- therefore modification times. If *file.o* is in build tree A (hard-linked
- from the cache) and *file.o* then is produced by ccache in build tree B by
+ therefore modification times. If `file.o` is in build tree *A* (hard-linked
+ from the cache) and `file.o` then is produced by ccache in build tree *B* by
hard-linking from the cache, the modification timestamp will be updated for
- *file.o* in build tree A as well. This can retrigger relinking in build tree
- A even though nothing really has changed.
+ `file.o` in build tree *A* as well. This can retrigger relinking in build tree
+ *A* even though nothing really has changed.
-[[config_hash_dir]] *hash_dir* (*CCACHE_HASHDIR* or *CCACHE_NOHASHDIR*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_hash_dir]
+*hash_dir* (*CCACHE_HASHDIR* or *CCACHE_NOHASHDIR*, see _<<Boolean values>>_ above)::
If true (which is the default), ccache will include the current working
directory (CWD) in the hash that is used to distinguish two compilations
- when generating debug info (compiler option *-g* with variations).
+ when generating debug info (compiler option `-g` with variations).
Exception: The CWD will not be included in the hash if
<<config_base_dir,*base_dir*>> is set (and matches the CWD) and the
- compiler option *-fdebug-prefix-map* is used. See also the discussion under
- _<<_compiling_in_different_directories,Compiling in different
- directories>>_.
+ compiler option `-fdebug-prefix-map` is used. See also the discussion under
+ _<<Compiling in different directories>>_.
+
The reason for including the CWD in the hash by default is to prevent a problem
with the storage of the current working directory in the debug info of an
code in different directories if you don't mind that CWD in the debug info
might be incorrect.
-[[config_ignore_headers_in_manifest]] *ignore_headers_in_manifest* (*CCACHE_IGNOREHEADERS*)::
+[#config_ignore_headers_in_manifest]
+*ignore_headers_in_manifest* (*CCACHE_IGNOREHEADERS*)::
This option is a list of paths to files (or directories with headers) that
ccache will *not* include in the manifest list that makes up the direct
change. The list separator is semicolon on Windows systems and colon on
other systems.
-[[config_ignore_options]] *ignore_options* (*CCACHE_IGNOREOPTIONS*)::
+[#config_ignore_options]
+*ignore_options* (*CCACHE_IGNOREOPTIONS*)::
This option is a space-delimited list of compiler options that ccache will
exclude from the hash. Excluding a compiler option from the hash can be
useful when you know it doesn't affect the result (but ccache doesn't know
that), or when it does and you don't care. If a compiler option in the list
is suffixed with an asterisk (`*`) it will be matched as a prefix. For
- example, `-fmessage-length=*` will match both `-fmessage-length=20` and
+ example, `+-fmessage-length=*+` will match both `-fmessage-length=20` and
`-fmessage-length=70`.
-[[config_inode_cache]] *inode_cache* (*CCACHE_INODECACHE* or *CCACHE_NOINODECACHE*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_inode_cache]
+*inode_cache* (*CCACHE_INODECACHE* or *CCACHE_NOINODECACHE*, see _<<Boolean values>>_ above)::
- If true, enables caching of source file hashes based on device, inode and
- timestamps. This will reduce the time spent on hashing included files as
- the result can be resused between compilations.
-+
-The feature is still experimental and thus off by default. It is currently not
-available on Windows.
+ If true, ccache will cache source file hashes based on device, inode and
+ timestamps. This reduces the time spent on hashing include files since the
+ result can be resused between compilations. The default is true. The feature
+ requires <<config_temporary_dir,*temporary_dir*>> to be located on a local
+ filesystem of a supported type.
+
-The feature requires *temporary_dir* to be located on a local filesystem.
+NOTE: The inode cache feature is currently not available on Windows.
-[[config_keep_comments_cpp]] *keep_comments_cpp* (*CCACHE_COMMENTS* or *CCACHE_NOCOMMENTS*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_keep_comments_cpp]
+*keep_comments_cpp* (*CCACHE_COMMENTS* or *CCACHE_NOCOMMENTS*, see _<<Boolean values>>_ above)::
If true, ccache will not discard the comments before hashing preprocessor
- output. This can be used to check documentation with *-Wdocumentation*.
+ output. The default is false. This can be used to check documentation with
+ `-Wdocumentation`.
-[[config_limit_multiple]] *limit_multiple* (*CCACHE_LIMIT_MULTIPLE*)::
+[#config_limit_multiple]
+*limit_multiple* (*CCACHE_LIMIT_MULTIPLE*)::
Sets the limit when cleaning up. Files are deleted (in LRU order) until the
levels are below the limit. The default is 0.8 (= 80%). See
- _<<_automatic_cleanup,Automatic cleanup>>_ for more information.
+ _<<Automatic cleanup>>_ for more information.
-[[config_log_file]] *log_file* (*CCACHE_LOGFILE*)::
+[#config_log_file]
+*log_file* (*CCACHE_LOGFILE*)::
If set to a file path, ccache will write information on what it is doing to
the specified file. This is useful for tracking down problems.
& ~
-------------------------------------------------------------------------------
-[[config_max_files]] *max_files* (*CCACHE_MAXFILES*)::
+[#config_max_files]
+*max_files* (*CCACHE_MAXFILES*)::
This option specifies the maximum number of files to keep in the cache. Use
- 0 for no limit (which is the default). See also
- _<<_cache_size_management,Cache size management>>_.
+ 0 for no limit (which is the default). See also _<<Cache size management>>_.
+
+[#config_max_size]
+*max_size* (*CCACHE_MAXSIZE*)::
+
+ This option specifies the maximum size of the cache. Use 0 for no limit. The
+ default value is 5G. Available suffixes: k, M, G, T (decimal) and Ki, Mi,
+ Gi, Ti (binary). The default suffix is G. See also
+ _<<Cache size management>>_.
-[[config_max_size]] *max_size* (*CCACHE_MAXSIZE*)::
+[#config_msvc_dep_prefix]
+*msvc_dep_prefix* (*CCACHE_MSVC_DEP_PREFIX*)::
- This option specifies the maximum size of the cache. Use 0 for no limit.
- The default value is 5G. Available suffixes: k, M, G, T (decimal) and Ki,
- Mi, Gi, Ti (binary). The default suffix is G. See also
- _<<_cache_size_management,Cache size management>>_.
+ This option specifies the prefix of included files output for MSVC compiler.
+ The default prefix is "`Note: including file:`". If you use a localized
+ compiler, this should be set accordingly.
-[[config_path]] *path* (*CCACHE_PATH*)::
+[#config_namespace]
+*namespace* (*CCACHE_NAMESPACE*)::
+
+ If set, the namespace string will be added to the hashed data for each
+ compilation. This will make the associated cache entries logically separate
+ from cache entries with other namespaces, but they will still share the same
+ storage space. Cache entries can also be selectively removed from the local
+ cache with the command line option `--evict-namespace`, potentially in
+ combination with `--evict-older-than`.
++
+For instance, if you use the same local cache for several disparate projects,
+you can use a unique namespace string for each one. This allows you to remove
+cache entries that belong to a certain project if you stop working with that
+project.
+
+[#config_path]
+*path* (*CCACHE_PATH*)::
If set, ccache will search directories in this list when looking for the
real compiler. The list separator is semicolon on Windows systems and colon
on other systems. If not set, ccache will look for the first executable
- matching the compiler name in the normal *PATH* that isn't a symbolic link
+ matching the compiler name in the normal `PATH` that isn't a symbolic link
to ccache itself.
-[[config_pch_external_checksum]] *pch_external_checksum* (*CCACHE_PCH_EXTSUM* or *CCACHE_NOPCH_EXTSUM*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_pch_external_checksum]
+*pch_external_checksum* (*CCACHE_PCH_EXTSUM* or *CCACHE_NOPCH_EXTSUM*, see _<<Boolean values>>_ above)::
When this option is set, and ccache finds a precompiled header file,
- ccache will look for a file with the extension ``.sum'' added
- (e.g. ``pre.h.gch.sum''), and if found, it will hash this file instead
+ ccache will look for a file with the extension "`.sum`" added
+ (e.g. "`pre.h.gch.sum`"), and if found, it will hash this file instead
of the precompiled header itself to work around the performance
penalty of hashing very large files.
-[[config_prefix_command]] *prefix_command* (*CCACHE_PREFIX*)::
+[#config_prefix_command]
+*prefix_command* (*CCACHE_PREFIX*)::
- This option adds a list of prefixes (separated by space) to the command
- line that ccache uses when invoking the compiler. See also
- _<<_using_ccache_with_other_compiler_wrappers,Using ccache with other
- compiler wrappers>>_.
+ This option adds a list of prefixes (separated by space) to the command line
+ that ccache uses when invoking the compiler. See also
+ _<<Using ccache with other compiler wrappers>>_.
-[[config_prefix_command_cpp]] *prefix_command_cpp* (*CCACHE_PREFIX_CPP*)::
+[#config_prefix_command_cpp]
+*prefix_command_cpp* (*CCACHE_PREFIX_CPP*)::
This option adds a list of prefixes (separated by space) to the command
line that ccache uses when invoking the preprocessor.
-[[config_read_only]] *read_only* (*CCACHE_READONLY* or *CCACHE_NOREADONLY*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_read_only]
+*read_only* (*CCACHE_READONLY* or *CCACHE_NOREADONLY*, see _<<Boolean values>>_ above)::
If true, ccache will attempt to use existing cached results, but it will not
- add new results to the cache. Statistics counters will still be updated,
- though, unless the <<config_stats,*stats*>> option is set to *false*.
+ add new results to any cache backend. Statistics counters will still be
+ updated, though, unless the <<config_stats,*stats*>> option is set to
+ *false*.
+
If you are using this because your ccache directory is read-only, you need to
set <<config_temporary_dir,*temporary_dir*>> since ccache will fail to create
temporary files otherwise. You may also want to set <<config_stats,*stats*>> to
*false* make ccache not even try to update stats files.
-[[config_read_only_direct]] *read_only_direct* (*CCACHE_READONLY_DIRECT* or *CCACHE_NOREADONLY_DIRECT*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_read_only_direct]
+*read_only_direct* (*CCACHE_READONLY_DIRECT* or *CCACHE_NOREADONLY_DIRECT*, see _<<Boolean values>>_ above)::
Just like <<config_read_only,*read_only*>> except that ccache will only try
to retrieve results from the cache using the direct mode, not the
preprocessor mode. See documentation for <<config_read_only,*read_only*>>
regarding using a read-only ccache directory.
-[[config_recache]] *recache* (*CCACHE_RECACHE* or *CCACHE_NORECACHE*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_recache]
+*recache* (*CCACHE_RECACHE* or *CCACHE_NORECACHE*, see _<<Boolean values>>_ above)::
If true, ccache will not use any previously stored result. New results will
still be cached, possibly overwriting any pre-existing results.
-[[config_run_second_cpp]] *run_second_cpp* (*CCACHE_CPP2* or *CCACHE_NOCPP2*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_remote_only]
+*remote_only* (*CCACHE_REMOTE_ONLY* or *CCACHE_NOREMOTE_ONLY*, see _<<Boolean values>>_ above)::
+
+ If true, ccache will only use <<config_remote_storage,remote storage>>. The
+ default is false. Note that cache statistics counters will still be kept in
+ the local cache directory unless <<config_stats,*stats*>> is false. See also
+ _<<Storage interaction>>_.
+
+[#config_remote_storage]
+*remote_storage* (*CCACHE_REMOTE_STORAGE*)::
+
+ This option specifies one or several storage backends (separated by space)
+ to query after checking the local cache (unless
+ <<config_remote_only,*remote_only*>> is true). See _<<Remote storage
+ backends>>_ for documentation of syntax and available backends.
++
+Examples:
++
+* `+file:/shared/nfs/directory+`
+* `+file:///shared/nfs/one|read-only file:///shared/nfs/two+`
+* `+http://example.com/cache+`
+* `+redis://example.com+`
++
+NOTE: In previous ccache versions this option was called *secondary_storage*
+(*CCACHE_SECONDARY_STORAGE*), which can still be used as an alias.
+
+[#config_reshare]
+*reshare* (*CCACHE_RESHARE* or *CCACHE_NORESHARE*, see _<<Boolean values>>_ above)::
+
+ If true, ccache will write results to remote storage even for local storage
+ cache hits. The default is false.
+
+[#config_run_second_cpp]
+*run_second_cpp* (*CCACHE_CPP2* or *CCACHE_NOCPP2*, see _<<Boolean values>>_ above)::
If true, ccache will first run the preprocessor to preprocess the source
- code (see _<<_the_preprocessor_mode,The preprocessor mode>>_) and then on a
- cache miss run the compiler on the source code to get hold of the object
- file. This is the default.
+ code (see _<<The preprocessor mode>>_) and then on a cache miss run the
+ compiler on the source code to get hold of the object file. This is the
+ default.
+
If false, ccache will first run preprocessor to preprocess the source code and
then on a cache miss run the compiler on the _preprocessed source code_ instead
when compiling preprocessed source code.
+
A solution to the above mentioned downside is to set *run_second_cpp* to false
-and pass *-fdirectives-only* (for GCC) or *-frewrite-includes* (for Clang) to
+and pass `-fdirectives-only` (for GCC) or `-frewrite-includes` (for Clang) to
the compiler. This will cause the compiler to leave the macros and other
preprocessor information, and only process the *#include* directives. When run
in this way, the preprocessor arguments will be passed to the compiler since it
still has to do _some_ preprocessing (like macros).
++
+This option is ignored with MSVC, as there is no way to make it compile without
+preprocessing first.
-[[config_sloppiness]] *sloppiness* (*CCACHE_SLOPPINESS*)::
+[#config_sloppiness]
+*sloppiness* (*CCACHE_SLOPPINESS*)::
By default, ccache tries to give as few false cache hits as possible.
However, in certain situations it's possible that you know things that
+
--
*clang_index_store*::
- Ignore the Clang compiler option *-index-store-path* and its argument when
+ Ignore the Clang compiler option `-index-store-path` and its argument when
computing the manifest hash. This is useful if you use Xcode, which uses an
index store path derived from the local project path. Note that the index
store won't be updated correctly on cache hits if you enable this
*file_stat_matches_ctime*::
Ignore ctimes when *file_stat_matches* is enabled. This can be useful when
backdating files' mtimes in a controlled way.
+*gcno_cwd*::
+ By default, ccache will include the current working directory in the hash
+ when producing a `.gcno` file (when compiling with `-ftest-coverage` or
+ `--coverage`). This is because GCC 9+ includes the current working directory
+ in the `.gcno` file. The *gcno_cwd* sloppiness makes ccache not hash the
+ current working directory so that you can get cache hits when compiling in
+ different directories, with the tradeoff of potentially getting an incorrect
+ directory in the `.gcno` file. *gcno_cwd* also disables hashing of the
+ current working directory if `-fprofile-abs-path` is used.
*include_file_ctime*::
- By default, ccache will not cache a file if it includes a header whose ctime
- is too new. This sloppiness disables that check. See also
- _<<_handling_of_newly_created_header_files,Handling of newly created header
- files>>_.
+ By default, ccache will disable the direct mode if an include file has too
+ new ctime. This sloppiness disables that check. See also _<<Handling of
+ newly created header files>>_.
*include_file_mtime*::
- By default, ccache will not cache a file if it includes a header whose
- mtime is too new. This sloppiness disables that check. See also
- _<<_handling_of_newly_created_header_files,Handling of newly created header
- files>>_.
+ By default, ccache will disable the direct mode if an include file has too
+ new mtime. This sloppiness disables that check. See also _<<Handling of
+ newly created header files>>_.
*ivfsoverlay*::
- Ignore the Clang compiler option *-ivfsoverlay* and its argument. This is
+ Ignore the Clang compiler option `-ivfsoverlay` and its argument. This is
useful if you use Xcode, which uses a virtual file system (VFS) for things
like combining Objective-C and Swift code.
*locale*::
- Ccache includes the environment variables *LANG*, *LC_ALL*, *LC_CTYPE* and
- *LC_MESSAGES* in the hash by default since they may affect localization of
+ Ccache includes the environment variables `LANG`, `LC_ALL`, `LC_CTYPE` and
+ `LC_MESSAGES` in the hash by default since they may affect localization of
compiler warning messages. Set this sloppiness to tell ccache not to do
that.
-*pch_defines*::
- Be sloppy about **#define**s when precompiling a header file. See
- _<<_precompiled_headers,Precompiled headers>>_ for more information.
*modules*::
- By default, ccache will not cache compilations if *-fmodules* is used since
+ By default, ccache will not cache compilations if `-fmodules` is used since
it cannot hash the state of compiler's internal representation of relevant
modules. This sloppiness allows caching in such a case. See
- _<<_c_modules,C++ modules>>_ for more information.
+ _<<C++ modules>>_ for more information.
+*pch_defines*::
+ Be sloppy about `#define` directives when precompiling a header file. See
+ _<<Precompiled headers>>_ for more information.
+*random_seed*::
+ Ignore the `-frandom-seed` option and its arguments when computing the input
+ hash. This is useful if your build system generates different seeds between
+ builds and you are OK with reusing cached results.
*system_headers*::
By default, ccache will also include all system headers in the manifest.
With this sloppiness set, ccache will only include system headers in the
hash but not add the system header files to the list of include files.
*time_macros*::
- Ignore `__DATE__`, `__TIME__` and `__TIMESTAMP__` being present in the
+ Ignore `+__DATE__+`, `+__TIME__+` and `+__TIMESTAMP__+` being present in the
source code.
--
+
-See the discussion under _<<_troubleshooting,Troubleshooting>>_ for more
-information.
+See the discussion under _<<Troubleshooting>>_ for more information.
-[[config_stats]] *stats* (*CCACHE_STATS* or *CCACHE_NOSTATS*, see _<<_boolean_values,Boolean values>>_ above)::
+[#config_stats]
+*stats* (*CCACHE_STATS* or *CCACHE_NOSTATS*, see _<<Boolean values>>_ above)::
If true, ccache will update the statistics counters on each compilation.
The default is true.
-[[config_temporary_dir]] *temporary_dir* (*CCACHE_TEMPDIR*)::
+[#config_stats_log]
+*stats_log* (*CCACHE_STATSLOG*)::
+
+ If set to a file path, ccache will write statistics counter updates to the
+ specified file. This is useful for getting statistics for individual builds.
+ To show a summary of the current stats log, use `ccache --show-log-stats`.
++
+NOTE: Lines in the stats log starting with a hash sign (`#`) are comments.
+
+[#config_temporary_dir]
+*temporary_dir* (*CCACHE_TEMPDIR*)::
This option specifies where ccache will put temporary files. The default is
- */run/user/<UID>/ccache-tmp* if */run/user/<UID>* exists, otherwise
- *<cache_dir>/tmp*.
+ `$XDG_RUNTIME_DIR/ccache-tmp` (typically `/run/user/<UID>/ccache-tmp`) if
+ `XDG_RUNTIME_DIR` is set and the directory exists, otherwise
+ `<cache_dir>/tmp`.
+
NOTE: In previous versions of ccache, *CCACHE_TEMPDIR* had to be on the same
-filesystem as the *CCACHE_DIR* path, but this requirement has been relaxed.)
+filesystem as the `CCACHE_DIR` path, but this requirement has been relaxed.
+
+[#config_umask]
+*umask* (*CCACHE_UMASK*)::
+
+ This option (an octal integer) specifies the umask for files and directories
+ in the cache directory. This is mostly useful when you wish to share your
+ cache with other users.
+
+
+== Remote storage backends
+
+The <<config_remote_storage,*remote_storage*>> option lets you configure ccache
+to use one or several remote storage backends. By default, the local cache
+directory located in <<config_cache_dir,*cache_dir*>> will be queried first and
+remote storage second, but <<config_remote_only,*remote_only*>> can be set to
+true to disable local storage. Note that cache statistics counters will still be
+kept in the local cache directory -- remote storage backends only store
+compilation results and manifests.
+
+A remote storage backend is specified with a URL, optionally followed by a pipe
+(`|`) and a pipe-separated list of attributes. An attribute is _key_=_value_ or
+just _key_ as a short form of _key_=*true*. Attribute values must be
+https://en.wikipedia.org/wiki/Percent-encoding[percent-encoded] if they contain
+percent, pipe or space characters.
+
+=== Attributes for all backends
+
+These optional attributes are available for all remote storage backends:
+
+* *read-only*: If *true*, only read from this backend, don't write. The default
+ is *false*.
+* *shards*: A comma-separated list of names for sharding (partitioning) the
+ cache entries using
+ https://en.wikipedia.org/wiki/Rendezvous_hashing[Rendezvous hashing],
+ typically to spread the cache over a server cluster. When set, the storage URL
+ must contain an asterisk (`+*+`), which will be replaced by one of the shard
+ names to form a real URL. A shard name can optionally have an appended weight
+ within parentheses to indicate how much of the key space should be associated
+ with that shard. A shard with weight *w* will contain *w*/*S* of the cache,
+ where *S* is the sum of all shard weights. A weight could for instance be set
+ to represent the available memory for a memory cache on a specific server. The
+ default weight is *1*.
++
+Examples:
++
+--
+* `+redis://cache-*.example.com|shards=a(3),b(1),c(1.5)+` will put 55% (3/5.5)
+ of the cache on `+redis://cache-a.example.com+`, 18% (1/5.5) on
+ `+redis://cache-b.example.com+` and 27% (1.5/5.5) on
+ `+redis://cache-c.example.com+`.
+* `+http://example.com/*|shards=alpha,beta+` will put 50% of the cache on
+ `+http://example.com/alpha+` and 50% on `+http://example.com/beta+`.
+--
+
+
+=== Storage interaction
+
+The table below describes the interaction between local and remote storage on
+cache hits and misses if <<config_remote_only,*remote_only*>> is false (which is
+the default):
-[[config_umask]] *umask* (*CCACHE_UMASK*)::
+[options="header",cols="20%,20%,60%"]
+|==============================================================================
+| *Local storage* | *Remote storage* | *What happens*
+
+| miss | miss | Compile, write to local, write to remote^[1]^
+| miss | hit | Read from remote, write to local
+| hit | - | Read from local, don't write to remote^[2]^
- This option specifies the umask for files and directories in the cache
- directory. This is mostly useful when you wish to share your cache with
- other users.
+|==============================================================================
+^[1]^ Unless remote storage has attribute `read-only=true`. +
+^[2]^ Unless local storage is set to share its cache hits with the
+<<config_reshare,*reshare*>> option.
-Cache size management
----------------------
+If <<config_remote_only,*remote_only*>> is true:
+
+[options="header",cols="20%,20%,60%"]
+|==============================================================================
+| *Local storage* | *Remote storage* | *What happens*
+
+| - | miss | Compile, write to remote, don't write to local
+| - | hit | Read from remote, don't write to local
+
+|==============================================================================
+
+=== File storage backend
+
+URL format: `+file:DIRECTORY+` or `+file://[HOST]DIRECTORY+`
+
+This backend stores data as separate files in a directory structure below
+*DIRECTORY*, similar (but not identical) to the local cache storage. A typical
+use case for this backend would be sharing a cache on an NFS directory.
+*DIRECTORY* must start with a slash. *HOST* can be the empty string or
+localhost. On Windows, *HOST* can also be the name of a server hosting a shared
+folder.
+
+IMPORTANT: ccache will not perform any cleanup of the storage -- that has to be
+done by other means, for instance by running `ccache --trim-dir` periodically.
+
+Examples:
+
+* `+file:/shared/nfs/directory+`
+* `+file:///shared/nfs/directory|umask=002|update-mtime=true+`
+* `+file://example.com/shared/folder+`
+
+Optional attributes:
+
+* *layout*: How to store file under the cache directory. Available values:
++
+--
+* *flat*: Store all files directly under the cache directory.
+* *subdirs*: Store files in 256 subdirectories of the cache directory.
+--
++
+The default is *subdirs*.
+* *umask*: This attribute (an octal integer) overrides the umask to use for
+ files and directories in the cache directory.
+* *update-mtime*: If *true*, update the modification time (mtime) of cache
+ entries that are read. The default is *false*.
+
+
+=== HTTP storage backend
+
+URL format: `+http://HOST[:PORT][/PATH]+`
+
+This backend stores data in an HTTP-compatible server. The required HTTP methods
+are `GET`, `PUT` and `DELETE`.
+
+IMPORTANT: ccache will not perform any cleanup of the storage -- that has to be
+done by other means, for instance by running `ccache --trim-dir` periodically.
+
+NOTE: HTTPS is not supported.
+
+TIP: See https://ccache.dev/howto/http-storage.html[How to set up HTTP storage]
+for hints on how to set up an HTTP server for use with ccache.
+
+Examples:
+
+* `+http://localhost+`
+* `+http://someusername:p4ssw0rd@example.com/cache/+`
+* `+http://localhost:8080|layout=bazel|connect-timeout=50+`
+
+Optional attributes:
+
+* *bearer-token*: Bearer token used to authorize the HTTP requests.
+* *connect-timeout*: Timeout (in ms) for network connection. The default is 100.
+* *keep-alive*: If *true*, keep the HTTP connection to the storage server open
+ to avoid reconnects. The default is *true*.
+* *layout*: How to map key names to the path part of the URL. Available values:
++
+--
+* *bazel*: Store values in a format compatible with the Bazel HTTP caching
+ protocol. More specifically, the entries will be stored as 64 hex digits
+ under the `/ac/` part of the cache.
++
+NOTE: You may have to disable verification of action cache values in the server
+for this to work since ccache entries are not valid action result metadata
+values.
+* *flat*: Append the key directly to the path part of the URL (with a leading
+ slash if needed).
+* *subdirs*: Append the first two characters of the key to the URL (with a
+ leading slash if needed), followed by a slash and the rest of the key. This
+ divides the entries into 256 buckets.
+--
++
+The default is *subdirs*.
+* *operation-timeout*: Timeout (in ms) for HTTP requests. The default is 10000.
+
+
+=== Redis storage backend
+
+URL formats:
+
+`+redis://[[USERNAME:]PASSWORD@]HOST[:PORT][/DBNUMBER]+` +
+`+redis+unix:SOCKET_PATH[?db=DBNUMBER]+` +
+`+redis+unix://[[USERNAME:]PASSWORD@localhost]SOCKET_PATH[?db=DBNUMBER]+`
+
+This backend stores data in a https://redis.io[Redis] (or Redis-compatible)
+server. There are implementations for both memory-based and disk-based storage.
+*PORT* defaults to *6379* and *DBNUMBER* defaults to *0*.
+
+NOTE: ccache will not perform any cleanup of the Redis storage, but you can
+https://redis.io/topics/lru-cache[configure LRU eviction].
+
+TIP: See https://ccache.dev/howto/redis-storage.html[How to set up Redis
+storage] for hints on setting up a Redis server for use with ccache.
+
+TIP: You can set up a cluster of Redis servers using the `shards` attribute
+described in _<<Remote storage backends>>_.
+
+Examples:
+
+* `+redis://localhost+`
+* `+redis://p4ssw0rd@cache.example.com:6379/0|connect-timeout=50+`
+* `+redis+unix:/run/redis.sock+`
+* `+redis+unix:///run/redis.sock+`
+* `+redis+unix://p4ssw0rd@localhost/run/redis.sock?db=0+`
+
+Optional attributes:
+
+* *connect-timeout*: Timeout (in ms) for network connection. The default is 100.
+* *operation-timeout*: Timeout (in ms) for Redis commands. The default is 10000.
+
+
+== Cache size management
By default, ccache has a 5 GB limit on the total size of files in the cache and
no limit on the number of files. You can set different limits using the command
-line options *-M*/*--max-size* and *-F*/*--max-files*. Use *ccache
--s/--show-stats* to see the cache size and the currently configured limits (in
-addition to other various statistics).
+line options `-M`/`--max-size` and `-F`/`--max-files`. Use the
+`-s`/`--show-stats` option to see the cache size and the currently configured
+limits (in addition to other various statistics).
Cleanup can be triggered in two different ways: automatic and manual.
-Automatic cleanup
-~~~~~~~~~~~~~~~~~
+=== Automatic cleanup
Ccache maintains counters for various statistics about the cache, including the
size and number of all cached files. In order to improve performance and reduce
limits is that a cleanup is a fairly slow operation, so it would not be a good
idea to trigger it often, like after each cache miss.
+The LRU cleanup makes use of the file modification time (mtime) of cache
+entries; ccache updates mtime of the cache entries read on a cache hit to mark
+them as "recently used".
-Manual cleanup
-~~~~~~~~~~~~~~
-You can run *ccache -c/--cleanup* to force cleanup of the whole cache, i.e. all
+=== Manual cleanup
+
+You can run `ccache -c/--cleanup` to force cleanup of the whole cache, i.e. all
of the sixteen subdirectories. This will recalculate the statistics counters
and make sure that the configuration options *max_size* and
<<config_max_files,*max_files*>> are not exceeded. Note that
cleanup.
-Cache compression
------------------
+== Cache compression
Ccache will by default compress all data it puts into the cache using the
compression algorithm http://zstd.net[Zstandard] (zstd) using compression level
<<config_compression,*compression*>> and
<<config_compression_level,*compression_level*>> for more information.
-You can use the command line option *-x/--show-compression* to print
+You can use the command line option `-x`/`--show-compression` to print
information related to compression. Example:
-------------------------------------------------------------------------------
-Total data: 14.8 GB (16.0 GB disk blocks)
-Compressed data: 11.3 GB (30.6% of original size)
- - Original data: 36.9 GB
- - Compression ratio: 3.267 x (69.4% space savings)
-Incompressible data: 3.5 GB
+Total data: 14.8 GB (16.0 GB disk blocks)
+Compressed data: 11.3 GB (30.6% of original size)
+ Original size: 36.9 GB
+ Compression ratio: 3.267 x (69.4% space savings)
+Incompressible data: 3.5 GB
-------------------------------------------------------------------------------
Notes:
-* The ``disk blocks'' size is the cache size when taking disk block size into
- account. This value should match the ``cache size'' value from ``ccache
- --show-stats''. The other size numbers refer to actual content sizes.
-* ``Compressed data'' refers to result and manifest files stored in the cache.
-* ``Incompressible data'' refers to files that are always stored uncompressed
+* The "`disk blocks`" size is the cache size when taking disk block size into
+ account. This value should match the "`Cache size`" value from "`ccache
+ --show-stats`". The other size numbers refer to actual content sizes.
+* "`Compressed data`" refers to result and manifest files stored in the cache.
+* "`Incompressible data`" refers to files that are always stored uncompressed
(triggered by enabling <<config_file_clone,*file_clone*>> or
<<config_hard_link,*hard_link*>>) or unknown files (for instance files
created by older ccache versions).
<<config_compression_level,*compression_level*>>.
The cache data can also be recompressed to another compression level (or made
-uncompressed) with the command line option *-X/--recompress*. If you choose to
+uncompressed) with the command line option `-X`/`--recompress`. If you choose to
disable compression by default or to use a low compression level, you can
(re)compress newly cached data with a higher compression level after the build
or at another time when there are more CPU cycles available, for instance every
recompressed.
-Cache statistics
-----------------
+== Cache statistics
+
+`ccache --show-stats` shows a summary of statistics, including cache size,
+cleanups (number of performed cleanups, either implicitly due to a cache size
+limit being reached or due to explicit `ccache -c` calls), overall hit rate, hit
+rate for <<The direct mode,direct>>/<<The preprocessor mode,preprocessed>> modes
+and hit rate for local and <<config_remote_storage,remote storage>>.
-*ccache -s/--show-stats* can show the following statistics:
+The summary also includes counters called "`Errors`" and "`Uncacheable`", which
+are sums of more detailed counters. To see those detailed counters, use the
+`-v`/`--verbose` flag. The verbose mode can show the following counters:
[options="header",cols="30%,70%"]
|==============================================================================
-|Name | Description
-| autoconf compile/link |
-Uncachable compilation or linking by an autoconf test.
+| *Counter* | *Description*
+
+| Autoconf compile/link |
+Uncacheable compilation or linking by an Autoconf test.
-| bad compiler arguments |
+| Bad compiler arguments |
Malformed compiler argument, e.g. missing a value for a compiler option that
requires an argument or failure to read a file specified by a compiler option
argument.
-| cache file missing |
-A file was unexpectedly missing from the cache. This only happens in rare
-situations, e.g. if one ccache instance is about to get a file from the cache
-while another instance removed the file as part of cache cleanup.
-
-| cache hit (direct) |
-A result was successfully found using <<_the_direct_mode,the direct mode>>.
-
-| cache hit (preprocessed) |
-A result was successfully found using <<_the_preprocessor_mode,the preprocessor
-mode>>.
-
-| cache miss |
-No result was found.
-
-| cache size |
-Current size of the cache.
-
-| called for link |
+| Called for linking |
The compiler was called for linking, not compiling. Ccache only supports
-compilation of a single file, i.e. calling the compiler with the *-c* option to
+compilation of a single file, i.e. calling the compiler with the `-c` option to
produce a single object file from a single source file.
-| called for preprocessing |
+| Called for preprocessing |
The compiler was called for preprocessing, not compiling.
-| can't use precompiled header |
-Preconditions for using <<_precompiled_headers,precompiled headers>> were not
-fulfilled.
-
-| can't use modules |
-Preconditions for using <<_c_modules,C++ modules>> were not fulfilled.
+| Could not use modules |
+Preconditions for using <<C++ modules>> were not fulfilled.
-| ccache internal error |
-Unexpected failure, e.g. due to problems reading/writing the cache.
+| Could not use precompiled header |
+Preconditions for using <<Precompiled headers,precompiled headers>> were not
+fulfilled.
-| cleanups performed |
-Number of cleanups performed, either implicitly due to the cache size limit
-being reached or due to explicit *ccache -c/--cleanup* calls.
+| Could not write to output file |
+The output path specified with `-o` could not be written to.
-| compile failed |
+| Compilation failed |
The compilation failed. No result stored in the cache.
-| compiler check failed |
+| Compiler check failed |
A compiler check program specified by
<<config_compiler_check,*compiler_check*>> (*CCACHE_COMPILERCHECK*) failed.
-| compiler produced empty output |
-The compiler's output file (typically an object file) was empty after
+| Compiler output file missing |
+One of the files expected to be produced by the compiler was missing after
compilation.
-| compiler produced no output |
-The compiler's output file (typically an object file) was missing after
+| Compiler produced empty output |
+The compiler's output file (typically an object file) was empty after
compilation.
-| compiler produced stdout |
-The compiler wrote data to standard output. This is something that compilers
-normally never do, so ccache is not designed to store such output in the cache.
-
-| couldn't find the compiler |
+| Could not find the compiler |
The compiler to execute could not be found.
-| error hashing extra file |
+| Error hashing extra file |
Failure reading a file specified by
<<config_extra_files_to_hash,*extra_files_to_hash*>> (*CCACHE_EXTRAFILES*).
-| files in cache |
-Current number of files in the cache.
+| Forced recache |
+<<config_recache,*CCACHE_RECACHE*>> was used to overwrite an existing result.
-| multiple source files |
+| Internal error |
+Unexpected failure, e.g. due to problems reading/writing the cache.
+
+| Missing cache file |
+A file was unexpectedly missing from the cache. This only happens in rare
+situations, e.g. if one ccache instance is about to get a file from the cache
+while another instance removed the file as part of cache cleanup.
+
+| Multiple source files |
The compiler was called to compile multiple source files in one go. This is not
supported by ccache.
-| no input file |
+| No input file |
No input file was specified to the compiler.
-| output to a non-regular file |
-The output path specified with *-o* is not a file (e.g. a directory or a device
-node).
+| Output to stdout |
+The compiler was instructed to write its output to standard output using `-o -`.
+This is not supported by ccache.
-| output to stdout |
-The compiler was instructed to write its output to standard output using *-o
--*. This is not supported by ccache.
+| Preprocessing failed |
+Preprocessing the source code using the compiler's `-E` option failed.
-| preprocessor error |
-Preprocessing the source code using the compiler's *-E* option failed.
-
-| stats updated |
-When statistics were updated the last time.
-
-| stats zeroed |
-When *ccache -z* was called the last time.
-
-| unsupported code directive |
-Code like the assembler *.incbin* directive was found. This is not supported
+| Unsupported code directive |
+Code like the assembler `.incbin` directive was found. This is not supported
by ccache.
-| unsupported compiler option |
+| Unsupported compiler option |
A compiler option not supported by ccache was found.
-| unsupported source language |
-A source language e.g. specified with *-x* was unsupported by ccache.
+| Unsupported environment variable |
+An environment variable not supported by ccache was set.
+
+| Unsupported source language |
+A source language e.g. specified with `-x` was unsupported by ccache.
|==============================================================================
-How ccache works
-----------------
+== How ccache works
The basic idea is to detect when you are compiling exactly the same code a
second time and reuse the previously produced output. The detection is done by
The direct mode is generally faster since running the preprocessor has some
overhead.
-If no previous result is detected (i.e., there is a cache miss) using the
-direct mode, ccache will fall back to the preprocessor mode unless the *depend
-mode* is enabled. In the depend mode, ccache never runs the preprocessor, not
-even on cache misses. Read more in _<<_the_depend_mode,The depend mode>>_
-below.
+If no previous result is detected (i.e., there is a cache miss) using the direct
+mode, ccache will fall back to the preprocessor mode unless the *depend mode* is
+enabled. In the depend mode, ccache never runs the preprocessor, not even on
+cache misses. Read more in _<<The depend mode>>_ below.
-Common hashed information
-~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Common hashed information
The following information is always included in the hash:
* the extension used by the compiler for a file with preprocessor output
- (normally *.i* for C code and *.ii* for C++ code)
+ (normally `.i` for C code and `.ii` for C++ code)
* the compiler's size and modification time (or other compiler-specific
information specified by <<config_compiler_check,*compiler_check*>>)
* the name of the compiler
<<config_extra_files_to_hash,*extra_files_to_hash*>> (if any)
-The preprocessor mode
-~~~~~~~~~~~~~~~~~~~~~
+=== The preprocessor mode
In the preprocessor mode, the hash is formed of the common information and:
-* the preprocessor output from running the compiler with *-E*
-* the command line options except those that affect include files (*-I*,
- *-include*, *-D*, etc; the theory is that these command line options will
+* the preprocessor output from running the compiler with `-E`
+* the command line options except those that affect include files (`-I`,
+ `-include`, `-D`, etc; the theory is that these command line options will
change the preprocessor output if they have any effect at all)
* any standard error output generated by the preprocessor
the cache.
-The direct mode
-~~~~~~~~~~~~~~~
+=== The direct mode
In the direct mode, the hash is formed of the common information and:
* the input source file
* the compiler options
-Based on the hash, a data structure called ``manifest'' is looked up in the
+Based on the hash, a data structure called "`manifest`" is looked up in the
cache. The manifest contains:
* references to cached compilation results (object file, dependency file, etc)
* a modification time of one of the include files is too new (needed to avoid a
race condition)
* a compiler option not supported by the direct mode is used:
-** a *-Wp,_X_* compiler option other than *-Wp,-MD,_path_*,
- *-Wp,-MMD,_path_* and *-Wp,-D_define_*
-** *-Xpreprocessor*
-* the string `__TIME__` is present in the source code
+** a `-Wp,++*++` compiler option other than `-Wp,-MD,<path>`, `-Wp,-MMD,<path>`
+ and `-Wp,-D<define>`
+** `-Xpreprocessor`
+* the string `+__TIME__+` is present in the source code
-The depend mode
-~~~~~~~~~~~~~~~
+=== The depend mode
If the depend mode is enabled, ccache will not use the preprocessor at all. The
hash used to identify results in the cache will be based on the direct mode
hash described above plus information about include files read from the
-dependency file generated by the compiler with *-MD* or *-MMD*.
+dependency list generated by MSVC with `/showIncludes`, or the dependency file
+generated by other compilers with `-MD` or `-MMD`.
Advantages:
setup where ccache will fall back to the preprocessor mode, which is tolerant
to some types of changes of compiler options and source code changes.
* If -MD is used, the manifest entries will include system header files as
- well, thus slowing down cache hits slightly, just as using -MD slows down
- make.
-* If -MMD is used, the manifest entries will not include system header files,
+ well, thus slowing down cache hits slightly, just as using `-MD` slows down
+ make. This is also the case for MSVC with `/showIncludes`.
+* If `-MMD` is used, the manifest entries will not include system header files,
which means ccache will ignore changes in them.
The depend mode will be disabled if any of the following holds:
* <<config_depend_mode,*depend_mode*>> is false.
* <<config_run_second_cpp,*run_second_cpp*>> is false.
-* The compiler is not generating dependencies using *-MD* or *-MMD*.
+* The compiler is not generating dependencies using `-MD` or `-MMD` (for MSVC,
+ `/showIncludes` is added automatically if not specified by the user).
-Handling of newly created header files
---------------------------------------
+== Handling of newly created header files
If modification time (mtime) or status change time (ctime) of one of the include
-files is the same second as the time compilation is being done, ccache disables
-the direct mode (or, in the case of a <<_precompiled_headers,precompiled
+files is equal to (or newer than) the time compilation is being done, ccache
+disables the direct mode (or, in the case of a <<Precompiled headers,precompiled
header>>, disables caching completely). This done as a safety measure to avoid a
race condition (see below).
5. The wrong object file is stored in the cache.
-Cache debugging
----------------
+== Cache debugging
To find out what information ccache actually is hashing, you can enable the
debug mode via the configuration option <<config_debug,*debug*>> or by setting
-*CCACHE_DEBUG* in the environment. This can be useful if you are investigating
+`CCACHE_DEBUG` in the environment. This can be useful if you are investigating
why you don't get cache hits. Note that performance will be reduced slightly.
When the debug mode is enabled, ccache will create up to five additional files
[options="header",cols="30%,70%"]
|==============================================================================
-|Filename | Description
-| *<objectfile>.ccache-input-c* |
+| *Filename* | *Description*
+
+| `<objectfile>.<timestamp>.ccache-input-c` |
Binary input hashed by both the direct mode and the preprocessor mode.
-| *<objectfile>.ccache-input-d* |
+| `<objectfile>.<timestamp>.ccache-input-d` |
Binary input only hashed by the direct mode.
-| *<objectfile>.ccache-input-p* |
+| `<objectfile>.<timestamp>.ccache-input-p` |
Binary input only hashed by the preprocessor mode.
-| *<objectfile>.ccache-input-text* |
+| `<objectfile>.<timestamp>.ccache-input-text` |
Human-readable combined diffable text version of the three files above.
-| *<objectfile>.ccache-log* |
+| `<objectfile>.<timestamp>.ccache-log` |
Log for this object file.
|==============================================================================
-If <<config_debug_dir,*config_dir*>> (environment variable *CCACHE_DEBUGDIR*) is
+ The timestamp format is
+`<year><month><day>_<hour><minute><second>_<microsecond>`.
+
+If <<config_debug_dir,*debug_dir*>> (environment variable `CCACHE_DEBUGDIR`) is
set, the files above will be written to that directory with full absolute paths
instead of next to the object file.
In the direct mode, ccache uses the 160 bit BLAKE3 hash of the
-*ccache-input-c* + *ccache-input-d* data (where *+* means concatenation), while
-the *ccache-input-c* + *ccache-input-p* data is used in the preprocessor mode.
+"`ccache-input-c`" + "`ccache-input-d`" data (where *+* means concatenation),
+while the "`ccache-input-c`" + "`ccache-input-p`" data is used in the
+preprocessor mode.
-The *ccache-input-text* file is a combined text version of the three
-binary input files. It has three sections (``COMMON'', ``DIRECT MODE'' and
-``PREPROCESSOR MODE''), which is turn contain annotations that say what kind of
+The "`ccache-input-text`" file is a combined text version of the three binary
+input files. It has three sections ("`COMMON`", "`DIRECT MODE`" and
+"`PREPROCESSOR MODE`"), which is turn contain annotations that say what kind of
data comes next.
To debug why you don't get an expected cache hit for an object file, you can do
something like this:
-1. Build with debug mode enabled.
-2. Save the *<objectfile>.ccache-** files.
-3. Build again with debug mode enabled.
-4. Compare *<objectfile>.ccache-input-text* for the two builds. This together
- with the *<objectfile>.ccache-log* files should give you some clues about
- what is happening.
+1. Enable `debug` (`CCACHE_DEBUG`).
+2. Build.
+3. Clean and build again.
+4. Compare the `<objectfile>.<timestamp>.ccache-input-text` files for the two
+ builds. This together with the `<objectfile>.<timestamp>.ccache-log` files
+ should give you some clues about what is happening.
-Compiling in different directories
-----------------------------------
+== Compiling in different directories
Some information included in the hash that identifies a unique compilation can
contain absolute paths:
* The preprocessed source code may contain absolute paths to include files if
- the compiler option *-g* is used or if absolute paths are given to *-I* and
+ the compiler option `-g` is used or if absolute paths are given to `-I` and
similar compiler options.
-* Paths specified by compiler options (such as *-I*, *-MF*, etc) on the command
+* Paths specified by compiler options (such as `-I`, `-MF`, etc) on the command
line may be absolute.
* The source code file path may be absolute, and that path may substituted for
- `__FILE__` macros in the source code or included in warnings emitted to
+ `+__FILE__+` macros in the source code or included in warnings emitted to
standard error by the preprocessor.
This means that if you compile the same code in different locations, you can't
Here's what can be done to enable cache hits between different build
directories:
-* If you build with *-g* (or similar) to add debug information to the object
+* If you build with `-g` (or similar) to add debug information to the object
file, you must either:
-+
---
-** use the compiler option *-fdebug-prefix-map=_old_=_new_* for relocating
- debug info to a common prefix (e.g. *-fdebug-prefix-map=$PWD=.*); or
+** use the compiler option `-fdebug-prefix-map=<old>=<new>` for relocating
+ debug info to a common prefix (e.g. `-fdebug-prefix-map=$PWD=.`); or
** set *hash_dir = false*.
---
* If you use absolute paths anywhere on the command line (e.g. the source code
- file path or an argument to compiler options like *-I* and *-MF*), you must
- set <<config_base_dir,*base_dir*>> to an absolute path to a ``base
- directory''. Ccache will then rewrite absolute paths under that directory to
+ file path or an argument to compiler options like `-I` and `-MF`), you must
+ set <<config_base_dir,*base_dir*>> to an absolute path to a "`base
+ directory`". Ccache will then rewrite absolute paths under that directory to
relative before computing the hash.
-Precompiled headers
--------------------
+== Precompiled headers
-Ccache has support for GCC's precompiled headers. However, you have to do some
-things to make it work properly:
+Ccache has support for precompiled headers with GCC and Clang. However, you have
+to do some things to make it work properly:
* You must set <<config_sloppiness,*sloppiness*>> to *pch_defines,time_macros*.
- The reason is that ccache can't tell whether `__TIME__`, `__DATE__` or
- `__TIMESTAMP__` is used when using a precompiled header. Further, it can't
- detect changes in **#define**s in the source code because of how
- preprocessing works in combination with precompiled headers.
+ The reason is that ccache can't tell whether `+__TIME__+`, `+__DATE__+` or
+ `+__TIMESTAMP__+` is used when using a precompiled header. Further, it can't
+ detect changes in ``#define``s in the source code because of how preprocessing
+ works in combination with precompiled headers.
* You may also want to include *include_file_mtime,include_file_ctime* in
<<config_sloppiness,*sloppiness*>>. See
- _<<_handling_of_newly_created_header_files,Handling of newly created header
- files>>_.
+ _<<Handling of newly created header files>>_.
* You must either:
+
--
-** use the compiler option *-include* to include the precompiled header (i.e.,
- don't use *#include* in the source code to include the header; the filename
- itself must be sufficient to find the header, i.e. *-I* paths are not
+* use the compiler option `-include` to include the precompiled header (i.e.,
+ don't use `#include` in the source code to include the header; the filename
+ itself must be sufficient to find the header, i.e. `-I` paths are not
searched); or
-** (for the Clang compiler) use the compiler option *-include-pch* to include
+* (for the Clang compiler) use the compiler option `-include-pch` to include
the PCH file generated from the precompiled header; or
-** (for the GCC compiler) add the compiler option *-fpch-preprocess* when
+* (for the GCC compiler) add the compiler option `-fpch-preprocess` when
compiling.
-
-If you don't do this, either the non-precompiled version of the header file
-will be used (if available) or ccache will fall back to running the real
-compiler and increase the statistics counter ``preprocessor error'' (if the
-non-precompiled header file is not available).
--
++
+* If you use Clang, you must compile with `-fno-pch-timestamp`.
+
+If you don't do this, either the non-precompiled version of the header file will
+be used (if available) or ccache will fall back to running the real compiler and
+increase the statistics counter "`Preprocessing failed`" (if the non-precompiled
+header file is not available).
-C++ modules
------------
+== C++ modules
-Ccache has support for Clang's *-fmodules* option. In practice ccache only
-additionally hashes *module.modulemap* files; it does not know how Clang
+Ccache has support for Clang's `-fmodules` option. In practice ccache only
+additionally hashes `module.modulemap` files; it does not know how Clang
handles its cached binary form of modules so those are ignored. This should not
-matter in practice: as long as everything else (including *module.modulemap*
+matter in practice: as long as everything else (including `module.modulemap`
files) is the same the cached result should work. Still, you must set
<<config_sloppiness,*sloppiness*>> to *modules* to allow caching.
-You must use both <<_the_direct_mode,*direct mode*>> and
-<<_the_depend_mode,*depend mode*>>. When using <<_the_preprocessor_mode,the
-preprocessor mode>> Clang does not provide enough information to allow hashing
-of *module.modulemap* files.
+You must use both <<The direct mode,*direct mode*>> and
+<<The depend mode,*depend mode*>>. When using
+<<The preprocessor mode,the preprocessor mode>> Clang does not provide enough
+information to allow hashing of `module.modulemap` files.
-Sharing a cache
----------------
+== Sharing a local cache
-A group of developers can increase the cache hit rate by sharing a cache
-directory. To share a cache without unpleasant side effects, the following
+A group of developers can increase the cache hit rate by sharing a local cache
+directory. To share a local cache without unpleasant side effects, the following
conditions should to be met:
* Use the same cache directory.
* Make sure that the configuration option <<config_hard_link,*hard_link*>> is
false (which is the default).
* Make sure that all users are in the same group.
-* Set the configuration option <<config_umask,*umask*>> to 002. This ensures
+* Set the configuration option <<config_umask,*umask*>> to *002*. This ensures
that cached files are accessible to everyone in the group.
* Make sure that all users have write permission in the entire cache directory
(and that you trust all users of the shared cache).
tells the filesystem to inherit group ownership for new directories. The
following command might be useful for this:
+
---
----
find $CCACHE_DIR -type d | xargs chmod g+s
----
---
The reason to avoid the hard link mode is that the hard links cause unwanted
side effects, as all links to a cached file share the file's modification
discussed in a previous section.
-Sharing a cache on NFS
-----------------------
+== Sharing a cache on NFS
It is possible to put the cache directory on an NFS filesystem (or similar
filesystems), but keep in mind that:
<<config_sloppiness,*sloppiness*>> to *system_headers* to ignore system
headers.
+An alternative to putting the main cache directory on NFS is to set up a
+<<config_remote_storage,remote storage>> file cache.
+
-Using ccache with other compiler wrappers
------------------------------------------
+== Using ccache with other compiler wrappers
The recommended way of combining ccache with another compiler wrapper (such as
-``distcc'') is by letting ccache execute the compiler wrapper. This is
+"`distcc`") is by letting ccache execute the compiler wrapper. This is
accomplished by defining <<config_prefix_command,*prefix_command*>>, for
-example by setting the environment variable *CCACHE_PREFIX* to the name of the
-wrapper (e.g. *distcc*). Ccache will then prefix the command line with the
+example by setting the environment variable `CCACHE_PREFIX` to the name of the
+wrapper (e.g. `distcc`). Ccache will then prefix the command line with the
specified command when running the compiler. To specify several prefix
commands, set <<config_prefix_command,*prefix_command*>> to a colon-separated
list of commands.
Unless you set <<config_compiler_check,*compiler_check*>> to a suitable command
-(see the description of that configuration option), it is not recommended to
-use the form *ccache anotherwrapper compiler args* as the compilation command.
-It's also not recommended to use the masquerading technique for the other
-compiler wrapper. The reason is that by default, ccache will in both cases hash
-the mtime and size of the other wrapper instead of the real compiler, which
-means that:
+(see the description of that configuration option), it is not recommended to use
+the form `ccache anotherwrapper compiler args` as the compilation command. It's
+also not recommended to use the masquerading technique for the other compiler
+wrapper. The reason is that by default, ccache will in both cases hash the mtime
+and size of the other wrapper instead of the real compiler, which means that:
* Compiler upgrades will not be detected properly.
* The cached results will not be shared between compilations with and without
used, ccache will not invoke the other wrapper when running the preprocessor,
which increases performance. You can use
<<config_prefix_command_cpp,*prefix_command_cpp*>> if you also want to invoke
-the other wrapper when doing preprocessing (normally by adding *-E*).
+the other wrapper when doing preprocessing (normally by adding `-E`).
-Caveats
--------
+== Caveats
* The direct mode fails to pick up new header files in some rare scenarios. See
- _<<_the_direct_mode,The direct mode>>_ above.
+ _<<The direct mode>>_ above.
-Troubleshooting
----------------
+== Troubleshooting
-General
-~~~~~~~
+=== General
A general tip for getting information about what ccache is doing is to enable
debug logging by setting the configuration option <<config_debug,*debug*>> (or
-the environment variable *CCACHE_DEBUG*); see _<<_cache_debugging,Cache
-debugging>>_ for more information. Another way of keeping track of what is
+the environment variable *CCACHE_DEBUG*); see _<<Cache debugging>>_
+for more information. Another way of keeping track of what is
happening is to check the output of *ccache -s*.
-Performance
-~~~~~~~~~~~
+=== Performance
Ccache has been written to perform well out of the box, but sometimes you may
have to do some adjustments of how you use the compiler and ccache in order to
storage device if possible. Having lots of free memory so that files in the
cache directory stay in the disk cache is also preferable.
-A good way of monitoring how well ccache works is to run *ccache -s* before and
+A good way of monitoring how well ccache works is to run `ccache -s` before and
after your build and then compare the statistics counters. Here are some common
problems and what may be done to increase the hit rate:
-* If ``cache hit (preprocessed)'' has been incremented instead of ``cache hit
- (direct)'', ccache has fallen back to preprocessor mode, which is generally
- slower. Some possible reasons are:
+* If the counter for preprocessed cache hits has been incremented instead of the
+ one for direct cache hits, ccache has fallen back to preprocessor mode, which
+ is generally slower. Some possible reasons are:
** The source code has been modified in such a way that the preprocessor output
is not affected.
** Compiler arguments that are hashed in the direct mode but not in the
- preprocessor mode have changed (*-I*, *-include*, *-D*, etc) and they didn't
+ preprocessor mode have changed (`-I`, `-include`, `-D`, etc) and they didn't
affect the preprocessor output.
-** The compiler option *-Xpreprocessor* or *-Wp,_X_* (except *-Wp,-MD,_path_*,
- *-Wp,-MMD,_path_*, and *-Wp,-D_define_*) is used.
+** The compiler option `-Xpreprocessor` or `-Wp,++*++` (except `-Wp,-MD,<path>`,
+ `-Wp,-MMD,<path>`, and `-Wp,-D<define>`) is used.
** This was the first compilation with a new value of the
<<config_base_dir,base directory>>.
** A modification or status change time of one of the include files is too new
(created the same second as the compilation is being done). See
- _<<_handling_of_newly_created_header_files,Handling of newly created header
- files>>_.
-** The `__TIME__` preprocessor macro is (potentially) being used. Ccache turns
- off direct mode if `__TIME__` is present in the source code. This is done as
- a safety measure since the string indicates that a `__TIME__` macro _may_
- affect the output. (To be sure, ccache would have to run the preprocessor,
- but the sole point of the direct mode is to avoid that.) If you know that
- `__TIME__` isn't used in practise, or don't care if ccache produces objects
- where `__TIME__` is expanded to something in the past, you can set
- <<config_sloppiness,*sloppiness*>> to *time_macros*.
-** The `__DATE__` preprocessor macro is (potentially) being used and the date
- has changed. This is similar to how `__TIME__` is handled. If `__DATE__` is
- present in the source code, ccache hashes the current date in order to be
- able to produce the correct object file if the `__DATE__` macro affects the
- output. If you know that `__DATE__` isn't used in practise, or don't care if
- ccache produces objects where `__DATE__` is expanded to something in the
+ _<<Handling of newly created header files>>_.
+** The `+__TIME__+` preprocessor macro is (potentially) being used. Ccache turns
+ off direct mode if `+__TIME__+` is present in the source code. This is done
+ as a safety measure since the string indicates that a `+__TIME__+` macro
+ _may_ affect the output. (To be sure, ccache would have to run the
+ preprocessor, but the sole point of the direct mode is to avoid that.) If you
+ know that `+__TIME__+` isn't used in practise, or don't care if ccache
+ produces objects where `+__TIME__+` is expanded to something in the past, you
+ can set <<config_sloppiness,*sloppiness*>> to *time_macros*.
+** The `+__DATE__+` preprocessor macro is (potentially) being used and the date
+ has changed. This is similar to how `+__TIME__+` is handled. If `+__DATE__+`
+ is present in the source code, ccache hashes the current date in order to be
+ able to produce the correct object file if the `+__DATE__+` macro affects the
+ output. If you know that `+__DATE__+` isn't used in practise, or don't care
+ if ccache produces objects where `+__DATE__+` is expanded to something in the
past, you can set <<config_sloppiness,*sloppiness*>> to *time_macros*.
-** The `__TIMESTAMP__` preprocessor macro is (potentially) being used and the
+** The `+__TIMESTAMP__+` preprocessor macro is (potentially) being used and the
source file's modification time has changed. This is similar to how
- `__TIME__` is handled. If `__TIMESTAMP__` is present in the source code,
+ `+__TIME__+` is handled. If `+__TIMESTAMP__+` is present in the source code,
ccache hashes the string representation of the source file's modification
time in order to be able to produce the correct object file if the
- `__TIMESTAMP__` macro affects the output. If you know that `__TIMESTAMP__`
- isn't used in practise, or don't care if ccache produces objects where
- `__TIMESTAMP__` is expanded to something in the past, you can set
- <<config_sloppiness,*sloppiness*>> to *time_macros*.
+ `+__TIMESTAMP__+` macro affects the output. If you know that
+ `+__TIMESTAMP__+` isn't used in practise, or don't care if ccache produces
+ objects where `+__TIMESTAMP__+` is expanded to something in the past, you can
+ set <<config_sloppiness,*sloppiness*>> to *time_macros*.
** The input file path has changed. Ccache includes the input file path in the
direct mode hash to be able to take relative include files into account and
- to produce a correct object file if the source code includes a `__FILE__`
+ to produce a correct object file if the source code includes a `+__FILE__+`
macro.
-* If ``cache miss'' has been incremented even though the same code has been
+* If a cache hit counter was not incremented even though the same code has been
compiled and cached before, ccache has either detected that something has
changed anyway or a cleanup has been performed (either explicitly or
- implicitly when a cache limit has been reached). Some perhaps unobvious
- things that may result in a cache miss are usage of `__TIME__`, `__DATE__` or
- `__TIMESTAMP__` macros, or use of automatically generated code that contains
+ implicitly when a cache limit has been reached). Some perhaps unobvious things
+ that may result in a cache miss are usage of `+__TIME__+`, `+__DATE__+` or
+ `+__TIMESTAMP__+` macros, or use of automatically generated code that contains
a timestamp, build counter or other volatile information.
-* If ``multiple source files'' has been incremented, it's an indication that
- the compiler has been invoked on several source code files at once. Ccache
- doesn't support that. Compile the source code files separately if possible.
-* If ``unsupported compiler option'' has been incremented, enable debug logging
+* If "`Multiple source files`" has been incremented, it's an indication that the
+ compiler has been invoked on several source code files at once. Ccache doesn't
+ support that. Compile the source code files separately if possible.
+* If "`Unsupported compiler option`" has been incremented, enable debug logging
and check which compiler option was rejected.
-* If ``preprocessor error'' has been incremented, one possible reason is that
- precompiled headers are being used. See _<<_precompiled_headers,Precompiled
- headers>>_ for how to remedy this.
-* If ``can't use precompiled header'' has been incremented, see
- _<<_precompiled_headers,Precompiled headers>>_.
-* If ``can't use modules'' has been incremented, see _<<_c_modules,C++
- modules>>_.
+* If "`Preprocessing failed`" has been incremented, one possible reason is that
+ precompiled headers are being used. See _<<Precompiled headers>>_ for how to
+ remedy this.
+* If "`Could not use precompiled header`" has been incremented, see
+ _<<Precompiled headers>>_.
+* If "`Could not use modules`" has been incremented, see _<<C++ modules>>_.
-Corrupt object files
-~~~~~~~~~~~~~~~~~~~~
+=== Corrupt object files
It should be noted that ccache is susceptible to general storage problems. If a
bad object file sneaks into the cache for some reason, it will of course stay
1. Build so that the bad object file ends up in the build tree.
2. Remove the bad object file from the build tree.
-3. Rebuild with *CCACHE_RECACHE* set.
+3. Rebuild with `CCACHE_RECACHE` set.
-An alternative is to clear the whole cache with *ccache -C* if you don't mind
+An alternative is to clear the whole cache with `ccache -C` if you don't mind
losing other cached results.
There are no reported issues about ccache producing broken object files
case, please report it.
-More information
-----------------
+== More information
Credits, mailing list information, bug reporting instructions, source code,
etc, can be found on ccache's web site: <https://ccache.dev>.
-Author
-------
+== Author
Ccache was originally written by Andrew Tridgell and is currently developed and
maintained by Joel Rosdahl. See AUTHORS.txt or AUTHORS.html and
-Ccache news
-===========
+= Ccache news
+
+== Ccache 4.7.4
+
+Release date: 2022-11-21
+
+
+=== Bug fixes
+
+- Fixed an inode cache race condition. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- The default temporary directory is now `$XDG_RUNTIME_DIR/ccache-tmp` instead
+ of a hardcoded `/run/user/<UID>/ccache-tmp`. If `XDG_RUNTIME_DIR` is not set,
+ `<cache_dir>/tmp` is used. This avoids creating `/run/user/<UID>` on systems
+ that don't have it if compiling as root. +
+ [small]#_[contributed by Joel Rosdahl and Oleg Sidorkin]_#
+
+- Added a fallback in case `posix_fallocate` returns `EINVAL` when creating the
+ inode cache file. +
+ [small]#_[contributed by Oleg Sidorkin]_#
+
+- Connection timeout for an HTTP connection is now reported as a timeout instead
+ of an error. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Temporary files found in the cache are no longer counted in
+ `--show-compression`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Removed duplicate magic header in output from `--inspect`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Ccache now properly waits for all recompression jobs to finish when there is
+ no `f` subdirectory in the cache. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Other minor improvements
+
+- Improved inode cache logging. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Documentation improvements
+
+- Removed stray parenthesis. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved description of how header files are handled. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added a hint about using `-fno-pch-timestamp` for precompiled headers with
+ Clang. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Removed obsolete description of compiler type "`pump`". +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+
+== Ccache 4.7.3
+
+Release date: 2022-11-05
+
+
+=== New features and improvements
+
+- Re-added support for handling a `-Wp,-MD` or `-Wp,-MMD` option with `-o` but
+ without `-MMD`, `-MQ` or `-MT` for GCC and Clang. (This combination of options
+ is used by the Linux kernel build system and became unsupported as a
+ side-effect of a feature added in ccache 4.7.) +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Variables that affect Clang version on macOS are now added to the input
+ hash. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Variables that affect the underlying compiler used by ICC (the Intel compiler)
+ are now added to the input hash. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Bug fixes
+
+- Fixed parsing of sloppiness with a trailing delimiter. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Other minor improvements
+
+- Removed a redundant slash in URLs when querying remote HTTP storage. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved logging related to the inode cache. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Documentation improvements
+
+- Added documentation of the default value of *keep_comments_cpp*. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Test improvements
+
+- Silenced various benign test warnings. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.7.2
+
+Release date: 2022-10-29
+
+
+=== Bug fixes
+
+- Fixed a problem when using 32-bit and 64-bit ccache binaries with the same
+ inode cache file. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Ccache now processes the argument following a `-Xarch` option. +
+ [small]#_[contributed by an anonymous user]_#
+
+- Made sure to use the configured umask for command line operations like
+ `--zero-stats` so that newly created cache directories will have correct
+ permissions. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Build improvements
+
+- Include `limits.h` for `PATH_MAX`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Test improvements
+
+- Disabled flaky Windows profiling tests. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.7.1
+
+Release date: 2022-10-22
+
+
+=== Bug fixes
+
+- Fixed a regression in 4.7 related to using the `-MD` or `-MMD` option when
+ compiling an assembler file. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Build improvements
+
+- Added support for compiling ccache with GCC 8. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+- Removed unneeded C++11 flag in libatomic test. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+
+== Ccache 4.7
+
+Release date: 2022-10-17
+
+
+=== Compatibility notes
+
+- The cache entry format has changed, so ccache 4.7 will not share cache entries
+ with earlier versions. Different ccache versions can however still use the
+ same cache storage without any issues.
+
+- The https://ccache.dev/manual/4.7.html#config_cache_dir[default location of
+ the cache directory] on Windows has been changed from `%APPDATA%\ccache` to
+ `%LOCALAPPDATA%\ccache`. Please remove any existing `%APPDATA%\ccache`
+ directory or move it to `%LOCALAPPDATA%\ccache` to keep it.
+
+
+=== Changed tooling
+
+- A C++17 compiler and CMake 3.15 or newer are now required to
+ build ccache.
+
+
+=== New and improved features
+
+- The https://ccache.dev/manual/4.7.html#config_inode_cache[inode cache] is now
+ enabled by default, but only if the filesystem where ccache's temporary
+ directory is located is known to work with the inode cache. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved performance of cache entry reading and writing. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added a https://ccache.dev/manual/4.7.html#config_remote_only[*remote_only*]
+ configuration option, which tells ccache to disable local storage. This way,
+ ccache can use a shared
+ link:pass:[https://ccache.dev/manual/4.7.html#_remote_storage_backends][network cache]
+ without writing cache entries locally, which can be useful if the local
+ storage is ephemeral. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Renamed configuration option *secondary_storage* to
+ https://ccache.dev/manual/4.7.html#config_remote_storage[*remote_storage*].
+ The old spelling will still work as a deprecated alias. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved handling of manifests when using both local and remote storage.
++
+On a cache miss, ccache used to update the local manifest file and write it to
+remote storage, thereby overwriting any preexisting manifest entries on remote
+storage. Similarly, the local manifest could be replaced by the remote manifest
+on a remote cache hit, thus discarding local entries. This has now been improved
+so that ccache will merge local and remote manifest entries transparently. +
+[small]#_[contributed by Joel Rosdahl and an anonymous user]_#
+
+- Added support for caching assembler listing files with compiler option
+ `-Wa,-a=file`. This also fixes a bug in ccache 4.6.2+ where usage of
+ `-Wa,-march=...` makes ccache fall back to running the compiler. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved statistics shown by `ccache --show-stats`. Most values are now shown
+ in relation to a total count, and some less useful statistics counters have
+ been changed to be displayed only with `--verbose`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved statistics for remote hits and misses. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Renamed "`Primary config`" to "`Config file`" and "`Secondary config`" to
+ "`System config file`" in the output of `ccache --show-stats --verbose`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added support for
+ link:pass:[https://ccache.dev/manual/4.7.html#_run_modes][masquerading as a
+ compiler] via a copy or hard link of the ccache executable. Previously, ccache
+ only supported masquerading as a compiler by using a symbolic link. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- A timestamp is now included in
+ link:pass:[https://ccache.dev/manual/4.7.html#_cache_debugging][per-object
+ debug filenames]. This makes it easier to compare two builds without having to
+ save previous debug files before the second build. It also makes sure debug
+ files won't be overwritten if an object file is compiled several times during
+ one build. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added support for Clang's `--` option. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Cache entries are now shared for different `-MT`/`-MQ` options. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- The https://ccache.dev/manual/4.7.html#config_cache_dir[default location of
+ the cache directory] on Windows has been changed to `%LOCALAPPDATA%\ccache`,
+ both in Windows Bash and Windows native environments such as CMD or
+ Powershell.
++
+Previous ccache versions defaulted to storing the cache in `%APPDATA%\ccache` on
+Windows in native environment (i.e., when the `%USER%` variable was not set),
+which could result in large network file transfers of the cache in domain
+environments and similar problems. +
+[small]#_[contributed by Rafael Kitover]_#
+
+- Ccache now uses subsecond resolution timestamps when checking for "`too new
+ include files`". In practice, this means that the direct mode will no longer
+ be disabled for newly generated include files. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Started using subsecond resolution timestamps in manifest files. This improves
+ accuracy with *file_stat_matches*
+ https://ccache.dev/manual/4.7.html#config_sloppiness[sloppiness]. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added support for subsecond timestamps on macOS. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added link:pass:[https://ccache.dev/manual/4.7.html#_the_depend_mode][depend
+ mode] support for MSVC. +
+ [small]#_[contributed by Orgad Shaneh and Luboš Luňák]_#
+
+- Added support for the Intel compiler on Windows. +
+ [small]#_[contributed by Daniel Richtmann]_#
+
+- Ccache now sets `CCACHE_DISABLE` when running the compiler. This avoids
+ running ccache twice (and potentially storing two different results in the
+ cache) if, for instance, the compiler happens to be a wrapper script that in
+ turn runs `ccache $compiler ...`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added support for Redis over Unix sockets. +
+ [small]#_[contributed by Anders F Björklund]_#
+
+- Added support for GCC's `-fprofile-abs-path` option by including the current
+ working directory in the input hash. To opt out of this, set *gcno_cwd*
+ https://ccache.dev/manual/4.7.html#config_sloppiness[sloppiness]. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made it possible to exclude `-frandom-seed=_seed_` options from the input hash
+ with a new *random_seed*
+ https://ccache.dev/manual/4.7.html#config_sloppiness[sloppiness]. +
+ [small]#_[contributed by Raihaan Shouhell]_#
+
+- Added support for server name in
+ link:pass:[https://ccache.dev/manual/4.7.html#_file_storage_backend][remote
+ storage file URLs] on Windows. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Configuration file locations are now included in the
+ link:pass:[https://ccache.dev/manual/4.7.html#_cache_debugging][debug log]. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- The inode cache file is no longer removed with `ccache --clear`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Result format version and number of files are now printed when inspecting a
+ result cache entry file. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Removed features
+
+- Removed the *share-hits* attribute for remote storage. It has been superseded
+ by the https://ccache.dev/manual/4.7.html#config_remote_only[*remote_only*]
+ configuration option. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Buggy support for GCC-specific environment variables `DEPENDENCIES_OUTPUT` and
+ `SUNPRO_DEPENDENCIES` has been removed. If any of those variables are set,
+ ccache now just falls back to running the real compiler. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Bug fixes
+
+- The correct dependency target will now be produced even when a
+ https://ccache.dev/manual/4.7.html#config_base_dir[base directory] is used --
+ the dependency target will still be an absolute path. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed a bug that could lead to bad modification timestamp (mtime) for object
+ files on systems that lack `utimensat` and `utimes` system calls (such as
+ Windows). +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed ordering of MSVC include directory options when using
+ `/external:I<directory>`. +
+ [small]#_[contributed by Raihaan Shouhell]_#
+
+- Fixed capturing of MSVC stdout/stderr when running from Visual Studio. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+- Carriage return characters are now retained in the compiler output on
+ Windows. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+- Made sure not to increment the "`preprocessed_cache_miss`" counter in recache
+ mode. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed an issue with the inode cache in combination with
+ `+__DATE__+`/`+__TIME__+`/`+__TIMESTAMP__+` macros in the source code. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made sure to enable the inode cache only if subsecond timestamps are
+ available. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Cache entries created with enabled hard linking or file cloning are no longer
+ written to remote storage since they won't be possible to retrieve correctly
+ anyway. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Ccache now accepts spaces between target and colon when parsing dependency
+ files in the depend mode. +
+ [small]#_[contributed by Louis Caron]_#
+
+- Fixed a crash when failing to write an error message to stderr after failing
+ to write to the log file. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made ccache able to retrieve an object file from the cache even if the
+ destination object file exists and is read-only. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- The `.exe` extension is now stripped from the ccache executable name in
+ `ccache --version` on Windows. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+- Fixed naming of temporary files written to the cache directory. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Build improvements
+
+- Enabled static runtime linking for MSVC. +
+ [small]#_[contributed by Cristian Adam and Rafael Kitover]_#
+
+- The Zstandard and Hiredis dependencies are now by default downloaded from the
+ Internet when unavailable. +
+ [small]#_[contributed by Rafael Kitover]_#
+
+- Added support for pkgconfig to find a Zstandard installation. +
+ [small]#_[contributed by Rosen Penev]_#
+
+- Removed usage of the deprecated `codecvt` header. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+- Added headers to CMake project files. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+
+=== Test improvements
+
+- Made the integration tests work on Windows. +
+ [small]#_[contributed by Orgad Shaneh and R. Voggenauer]_#
+
+- Improved diagnostics from the "`Version output readable`" test. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+- Made setting the `KEEP_TESTDIR` variable actually work. +
+ [small]#_[contributed by Louis Caron]_#
+
+- Fixed a typo in depend mode tests. +
+ [small]#_[contributed by Louis Caron]_#
+
+- Added more depend mode tests. +
+ [small]#_[contributed by Louis Caron]_#
+
+
+=== Documentation improvements
+
+- Improved the
+ https://github.com/ccache/ccache/blob/v4.7/doc/INSTALL.md[installation
+ guide]. +
+ [small]#_[contributed by Rafael Kitover]_#
+
+- Fixed a typo in the help text. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.6.3
+
+Release date: 2022-08-27
+
+
+=== Bug fixes
+
+- Fixed MSVC support (regression in ccache 4.6.2). +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed detection of PCH header for concatenated `-include` option (e.g.,
+ `-includepch.h` instead of `-include pch.h`). +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Build improvements
+
+- Fixed build with musl when using GCC 12. +
+ [small]#_[contributed by Khem Raj]_#
+
+
+=== Test improvements
+
+- Disabled the "`Failure to write output file`" test when running on a
+ filesystem that doesn't support read-only directories. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.6.2
+
+Release date: 2022-08-22
+
+
+=== Bug fixes
+
+- Fixed a race condition that could lead to a crash if a file in the cache is
+ removed with unlucky timing, e.g. by another ccache process doing cache
+ cleanup. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Dependency file rewriting will now always be performed if
+ `base_dir`/`CCACHE_BASEDIR` is active. This fixes a problem with the
+ dependency file content when Clang is used with `-fsanitize=address`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed handling of error condition for `--hash-file`/`--checksum-file`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made sure to enable the inode cache only if subsecond `stat` timestamps are
+ available. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added a work-around for a Clang bug when writing to a full NFS file system. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made failure writing to the output file increment the "`bad output file`"
+ counter instead of "`cache miss`". +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed false positive cache hits for code constructions similar to
+ `__asm__(".incbin" " \"file\"")`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed false success for `-fcolor-diagnostics` probe with GCC. A side effect of
+ this is that a compiler type that ccache can't identify from the compiler name
+ (such as `/usr/bin/cc` where `cc` is not a symlink) from now on won't produce
+ color diagnostics when used via ccache even if the compiler actually is GCC or
+ Clang. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- More cases of invalid secondary storage URLs are now handled gracefully. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed the display of maximum cache size in `ccache -s` if it's 0 (=
+ unlimited). +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Removed AsciiDoc markup from help text of `--trim-dir`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- The temporary directory is now cleaned up properly even if it's left
+ unconfigured. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made cleanup of the temporary directory not rely a directory timestamp. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made sure to retain mtime/atime when recompressing cache files with
+ `-X`/`--recompress`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- The correct umask is now used when populating the primary cache from a
+ secondary cache. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed creation of temporary files on file systems that don't support hard
+ links (such as FAT32). +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added knowledge about `-Wa,...=file` so that ccache then falls back to running
+ the real compiler. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Corrected handling of space in paths when using response file on Windows. +
+ [small]#_[contributed by Sergey Semushin]_#
+
+- Fixed crash due to empty include filename in preprocessor output generated by
+ `f2c`. +
+ [small]#_[contributed by Oleg Sidorkin]_#
+
+
+=== Build improvements
+
+- Fixed build problems with a development version of GCC 13. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed build problems with MSVC. +
+ [small]#_[contributed by Florin Trofin]_#
+
+
+=== Test improvements
+
+- Clang warnings from the "`-fdebug-prefix-map`" test are now suppressed. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made sure to only run the "`-ftest-coverage + -fprofile-dir`" test with GCC. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed printing of error messages with embedded newlines. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed warning when running the "`inode_cache`" test in isolation. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed test failure when the compiler used for testing is an old ccache version
+ masquerading as the compiler. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Documentation improvements
+
+- Mentioned that mtime is used for LRU cleanup. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.6.1
+
+Release date: 2022-05-15
+
+
+=== New features
+
+- Added support for passing a directory to the MSVC `/Fo` option. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+- Added knowledge about the `-imsvc` compiler option. +
+ [small]#_[contributed by Jacob Young]_#
+
+- Added knowledge about the `-z` linker option. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Bug fixes
+
+- Improved handling of `.gcno` files in combination with absolute input file
+ paths. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Adapted to changes in GCC 9+ `.gcno` files, which contain the current working
+ directory, by including said directory in the hash. You can opt out of this by
+ enabling "`gcno_cwd sloppiness`". +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- A preexisting object file is no longer considered when using
+ `-fsyntax-only`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Authenticate with Redis before database selection. +
+ [small]#_[contributed by an anonymous user]_#
+
+- Don't exit with an error on failure reading a cached file. +
+ [small]#_[contributed by an anonymous user]_#
+
+- Bail out on too hard MSVC environment variables `CL` and `+_CL_+`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Only use `/run/user/<UID>/ccache-tmp` as the temporary directory if it's
+ writable. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed handling of the final newline in cached standard output from the
+ compiler. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+- Fixed a bug related to distcc markers in standard error output. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Paths to `base_dir` are now properly normalized on Windows. +
+ [small]#_[contributed by Vili Väinölä and Joel Rosdahl]_#
+
+- Fixed handling of MSVC `/Fp` and `/Yu` options with concatenated path. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed "`Multiple precompiled headers used`" error if MSVC `/Yu` option is used
+ after `/Fp`. +
+ [small]#_[contributed by Alexey Telishev]_#
+
+- Check for short reads when reading strings in result/manifest files. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+- Log expanded secondary storage URL in put/remove. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed logging of statistics counters with value higher than one in debug log
+ and stats log. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Avoid incorrect error log message for Redis write operations in `reshare`
+ mode. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Support Redis URL without host (meaning localhost). +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Build improvements
+
+- Prefer CMake find module for hiredis and zstd packages. +
+ [small]#_[contributed by Cristian Adam and Joel Rosdahl]_#
+
+- Fixed building and linking BLAKE3 with MSVC. +
+ [small]#_[contributed by Rafael Kitover]_#
+
+- Fixed static linkage with hiredis on Windows. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+- Fixed miscompile of nonstd::expected on MSVC v19.22. +
+ [small]#_[contributed by Jacob Young]_#
+
+- Fixed build arguments to clang-cl. +
+ [small]#_[contributed by Jacob Young]_#
+
+- Fixed parsing of MSVC response files. +
+ [small]#_[contributed by Jacob Young]_#
+
+- Support Git 1.x when determining ccache version. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Test improvements
+
+- Worked around an endianness problem which affected builds and tests on
+ big-endian systems. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- A C++-capable compiler is no longer required for the test suite. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed an issue with inode cache tests, leading to sporadic failures in the
+ inode test suite when running many parallel tests. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed sporadic failures in the profiling test suite. +
+ [small]#_[contributed by Jacob Young]_#
+
+
+=== Documentation improvements
+
+- Added reference to example build configs in installation instructions. +
+ [small]#_[contributed by an anonymous user]_#
+
+- Default cache locations are now mentioned for Windows and macOS as well. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added a warning about usage of `base_dir`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.6
+
+Release date: 2022-02-27
+
+
+=== New features
+
+- Added support for caching calls to Microsoft Visual C++ (MSVC) and clang-cl
+ (MSVC compatibility for Clang). +
+ [small]#_[contributed by Cristian Adam, Luboš Luňák, Orgad Shaneh and Joel
+ Rosdahl]_#
+
+- Added an option to use a bearer token with the HTTP backend. This makes it
+ possible to use e.g. Google Cloud Storage as a secondary storage backend. +
+ [small]#_[contributed by an anonymous user]_#
+
+- Added support for caching standard output from the compiler. +
+ [small]#_[contributed by Luboš Luňák and Joel Rosdahl]_#
+
+- Added a new `--inspect` option for debugging cache entries, replacing the
+ previous `--dump-manifest` and `--dump-result` options. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Enabled HTTP keep-alive by default. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Bug fixes
+
+- Fixed copying of binary files on Windows. +
+ [small]#_[contributed by R. Voggenauer]_#
+
+- Improved detection of the `.incbin` assembler directive to reduce false
+ positives. +
+ [small]#_[contributed by Alexey Sheplyakov]_#
+
+- Ccache now verifies that `/run/user/<UID>/ccache-tmp` is writable before using
+ it for temporary files. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed statistics output for secondary storage. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+- Fixed a problem when copying a cache entry from secondary storage into an
+ empty primary storage. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Visual Studio .rsp files with UTF-16LE encoding are now handled correctly. +
+ [small]#_[contributed by Vili Väinölä]_#
+
+- Made conversion to relative paths more reliable on Windows. +
+ [small]#_[contributed by Marius Zwicker]_#
+
+- The process umask is now respected when making hard linked files read only. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- A forced recache is no longer considered a "`direct cache miss`". +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Documentation improvements
+
+- Corrected reference to the `debug_dir` option. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved documentation of `--config-path`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added documentation that compiler plugins are hashed too. +
+ [small]#_[contributed by Philipp Gortan]_#
+
+
+=== Test improvements
+
+- The "`trim_dir`" test suite is now only run when cleanup tests are enabled. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.5.1
+
+Release date: 2021-11-17
+
+
+=== Bug fixes
+
+- Fixed entry_size field for result entries. This bug affected the recompression
+ feature (`-X`/`--recompress`) in ccache 4.5. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- The actual compression level is now once again stored in the cache entry
+ header. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Corrected error handling for non-constructible secondary storage backends. For
+ instance, this avoids a crash when a Redis server can't be reached. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.5
+
+Release date: 2021-11-13
+
+
+=== New features
+
+- Made various improvements to the cache entry format. Among other things, the
+ header now contains a creation timestamp and a note of the ccache version used
+ to create the entry. The cache entry checksum has also been upgraded to use
+ 128-bit XXH3 instead 64-bit XXH3. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added support for cache namespaces. If a namespace configured, e.g. using
+ `CCACHE_NAMESPACE=some_string`, the namespace string will be added to the
+ hashed data for each compilation. This will make the associated cache entries
+ logically separate from cache entries in other namespaces, but they will still
+ share the same storage space. Cache entries can also be selectively removed
+ from the primary cache with the new command line option `--evict-namespace`,
+ potentially in combination with `--evict-older-than`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made HTTP keep-alive configurable, defaulting to off for now. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+- Added support for rewriting absolute path to Clang option `--gcc-toolchain`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Compatibility notes
+
+- A consequence of the changed cache entry format is that ccache 4.5 will not
+ share cache entries with earlier versions. Different ccache versions can
+ however still use the same cache storage without any issues.
+
+
+=== Bug fixes
+
+- Fixed a problem with special characters in the user info part of URLs for HTTP
+ storage. +
+ [small]#_[contributed by Russell McClellan]_#
+
+- Fixed win32 log messages about file locks. +
+ [small]#_[contributed by Luboš Luňák]_#
+
+- Fixed debug directory handling on Windows. +
+ [small]#_[contributed by Luboš Luňák]_#
+
+- The hard link and file clone features are now disabled when secondary storage
+ is used since they only work for the local primary cache. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.4.2
+
+Release date: 2021-09-28
+
+
+=== Bug fixes
+
+- Fixed a bug introduced in 4.4 where ccache could produce false direct cache
+ hits in some situations if it decides to disable the direct mode temporarily
+ (e.g. due to "`too new header`" file). +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Test improvements
+
+- Use shell builtin pwd command for basedir test. +
+ [small]#_[contributed by Kira Bruneau]_#
+
+- Cope with CC being a wrapper script that uses ccache. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.4.1
+
+Release date: 2021-09-11
+
+
+=== New features
+
+- The secondary storage statistics section of `-s/--show-stats` is now shown
+ only if it's non-empty or with two verbose options. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added display of statistics counters for misses. Previously they were only
+ implicit in the "`hits + misses`" sums. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Bug fixes
+
+- Fixed spurious crashes when using the HTTP or Redis backend and the remote
+ connection hung up. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made sure to always store configuration origin value. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+
+=== Build improvements
+
+- The matching version of lld is now used for Clang. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+- The standard linker is now used if IPO (LTO) is enabled. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+- Disabled IPO (LTO) for MinGW toolchains since they seem to be generally
+ broken. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+- Fixed build errors with Clang on Windows. +
+ [small]#_[contributed by Orgad Shaneh]_#
+
+
+=== Test improvements
+
+- Fixed .incbin test with newer binutil versions. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed basedir test suite failure when using a symlinked CWD. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved output of differing text files on failure. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+== Ccache 4.4
+
+Release date: 2021-08-19
+
+
+=== New features
+
+- Made it possible to share a cache over network or on a local filesystem. The
+ configuration option `secondary_storage`/`CCACHE_SECONDARY_STORAGE` specifies
+ one or several storage backends to query after the primary local cache
+ storage. It is also possible to configure sharding (partitioning) of the cache
+ to spread it over a server cluster using
+ https://en.wikipedia.org/wiki/Rendezvous_hashing[Rendezvous hashing]. See the
+ _https://ccache.dev/manual/4.4.html#_secondary_storage_backends[Secondary
+ storage backends]_ chapter in the manual for details. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added an HTTP backend for secondary storage on any HTTP server that supports
+ GET/PUT/DELETE methods. See https://ccache.dev/howto/http-storage.html[How to
+ set up HTTP storage] for hints on how to set up an HTTP server for use with
+ ccache. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+- Added a Redis backend for secondary storage on any server that supports the
+ Redis protocol. See https://ccache.dev/howto/redis-storage.html[How to set up
+ Redis storage] for hints on how to set up a Redis server for use with
+ ccache. +
+ [small]#_[contributed by Anders F Björklund]_#
+
+- Added a filesystem backend for secondary storage. It can for instance be used
+ for a shared cache over networked filesystems such as NFS, or for mounting a
+ secondary read-only cache layer into a build container. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added `--trim-dir`, `--trim-max-size` and `--trim-method` options that can be
+ used to trim a secondary storage directory to a certain size, e.g. via
+ cron. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added a configuration option `reshare`/`CCACHE_RESHARE` which makes ccache
+ send results to secondary storage even for primary storage cache hits. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added new statistics counters for direct/preprocessed cache misses, primary
+ storage hits/misses, secondary storage hits/misses/errors/timeouts and forced
+ recaches. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved statistics summary. The `-s`/`--show-stats` option now prints a more
+ condensed overview where the counters representing "`uncacheable calls`" are
+ summed as uncacheable and errors counters. The summary shows hit rate for
+ direct/preprocessed hits/misses, as well as primary/secondary storage
+ hits/misses. More details are shown with `-v`/`--verbose`. Note: Scripts
+ should use `--print-stats` (available since ccache 3.7) instead of trying to
+ parse the output of `--show-stats`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added a "`stats log`" feature (configuration option
+ `stats_log`/`CCACHE_STATSLOG`), which tells ccache to store statistics in a
+ separate log file specified by the user. It can for instance be used to
+ collect statistics for a single build without interference from other
+ concurrent builds. Statistics from the log file can then be viewed with
+ `ccache --show-log-stats`. +
+ [small]#_[contributed by Anders F Björklund]_#
+
+- Added support for clang's `--config` option. +
+ [small]#_[contributed by Tom Stellard]_#
+
+- Added support for one `-Xarch_*` option that matches a corresponding `-arch`
+ option. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Renamed the `--directory` option to `--dir` for consistency with other
+ options. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made the `--config-path` and `--dir` options affect the whole command line so
+ that they don't have to be put before `-s`/`--show-stats`. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made `--dump-manifest` and `--dump-result` accept filename `-` for reading
+ from standard input. +
+ [small]#_[contributed by Anders F Björklund]_#
+
+- Made the output of `--print-stats` sorted. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Added more internal trace points. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+
+=== Bug fixes
+
+- Fixed a crash if using `base_dir` and `$PWD` is set to a relative path. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed a bug with `-fprofile-generate` where ccache could give false positive
+ cache hits when compiling with relative paths in another directory. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed a bug in `debug_dir`/`CCACHE_DEBUGDIR`. The absolute path to the object
+ file was not created correctly if the object file didn't already exist. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Disabled preprocessor hits for pre-compiled headers with Clang again. +
+ [small]#_[contributed by Arne Hasselbring]_#
+
+- Fixed a problem when using the Gold linker on MIPS by only probing for a
+ faster linker in dev build mode and on x86_64. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Made the `-DENABLE_TRACING=1` mode work again. +
+ [small]#_[contributed by Anders F Björklund]_#
+
+
+=== Changed tooling
+
+- A C++14 compiler or newer is now required to build ccache. For GCC, this means
+ version 6 or newer in practice.
+
+- CMake 3.10 or newer is now required to build ccache.
+
+- https://asciidoctor.org[Asciidoctor] is now required to build ccache
+ documentation.
+
+
+=== Build/test/documentation improvements
+
+- Fixed an issue in the modules test suite that showed up when running the
+ ccache test suite with the clang wrapper provided by Nixpkgs. +
+ [small]#_[contributed by Ryan Burns]_#
+
+- Made the nvcc_ldir test suite require a working NVCC. +
+ [small]#_[contributed by Michael Kruse]_#
+
+- Made the ivfsoverlay test suite more robust. +
+ [small]#_[contributed by Michael Kruse]_#
+
+- Fixed issue with usage of `/FI` when building ccache with MSVC. +
+ [small]#_[contributed by Michael Kruse]_#
+
+- Fixed Apple Clang detection in the integration test suite. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+- Made clang the default compiler when running the test suite on macOS. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+- Silenced stray printout from "-P -c" test case. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Fixed selection of the ccache binary to use when running the test suite with
+ multi-config generators like Xcode. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+- Fixed CMake feature detection for `ctim`/`mtim` fields in `struct stat`. +
+ [small]#_[contributed by Gregor Jasny]_#
+
+- Fixed issue with not linking to .lib correctly on Windows. +
+ [small]#_[contributed by R. Voggenauer]_#
+
+- Made it possible to override `CCACHE_DEV_MODE` on the command line. +
+ [small]#_[contributed by Joel Rosdahl]_#
+
+- Improved HTML documentation style. +
+ [small]#_[contributed by Joel Rosdahl with minor fixes by Orgad Shaneh]_#
+
+
+== Ccache 4.3
-Ccache 4.3
-----------
Release date: 2021-05-09
-New features
-~~~~~~~~~~~~
+
+=== New features
- Ccache now ignores the Clang compiler option `-ivfsoverlay` and its argument
- if you opt in to ``ivfsoverlay sloppiness''. This is useful if you use Xcode,
+ if you opt in to "`ivfsoverlay sloppiness`". This is useful if you use Xcode,
which uses a virtual file system (VFS) for things like combining Objective-C
and Swift code.
-- When using `-P` in combination with `-E`, ccache now reports this as ``called
- for preprocessing'' instead of ``unsupported compiler option''.
+- When using `-P` in combination with `-E`, ccache now reports this as "`called
+ for preprocessing`" instead of "`unsupported compiler option`".
- Added support for `-specs file.specs` and `--specs file.specs` without an
equal sign between the arguments.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
-- "Split dwarf" code paths are now disabled when outputting to `/dev/null`. This
+- "`Split dwarf`" code paths are now disabled when outputting to `/dev/null`. This
avoids an attempt to delete `/dev/null.dwo`.
- Made the `stat`/`lstat` wrapper function for Windows treat pending deletes as
- Fixed a bug that made ccache process header files redundantly for some
relative headers when using Clang.
-- The object path in now included in the input hash when using `-fprofile-arcs`
+- The object path is now included in the input hash when using `-fprofile-arcs`
(or `--coverage`) since the object file embeds a `.gcda` path based on the
object file path.
-Build improvements
-~~~~~~~~~~~~~~~~~~
+=== Build improvements
- Added an `ENABLE_DOCUMENTATION` build option (default: true) that can be used
to disable the build of documentation.
expands to the empty string.
-Ccache 4.2.1
-------------
+== Ccache 4.2.1
+
Release date: 2021-03-27
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Ccache now only duplicates the stderr file descriptor into `$UNCACHED_ERR_FD`
for calls to the preprocessor/compiler. This works around a complex bug in
- Fixed handling of long command lines on Windows.
-Portability and build improvements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Portability and build improvements
- Build configuration scripts now probe for atomic increment as well. This fixes
a linking error on Sparc.
- Added support for building ccache with xlclang++ on AIX 7.2.
-- Fixed assertion in the "Debug option" test.
+- Fixed assertion in the "`Debug option`" test.
- Made build configuration skip using ccache when building with MSVC.
- Upgraded to doctest 2.4.6. This fixes a build error with glibc >= 2.34.
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
-- Fixed markup of `compiler_type` value "other".
+- Fixed markup of `compiler_type` value `other`.
- Fixed markup of `debug_dir` documentation.
- Fixed references to the `extra_files_to_hash` configuration option.
-Ccache 4.2
-----------
+== Ccache 4.2
+
Release date: 2021-02-02
-New features
-~~~~~~~~~~~~
+
+=== New features
- Improved calculation of relative paths when using `base_dir` to also consider
canonical paths (i.e. paths with dereferenced symlinks) as candidates.
- The value of the `SOURCE_DATE_EPOCH` variable is now only hashed if it
potentially affects the output from ccache. This means that ccache now (like
before version 4.0) will be able to produce cache hits for source code that
- doesn't contain `__DATE__` or `__TIME__` macros regardless of the value of
+ doesn't contain `+__DATE__+` or `+__TIME__+` macros regardless of the value of
`SOURCE_DATE_EPOCH`.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Fixed a bug where a non-Clang compiler would silently accept the
Clang-specific `-f(no-)color-diagnostics` option when run via ccache. This
- Fixed retrieval of the object file the destination is `/dev/null`.
-Portability and build improvements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Portability and build improvements
- Additional compiler flags like `-Wextra -Werror` are now only added when
building ccache in developer mode.
- Took steps towards being able to run the test suite on Windows.
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
- Improved wording of `compiler_check` string values.
- Mention that ccache requires the `-c` compiler option.
-Ccache 4.1
-----------
+== Ccache 4.1
+
Release date: 2020-11-22
-New features
-~~~~~~~~~~~~
+
+=== 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` →
+ 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
`CCACHE_CONFIGPATH` temporarily.
-Bug fixes
-~~~~~~~~~
+=== 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).
+ 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
+- 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
found out at runtime that file cloning is unsupported by the OS.
-Portability and build fixes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== 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 "`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
+- 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
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Other improvements and fixes
- The man page is now built by default if the required tools are available.
- Improved build errors when building ccache with very old compiler versions.
-- Fall back to version “unknown” when Git is not installed.
+- 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
-----------
+== Ccache 4.0
+
Release date: 2020-10-18
-Summary of major changes
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== Summary of major changes
- Changed the default cache directory location to follow the XDG base directory
specification.
- Improved cache directory structure.
-- Added support for using file cloning (AKA “reflinks”).
+- Added support for using file cloning (AKA "`reflinks`").
-- Added an experimental “inode cache” for file hashes.
+- Added an experimental "`inode cache`" for file hashes.
-Compatibility notes
-~~~~~~~~~~~~~~~~~~~
+=== Compatibility notes
- The default location of the cache directory has changed to follow the XDG
- base directory specification (<<_detailed_functional_changes,more details
+ base directory specification (<<Detailed functional changes,more details
below>>). This means that scripts can no longer assume that the cache
directory is `~/.ccache` by default. The `CCACHE_DIR` environment variable
still overrides the default location just like before.
- The cache directory structure has changed compared to previous versions
- (<<_detailed_functional_changes,more details below>>). This means that ccache
+ (<<Detailed functional changes,more details below>>). This means that ccache
4.0 will not share cache results with earlier versions. It is however safe to
run ccache 4.0 and earlier versions against the same cache directory: cache
bookkeeping, statistics and cleanup are backward compatible, with the minor
- exception that some statistics counters incremented by ccache 4.0 won’t be
+ exception that some statistics counters incremented by ccache 4.0 won't be
visible when running `ccache -s` with an older version.
-Changed tooling
-~~~~~~~~~~~~~~~
+=== Changed tooling
- CMake is now used instead of Autoconf for configuration and building.
- Ccache can now be built using Microsoft Visual C++.
-Detailed functional changes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Detailed functional changes
-- All data of a cached result is now stored in a single file called “result”
+- All data of a cached result is now stored in a single file called "`result`"
instead of up to seven files. This reduces inode usage and improves data
locality.
The `cache_dir_levels` (`CCACHE_NLEVELS`) configuration option has therefore
been removed.
-- Added an experimental “inode cache” for file hashes, allowing computed hash
+- Added an experimental "`inode cache`" for file hashes, allowing computed hash
values to be reused both within and between builds. The inode cache is off by
default but can be enabled by setting `inode_cache` (`CCACHE_INODECACHE`) to
`true`.
-- Added support for using file cloning (AKA “reflinks”) on Btrfs, XFS and APFS
+- Added support for using file cloning (AKA "`reflinks`") on Btrfs, XFS and APFS
to copy data to and from the cache very efficiently.
- Two measures have been implemented to make the hard link mode safer:
`-x/--show-compression` and `-X/--recompress`.
- When supported by the CPU, a SIMD-friendly (using AVX2) algorithm is now used
- to scan input source code for `__DATE__`, `__TIME__` and `__TIMESTAMP__`
- macros. This can decrease the number of CPU cycles for a direct cache hit
- with up to 15% in some cases.
+ to scan input source code for `+__DATE__+`, `+__TIME__+` and `+__TIMESTAMP__+`
+ macros. This can decrease the number of CPU cycles for a direct cache hit with
+ up to 15% in some cases.
- Some unnecessary `stat(2)` system calls are now avoided when verifying header
files.
- Added optional logging to syslog if `log_file` (`CCACHE_LOGFILE`) is set to
`syslog`.
-- The compiler option `-fmodules` is now handled in the “depend mode”. If
- “depend mode” is disabled the option is still considered too hard and ccache
+- The compiler option `-fmodules` is now handled in the "`depend mode`". If
+ "`depend mode`" is disabled the option is still considered too hard and ccache
will fall back to running the compiler.
- Ccache can now cache compilations with coverage notes (`.gcno` files)
- Ccache is now able to share cache entries for different object file names
when using `-MD` or `-MMD`.
-- Clang’s `-Xclang` (used by CMake for precompiled headers),
+- Clang's `-Xclang` (used by CMake for precompiled headers),
`-fno-pch-timestamp`, `-emit-pch`, `-emit-pth` and `-include-pth` options are
now understood.
-- Added support for the HIP (“C++ Heterogeneous-Compute Interface for
- Portability”) language.
+- Added support for the HIP ("`C++ Heterogeneous-Compute Interface for
+ Portability`") language.
- The manifest format now allows for header files larger than 4 GiB.
-- Made it possible to once again cache compilations with `__DATE__` in the
+- Made it possible to once again cache compilations with `+__DATE__+` in the
source code.
-- Added handling of the `__TIMESTAMP__` macro.
+- Added handling of the `+__TIMESTAMP__+` macro.
- An absolute input source path is now rewritten to a relative path when using
`base_dir`.
- Made handling of `.dwo` files and interaction between `-gsplit-dwarf` and
other `-g*` options more robust.
-- The “couldn't find compiler” statistics counter is no longer incremented when
- ccache exits with a fatal error.
+- The "`couldn't find compiler`" statistics counter is no longer incremented
+ when ccache exits with a fatal error.
- Failure to run a `compiler_check` command is no longer a fatal error.
older than 3.1 (released 2010).
-Other improvements
-~~~~~~~~~~~~~~~~~~
+=== Other improvements
- Improved help text and documentation of command line options.
- Added HTML anchors to configuration options in the manual so that it is
possible link to a specific option.
-- Tweaked placement of “(readonly)” in output of `ccache -s`.
+- Tweaked placement of "`(readonly)`" in output of `ccache -s`.
- Improved visibility of color output from the test suite.
- Disabled read-only tests on file systems that lack such support.
-ccache 3.7.12
--------------
+== Ccache 3.7.12
Release date: 2020-10-01
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Coverage files (`.gcno`) produced by GCC 9+ when using `-fprofile-dir=dir`
are now handled gracefully by falling back to running the compiler.
32-bit mode.
-Other
-~~~~~
+=== Other
- Improved documentation about sharing a cache on NFS.
- Fixed test case failures with GCC 4.4.
-ccache 3.7.11
--------------
+== Ccache 3.7.11
Release date: 2020-07-21
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Added knowledge about `-fprofile-{correction,reorder-functions,values}`.
- ccache now handles the Intel compiler option `-xCODE` (where `CODE` is a
processor feature code) correctly.
-- Added support for NVCC’s `-Werror` and `--Werror` options.
+- Added support for NVCC's `-Werror` and `--Werror` options.
-Other
-~~~~~
+=== Other
-- ccache’s “Directory is not hashed if using -gz[=zlib]” tests are now skipped
+- ccache's "`Directory is not hashed if using -gz[=zlib]`" tests are now skipped
for GCC 6.
-ccache 3.7.10
-------------
+== Ccache 3.7.10
+
Release date: 2020-06-22
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Improved handling of profiling options. ccache should now work correctly for
profiling options like `-fprofile-{generate,use}[=path]` for GCC ≥ 9 and
- ccache now copies files directly from the cache to the destination file
instead of via a temporary file. This avoids problems when using filenames
- long enough to be near the file system’s filename max limit.
+ long enough to be near the file system's filename max limit.
- When the hard-link mode is enabled, ccache now only uses hard links for
object files, not other files like dependency files. This is because
- compilers unlink object files before writing to them but they don’t do that
+ compilers unlink object files before writing to them but they don't do that
for dependency files, so the latter can become overwritten and therefore
corrupted in the cache.
- Temporary files are now deleted immediately on signals like SIGTERM and
SIGINT instead of some time later in a cleanup phase.
-- Fixed a bug that affected ccache’s `-o/--set-config` option for the
+- Fixed a bug that affected ccache's `-o/--set-config` option for the
`base_dir` and `cache_dir_levels` keys.
-ccache 3.7.9
-------------
+== Ccache 3.7.9
+
Release date: 2020-03-29
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Fixed replacing of /dev/null when building as root with hard link mode
enabled and using `-o /dev/null`.
-- Removed incorrect assertion resulting in “ccache: error: Internal error in
- format” when using `-fdebug-prefix-map=X=` with X equal to `$PWD`.
+- Removed incorrect assertion resulting in "`ccache: error: Internal error in
+ format`" when using `-fdebug-prefix-map=X=` with X equal to `$PWD`.
-Other
-~~~~~
+=== Other
- Improved CUDA/NVCC support: Recognize `-dc` and `-x cu` options.
- Improved name of temporary file used in NFS-safe unlink.
-ccache 3.7.8
-------------
+== Ccache 3.7.8
+
Release date: 2020-03-16
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Use `$PWD` instead of the real CWD (current working directory) when checking
for CWD in preprocessed output. This fixes a problem when `$PWD` includes a
- If `localtime_r` fails the epoch time is now logged instead of garbage.
-Other
-~~~~~
+=== Other
- Improved error message when a boolean environment variable has an invalid
value.
- Improved the regression fix in ccache 3.7.5 related to not passing
compilation-only options to the preprocessor.
-- ccache’s PCH test suite now skips running the tests if it detects broken PCH
+- ccache's PCH test suite now skips running the tests if it detects broken PCH
compiler support.
- Fixed unit test failure on Windows.
-- Fixed “stringop-truncation” build warning on Windows.
+- Fixed "`stringop-truncation`" build warning on Windows.
-- Improved “x_rename” implementation on Windows.
+- Improved "`x_rename`" implementation on Windows.
- Improved removal of temporary file when rewriting absolute paths to relative
in the dependency file.
-- Clarified “include_file_ctime sloppiness” in the Performance section in the
+- Clarified "`include_file_ctime sloppiness`" in the Performance section in the
manual.
-ccache 3.7.7
-------------
+== Ccache 3.7.7
+
Release date: 2020-01-05
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Fixed a bug related to object file location in the dependency file (if using
`-MD` or `-MMD` but not `-MF` and the build directory is not the same as the
compilers again. (A better fix for this is planned for ccache 4.0.)
- Removed the unify mode since it has bugs and shortcomings that are non-trivial
- or impossible to fix: it doesn’t work with the direct mode, it doesn’t handle
+ or impossible to fix: it doesn't work with the direct mode, it doesn't handle
C++ raw strings correctly, it can give false cache hits for `.incbin`
- directives, it’s turned off when using `-g` and it can make line numbers in
+ directives, it's turned off when using `-g` and it can make line numbers in
warning messages and `__LINE__` macros incorrect.
- mtime and ctime values are now stored in the manifest files only when
sloppy_file_stat is set. This avoids adding superfluous manifest file entries
on direct mode cache misses.
-- A “Result:” line is now always printed to the log.
+- A "`Result:`" line is now always printed to the log.
-- The “cache miss” statistics counter will now be updated for read-only cache
+- The "`cache miss`" statistics counter will now be updated for read-only cache
misses, making it consistent with the cache hit case.
-ccache 3.7.6
-------------
+== Ccache 3.7.6
+
Release date: 2019-11-17
-Bug fixes
-~~~~~~~~~
-- The opt-in “file_macro sloppiness” mode has been removed so that the input
+=== Bug fixes
+
+- The opt-in "`file_macro sloppiness`" mode has been removed so that the input
file path now is always included in the direct mode hash. This fixes a bug
- that could result in false cache hits in an edge case when “file_macro
- sloppiness” is enabled and several identical source files include a relative
+ that could result in false cache hits in an edge case when "`file_macro
+ sloppiness`" is enabled and several identical source files include a relative
header file with the same name but in different directories.
- Statistics files are no longer lost when the filesystem of the cache is full.
- Properly handle color diagnostics in the depend mode as well.
-ccache 3.7.5
-------------
+== Ccache 3.7.5
+
Release date: 2019-10-22
-New features
-~~~~~~~~~~~~
+
+=== New features
- Added support for `-MF=arg` (with an extra equal sign) as understood by
EDG-based compilers.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Fixed a regression in 3.7.2 that could result in a warning message instead of
- an error in an edge case related to usage of “-Werror”.
+ an error in an edge case related to usage of "`-Werror`".
- An implicit `-MQ` is now passed to the preprocessor only if the object file
extension is non-standard. This will make it easier to use EDG-based
- compilers (e.g. GHS) which don’t understand `-MQ`. (This is a bug fix of the
+ compilers (e.g. GHS) which don't understand `-MQ`. (This is a bug fix of the
corresponding improvement implemented in ccache 3.4.)
- ccache now falls back to running the real compiler instead of failing fataly
- Fixed warning during configure in out-of-tree build in developer mode.
-ccache 3.7.4
-------------
+== Ccache 3.7.4
+
Release date: 2019-09-12
-Improvements
-~~~~~~~~~~~~
+
+=== Improvements
- Added support for the `-gz[=type]` compiler option (previously ccache would
- think that “-gz” alone would enable debug information, thus potentially
+ think that "`-gz`" alone would enable debug information, thus potentially
including the current directory in the hash).
-- Added support for converting paths like “/c/users/...” into relative paths on
- Windows.
+- Added support for converting paths like "`/c/users/...`" into relative paths
+ on Windows.
-ccache 3.7.3
-------------
+== Ccache 3.7.3
+
Release date: 2019-08-17
-Bug fixes
-~~~~~~~~~
-- The cache size (which is counted in “used disk blocks”) is now correct on
+=== Bug fixes
+
+- The cache size (which is counted in "`used disk blocks`") is now correct on
filesystems that use more or less disk blocks than conventional filesystems,
e.g. ecryptfs or btrfs/zfs with transparent compression. This also fixes a
- related problem with ccache’s own test suite when run on such file systems.
+ related problem with ccache's own test suite when run on such file systems.
-- Fixed a regression in 3.7.2 when using the compiler option “-Werror” and then
- “-Wno-error” later on the command line.
+- Fixed a regression in 3.7.2 when using the compiler option "`-Werror`" and
+ then "`-Wno-error`" later on the command line.
-ccache 3.7.2
-------------
+== Ccache 3.7.2
+
Release date: 2019-07-19
-Bug fixes
-~~~~~~~~~
-- The compiler option `-gdwarf*` no longer forces “run_second_cpp = true”.
+=== Bug fixes
+
+- The compiler option `-gdwarf*` no longer forces "`run_second_cpp = true`".
- Added verification that the value passed to the `-o/--set-config` option is
valid.
- Unknown manifest versions are now handled gracefully in `--dump-manifest`.
-- Fixed `make check` with “funny” locales.
+- Fixed `make check` with "`funny`" locales.
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
- Added a hint about not running `autogen.sh` when building from a release
archive.
- Mention that `xsltproc` is needed when building from the source repository.
-ccache 3.7.1
-------------
+== Ccache 3.7.1
+
Release date: 2019-05-01
-Changes
-~~~~~~~
+
+=== Changes
- Fixed a problem when using the compiler option `-MF /dev/null`.
- Long commandlines are now handled gracefully on Windows by using the `@file`
syntax to avoid hitting the commandline size limit.
-- Fixed complaint from GCC 9’s `-Werror=format-overflow` when compiling ccache
+- Fixed complaint from GCC 9's `-Werror=format-overflow` when compiling ccache
itself.
-ccache 3.7
-----------
+== Ccache 3.7
+
Release date: 2019-04-23
-Changes
-~~~~~~~
+
+=== Changes
- Fixed crash when the debug mode is enabled and the output file is in a
non-writable directory, e.g. when the output file is `/dev/null`.
machine-parsable (tab-separated) format.
- ccache no longer creates a missing output directory, thus mimicking the
- compiler behavior for `-o out/obj.o` when “out” doesn’t exist.
+ compiler behavior for `-o out/obj.o` when `out` doesn't exist.
-- `-fdebug-prefix-map=ARG`, `-ffile-prefix-map=ARG` and
- `-fmacro-prefix-map=ARG` are now included in the hash, but only the part
- before “ARG”. This fixes a bug where compiler feature detection of said flags
- would not work correctly with ccache.
+- `-fdebug-prefix-map=ARG`, `-ffile-prefix-map=ARG` and `-fmacro-prefix-map=ARG`
+ are now included in the hash, but only the part before "`ARG`". This fixes a
+ bug where compiler feature detection of said flags would not work correctly
+ with ccache.
- Bail out on too hard compiler option `-gtoggle`.
involved.
-ccache 3.6
-----------
+== Ccache 3.6
+
Release date: 2019-01-14
-Changes
-~~~~~~~
-- ccache now has an opt-in “depend mode”. When enabled, ccache never executes
+=== Changes
+
+- ccache now has an opt-in "`depend mode`". When enabled, ccache never executes
the preprocessor, which results in much lower cache miss overhead at the
expense of a lower potential cache hit rate. The depend mode is only possible
to use when the compiler option `-MD` or `-MMD` is used.
-- Added support for GCC’s `-ffile-prefix-map` option. The `-fmacro-prefix-map`
+- Added support for GCC's `-ffile-prefix-map` option. The `-fmacro-prefix-map`
option is now also skipped from the hash.
- Added support for multiple `-fsanitize-blacklist` arguments.
- Fixed a problem due to Clang overwriting the output file when compiling an
assembler file.
-- Clarified the manual to explain the reasoning behind the “file_macro”
+- Clarified the manual to explain the reasoning behind the "`file_macro`"
sloppiness setting in a better way.
- ccache now handles several levels of nonexistent directories when rewriting
- A new sloppiness setting `clang_index_store` makes ccache skip the Clang
compiler option `-index-store-path` and its argument when computing the
manifest hash. This is useful if you use Xcode, which uses an index store
- path derived from the local project path. Note that the index store won’t be
+ path derived from the local project path. Note that the index store won't be
updated correctly on cache hits if you enable this option.
- Rename sloppiness `no_system_headers` to `system_headers` for consistency
with other options. `no_system_headers` can still be used as an
(undocumented) alias.
-- The GCC variables “DEPENDENCIES_OUTPUT” and “SUNPRO_DEPENDENCIES” are now
+- The GCC variables "`DEPENDENCIES_OUTPUT`" and "`SUNPRO_DEPENDENCIES`" are now
supported correctly.
-- The algorithm that scans for `__DATE_` and `__TIME__` tokens in the hashed
- source code now doesn’t produce false positives for tokens where `__DATE__`
- or `__TIME__` is a substring.
+- The algorithm that scans for `__DATE_` and `+__TIME__+` tokens in the hashed
+ source code now doesn't produce false positives for tokens where `+__DATE__+`
+ or `+__TIME__+` is a substring.
+
+== Ccache 3.5.1
-ccache 3.5.1
-------------
Release date: 2019-01-02
-Changes
-~~~~~~~
+
+=== Changes
- Added missing getopt_long.c source file to release archive.
- Improved development mode build flags.
-ccache 3.5
-----------
+== Ccache 3.5
+
Release date: 2018-10-15
-Changes
-~~~~~~~
+
+=== Changes
- Added a boolean `debug` (`CCACHE_DEBUG`) configuration option. When enabled,
- ccache will create per-object debug files that are helpful e.g. when
- debugging unexpected cache misses. See also the new “Cache debugging” section
- in the manual.
+ ccache will create per-object debug files that are helpful e.g. when debugging
+ unexpected cache misses. See also the new "`Cache debugging`" section in the
+ manual.
- Renamed `CCACHE_CC` to `CCACHE_COMPILER` (keeping the former as a deprecated
alias).
- Improved performance substantially when using `hash_dir = false` on platforms
like macOS where `getcwd()` is slow.
-- Added “stats updated” timestamp in `ccache -s` output. This can be useful if
+- Added "`stats updated`" timestamp in `ccache -s` output. This can be useful if
you wonder whether ccache actually was used for your last build.
-- Renamed “stats zero time” to “stats zeroed” and documented it. The counter is
- also now only present in `ccache -s` output when `ccache -z` actually has
+- Renamed "`stats zero time`" to "`stats zeroed`" and documented it. The counter
+ is also now only present in `ccache -s` output when `ccache -z` actually has
been called.
- The content of the `-fsanitize-blacklist` file is now included in the hash,
so updates to the file will now correctly result in separate cache entries.
-- It’s now possible to opt out of building and installing man pages when
+- It's now possible to opt out of building and installing man pages when
running `make install` in the source repository.
-- If the compiler type can’t be detected (e.g. if it is named `cc`), use safer
- defaults that won’t trip up Clang.
+- If the compiler type can't be detected (e.g. if it is named `cc`), use safer
+ defaults that won't trip up Clang.
- Made the ccache test suite work on FreeBSD.
- Added `file_stat_matches_ctime` option to disable ctime check if
`file_stat_matches` is enabled.
-- Made “./configure --without-bundled-zlib” do what’s intended.
+- Made "`./configure --without-bundled-zlib`" do what's intended.
+
+== Ccache 3.4.3
-ccache 3.4.3
------------
Release date: 2018-09-02
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Fixed a race condition when creating the initial config file in the cache
directory.
- Upgraded bundled zlib to version 1.2.11.
-ccache 3.4.2
-------------
+== Ccache 3.4.2
+
Release date: 2018-03-25
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- The cleanup algorithm has been fixed to not misbehave when files are removed
by another process while the cleanup process is running. Previously, too many
triggered at the same time, in extreme cases trimming the cache to a much
smaller size than the configured limits.
-- Correctly hash preprocessed headers located in a “.gch directory”.
+- Correctly hash preprocessed headers located in a "`.gch directory`".
Previously, ccache would not pick up changes to such precompiled headers,
risking false positive cache hits.
- Fixed build failure when using the bundled zlib sources.
- ccache 3.3.5 added a workaround for not triggering Clang errors when a
- precompiled header’s dependency has an updated timestamp (but identical
+ precompiled header's dependency has an updated timestamp (but identical
content). That workaround is now only applied when the compiler is Clang.
- Made it possible to perform out-of-source builds in dev mode again.
-ccache 3.4.1
-------------
+== Ccache 3.4.1
+
Release date: 2018-02-11
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Fixed printing of version number in `ccache --version`.
-ccache 3.4
-----------
+== Ccache 3.4
+
Release date: 2018-02-11
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+=== New features and enhancements
- The compiler option form `--sysroot arg` is now handled like the documented
`--sysroot=arg` form.
- Added support for caching `.su` files generated by GCC flag `-fstack-usage`.
-- ccache should now work with distcc’s “pump” wrapper.
+- ccache should now work with distcc's "`pump`" wrapper.
- The optional unifier is no longer disabled when the direct mode is enabled.
- Boolean environment variable settings no longer accept the following
(case-insensitive) values: `0`, `false`, `disable` and `no`. All other values
- are accepted and taken to mean “true”. This is to stop users from setting
+ are accepted and taken to mean "`true`". This is to stop users from setting
e.g. `CCACHE_DISABLE=0` and then expect the cache to be used.
- Improved support for `run_second_cpp = false`: If combined with passing
- An implicit `-MQ` is now passed to the preprocessor only if the object file
extension is non-standard. This should make it easier to use EDG-based
- compilers (e.g. GHS) which don’t understand `-MQ`.
+ compilers (e.g. GHS) which don't understand `-MQ`.
- ccache now treats an unreadable configuration file just like a missing
configuration file.
- Documented caveats related to colored warnings from compilers.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- File size and number counters are now updated correctly when files are
overwritten in the cache, e.g. when using `CCACHE_RECACHE`.
- Fixed how the NVCC options `-optf` and `-odir` are handled.
-ccache 3.3.6
-------------
+== Ccache 3.3.6
+
Release date: 2018-01-28
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Improved instructions on how to get cache hits between different working
directories.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Fixed regression in ccache 3.3.5 related to the `UNCACHED_ERR_FD` feature.
-ccache 3.3.5
-------------
+== Ccache 3.3.5
+
Release date: 2018-01-13
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Documented how automatic cache cleanup works.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Fixed a regression where the original order of debug options could be lost.
- This reverts the “Improved parsing of `-g*` options” feature in ccache 3.3.
+ This reverts the "`Improved parsing of `-g*` options`" feature in ccache 3.3.
- Multiple `-fdebug-prefix-map` options should now be handled correctly.
- `ccache -c/--cleanup` now works like documented: it just recalculates size
counters and trims the cache to not exceed the max size and file number
- limits. Previously, the forced cleanup took “limit_multiple” into account, so
- that `ccache -c/--cleanup` by default would trim the cache to 80% of the max
- limit.
+ limits. Previously, the forced cleanup took "`limit_multiple`" into account,
+ so that `ccache -c/--cleanup` by default would trim the cache to 80% of the
+ max limit.
- ccache no longer ignores linker arguments for Clang since Clang warns about
them.
`-remap` or `-trigraphs` option in preprocessor mode.
-ccache 3.3.4
-------------
+== Ccache 3.3.4
+
Release date: 2017-02-17
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Documented the different cache statistics counters.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Fixed a regression in ccache 3.3 related to potentially bad content of
dependency files when compiling identical source code but with different
source paths. This was only partially fixed in 3.3.2 and reverts the new
- “Names of included files are no longer included in the hash of the compiler’s
- preprocessed output” feature in 3.3.
+ "`Names of included files are no longer included in the hash of the compiler's
+ preprocessed output`" feature in 3.3.
- Corrected statistics counter for `-optf`/`--options-file` failure.
- Fixed undefined behavior warnings in ccache found by `-fsanitize=undefined`.
-ccache 3.3.3
-------------
+== Ccache 3.3.3
+
Release date: 2016-10-26
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- ccache now detects usage of `.incbin` assembler directives in the source code
and avoids caching such compilations.
-ccache 3.3.2
-------------
+== Ccache 3.3.2
+
Release date: 2016-09-28
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Fixed a regression in ccache 3.3 related to potentially bad content of
dependency files when compiling identical source code but with different
resulting in missing dependency files from direct mode cache hits.
-ccache 3.3.1
-------------
+== Ccache 3.3.1
+
Release date: 2016-09-07
-Bug fixes
-~~~~~~~~~
-- Fixed a problem in the “multiple `-arch` options” support introduced in 3.3.
+=== Bug fixes
+
+- Fixed a problem in the "`multiple `-arch` options`" support introduced in 3.3.
When using the direct mode (the default), different combinations of `-arch`
options were not detected properly.
(`CCACHE_CPP2`) is enabled.
-ccache 3.3
-----------
+== Ccache 3.3
+
Release date: 2016-08-27
-Notes
-~~~~~
+=== Notes
- A C99-compatible compiler is now required to build ccache.
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- The configuration option `run_second_cpp` (`CCACHE_CPP2`) now defaults to
- true. This improves ccache’s out-of-the-box experience for compilers that
- can’t compile their own preprocessed output with the same outcome as if they
+ true. This improves ccache's out-of-the-box experience for compilers that
+ can't compile their own preprocessed output with the same outcome as if they
compiled the real source code directly, e.g. newer versions of GCC and Clang.
- The configuration option `hash_dir` (`CCACHE_HASHDIR`) now defaults to true.
- Added a new statistics counter that tracks the number of performed cleanups
due to the cache size being over the limit. The value is shown in the output
- of “ccache -s”.
+ of "`ccache -s`".
- Added support for relocating debug info directory using `-fdebug-prefix-map`.
This allows for cache hits even when `hash_dir` is used in combination with
`base_dir`.
-- Added a new “cache hit rate” field to the output of “ccache -s”.
+- Added a new "`cache hit rate`" field to the output of "`ccache -s`".
-- Added support for caching compilation of assembler code produced by e.g. “gcc
- -S file.c”.
+- Added support for caching compilation of assembler code produced by e.g. "`gcc
+ -S file.c`".
- Added support for cuda including the -optf/--options-file option.
- Added support for Fortran 77.
-- Added support for multiple `-arch` options to produce “fat binaries”.
+- Added support for multiple `-arch` options to produce "`fat binaries`".
- Multiple identical `-arch` arguments are now handled without bailing.
- ccache now understands the undocumented `-coverage` (only one dash) GCC
option.
-- Names of included files are no longer included in the hash of the compiler’s
+- Names of included files are no longer included in the hash of the compiler's
preprocessed output. This leads to more potential cache hits when not using
the direct mode.
slightly.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Bail out on too hard compiler option `-P`.
- Fixed build and test for MinGW32 and Windows.
-ccache 3.2.9
-------------
+== Ccache 3.2.9
+
Release date: 2016-09-28
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Fixed a regression in ccache 3.2.8: ccache could get confused when using the
compiler option `-Wp,` to pass multiple options to the preprocessor,
resulting in missing dependency files from direct mode cache hits.
-ccache 3.2.8
-------------
+== Ccache 3.2.8
+
Release date: 2016-09-07
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Fixed an issue when compiler option `-Wp,-MT,path` is used instead of `-MT
path` (and similar for `-MF`, `-MP` and `-MQ`) and `run_second_cpp`
option.
-ccache 3.2.7
-------------
+== Ccache 3.2.7
+
Release date: 2016-07-20
-Bug fixes
-~~~~~~~~~
+
+=== Bug fixes
- Fixed a bug which could lead to false cache hits for compiler command lines
with a missing argument to an option that takes an argument.
-- ccache now knows how to work around a glitch in the output of GCC 6’s
+- ccache now knows how to work around a glitch in the output of GCC 6's
preprocessor.
-ccache 3.2.6
-------------
+== Ccache 3.2.6
+
Release date: 2016-07-12
-Bug fixes
-~~~~~~~~~
-- Fixed build problem on QNX, which lacks “SA_RESTART”.
+=== Bug fixes
+
+- Fixed build problem on QNX, which lacks "`SA_RESTART`".
- Bail out on compiler option `-fstack-usage` since it creates a `.su` file
- which ccache currently doesn’t handle.
+ which ccache currently doesn't handle.
- Fixed a bug where (due to ccache rewriting paths) the compiler could choose
incorrect include files if `CCACHE_BASEDIR` is used and the source file path
is absolute and is a symlink.
-ccache 3.2.5
-------------
+== Ccache 3.2.5
+
Release date: 2016-04-17
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Only pass Clang-specific `-stdlib=` to the preprocessor.
- Improved handling of stale NFS handles.
-- Made it harder to misinterpret documentation of boolean environment settings’
+- Made it harder to misinterpret documentation of boolean environment settings'
semantics.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Include m4 files used by configure.ac in the source dist archives.
-- Corrected “Performance” section in the manual regarding `__DATE_`, `__TIME__`
- and `__FILE__` macros.
+- Corrected "`Performance`" section in the manual regarding `__DATE_`,
+ `+__TIME__+` and `+__FILE__+` macros.
- Fixed build on Solaris 10+ and AIX 7.
- Fixed failure to create directories on QNX.
-- Don’t (try to) update manifest file in “read-only” and “read-only direct”
+- Don't (try to) update manifest file in "`read-only`" and "`read-only direct`"
modes.
-- Fixed a bug in caching of `stat` system calls in “file_stat_matches
- sloppiness mode”.
+- Fixed a bug in caching of `stat` system calls in "`file_stat_matches
+ sloppiness mode`".
- Fixed bug in hashing of Clang plugins, leading to unnecessary cache misses.
-- Fixed --print-config to show “pch_defines sloppiness”.
+- Fixed --print-config to show "`pch_defines sloppiness`".
-- The man page is now built when running “make install” from Git repository
+- The man page is now built when running "`make install`" from Git repository
sources.
-ccache 3.2.4
-------------
+== Ccache 3.2.4
+
Release date: 2015-10-08
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Fixed build error related to zlib on systems with older make versions
(regression in ccache 3.2.3).
64 GiB on 32-bit systems.
-ccache 3.2.3
-------------
+== Ccache 3.2.3
+
Release date: 2015-08-16
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Added support for compiler option `-gsplit-dwarf`.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Support external zlib in nonstandard directory.
- Bail out on compiler option `--save-temps` in addition to `-save-temps`.
-- Only log “Disabling direct mode” once when failing to read potential include
+- Only log "`Disabling direct mode`" once when failing to read potential include
files.
-ccache 3.2.2
-------------
+== Ccache 3.2.2
+
Release date: 2015-05-10
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Added support for `CCACHE_COMPILERCHECK=string:<value>`. This is a faster
- alternative to `CCACHE_COMPILERCHECK=<command>` if the command’s output can
+ alternative to `CCACHE_COMPILERCHECK=<command>` if the command's output can
be precalculated by the build system.
- Add support for caching code coverage results (compiling for gcov).
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Made hash of cached result created with and without `CCACHE_CPP2` different.
This makes it possible to rebuild with `CCACHE_CPP2` set without having to
clear the cache to get new results.
-- Don’t try to reset a nonexistent stats file. This avoids “No such file or
- directory” messages in the ccache log when the cache directory doesn’t exist.
+- Don't try to reset a nonexistent stats file. This avoids "`No such file or
+ directory`" messages in the ccache log when the cache directory doesn't exist.
- Fixed a bug where ccache deleted Clang diagnostics after compiler failures.
- Fixed build error when compiling ccache with recent Clang versions.
-ccache 3.2.1
-------------
+== Ccache 3.2.1
+
Release date: 2014-12-10
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Fixed regression in temporary file handling, which lead to incorrect
permissions for stats, manifest and ccache.conf files in the cache.
options.
-ccache 3.2
-----------
+== Ccache 3.2
+
Release date: 2014-11-17
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Added support for configuring ccache via one or several configuration files
instead of via environment variables. Environment variables still have
- Made creation of temporary directories and cache directories smarter to avoid
unnecessary `stat` calls.
-- Improved efficiency of the algorithm that scans for `__DATE_` and `__TIME__`
+- Improved efficiency of the algorithm that scans for `__DATE_` and `+__TIME__+`
tokens in the hashed source code.
- Added support for several binaries (separated by space) in `CCACHE_PREFIX`.
- The `-c` option is no longer passed to the preprocessor. This fixes problems
- with Clang and Solaris’s C++ compiler.
+ with Clang and Solaris's C++ compiler.
- ccache no longer passes preprocessor options like `-D` and `-I` to the
compiler when compiling preprocessed output. This fixes warnings emitted by
- Compiler options `-fprofile-generate`, `-fprofile-arcs`, `-fprofile-use` and
`-fbranch-probabilities` are now handled without bailing.
-- Added support for Clang’s `--serialize-diagnostic` option, storing the
+- Added support for Clang's `--serialize-diagnostic` option, storing the
diagnostic file (`.dia`) in the cache.
- Added support for precompiled headers when using Clang.
the other way around. This is needed to support compiler options like
`-fprofile-arcs` and `--serialize-diagnostics`.
-- ccache now checks that included files’ ctimes aren’t too new. This check can
- be turned off by adding `include_file_ctime` to the “ccache sloppiness”
+- ccache now checks that included files' ctimes aren't too new. This check can
+ be turned off by adding `include_file_ctime` to the "`ccache sloppiness`"
setting.
- Added possibility to get cache hits based on filename, size, mtime and ctime
only. On other words, source code files are not even read, only stat-ed. This
- operation mode is opt-in by adding `file_stat_matches` to the “ccache
- sloppiness” setting.
+ operation mode is opt-in by adding `file_stat_matches` to the "`ccache
+ sloppiness`" setting.
- The filename part of options like `-Wp,-MDfilename` is no longer included in
- the hash since the filename doesn’t have any bearing on the result.
+ the hash since the filename doesn't have any bearing on the result.
-- Added a “read-only direct” configuration setting, which is like the ordinary
+- Added a "`read-only direct`" configuration setting, which is like the ordinary
read-only setting except that ccache will only try to retrieve results from
the cache using the direct mode, not the preprocessor mode.
- Added support for `@file` and `-@file` arguments (reading options from a
file).
-- `-Wl,` options are no longer included in the hash since they don’t affect
+- `-Wl,` options are no longer included in the hash since they don't affect
compilation.
- Bail out on too hard compiler option `-Wp,-P`.
- Various other improvements of the test suite.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Any previous `.stderr` is now removed from the cache when recaching.
- Fixed test suite failures when `CC` is a ccache-wrapped compiler.
-ccache 3.1.12
--------------
+== Ccache 3.1.12
+
Release date: 2016-07-12
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Fixed a bug where (due to ccache rewriting paths) the compiler could choose
incorrect include files if `CCACHE_BASEDIR` is used and the source file path
is absolute and is a symlink.
-ccache 3.1.11
--------------
+== Ccache 3.1.11
+
Release date: 2015-03-07
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Fixed bug which could result in false cache hits when source code contains
`'"'` followed by `" /*"` or `" //"` (with variations).
This makes it possible to rebuild with `CCACHE_CPP2` set without having to
clear the cache to get new results.
-- Don’t try to reset a nonexistent stats file. This avoids “No such file or
- directory” messages in the ccache log when the cache directory doesn’t exist.
+- Don't try to reset a nonexistent stats file. This avoids "`No such file or
+ directory`" messages in the ccache log when the cache directory doesn't exist.
-ccache 3.1.10
--------------
+== Ccache 3.1.10
+
Release date: 2014-10-19
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Added support for the `-Xclang` compiler option.
`CCACHE_BASEDIR` to reuse results across different directories.)
- Added note in documentation that `--ccache-skip` currently does not mean
- “don’t hash the following option”.
+ "`don't hash the following option`".
- To enable support for precompiled headers (PCH), `CCACHE_SLOPPINESS` now also
needs to include the new `pch_defines` sloppiness. This is because ccache
- can’t detect changes in the source code when only defined macros have been
+ can't detect changes in the source code when only defined macros have been
changed.
- Stale files in the internal temporary directory (`<ccache_dir>/tmp`) are now
cleaned up if they are older than one hour.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
-- Fixed path canonicalization in `make_relative_path()` when path doesn’t
+- Fixed path canonicalization in `make_relative_path()` when path doesn't
exist.
- Fixed bug in `common_dir_prefix_length()`. This corrects the `CCACHE_BASEDIR`
- Fixed problem with logging of current working directory.
-ccache 3.1.9
-------------
+== Ccache 3.1.9
+
Release date: 2013-01-06
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- The EAGAIN signal is now handled correctly when emitting cached stderr
output. This fixes a problem triggered by large error outputs from the
- Subdirectories in the cache are no longer created in read-only mode.
-- Fixed so that ccache’s log file descriptor is not made available to the
+- Fixed so that ccache's log file descriptor is not made available to the
compiler.
- Improved error reporting when failing to create temporary stdout/stderr files
- Disappearing temporary stdout/stderr files are now handled gracefully.
-Other
-~~~~~
+=== Other
- Fixed test suite to work on ecryptfs.
-ccache 3.1.8
-------------
+== Ccache 3.1.8
+
Release date: 2012-08-11
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Made paths to dependency files relative in order to increase cache hits.
- Clang plugins are now hashed to catch plugin upgrades.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Fixed crash when the current working directory has been removed.
base directory.
-Other
-~~~~~
+=== Other
- Made git version macro work when compiling outside of the source directory.
- Fixed `static_assert` macro definition clash with GCC 4.7.
-ccache 3.1.7
-------------
+== Ccache 3.1.7
+
Release date: 2012-01-08
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Non-writable `CCACHE_DIR` is now handled gracefully when `CCACHE_READONLY` is
set.
- Made failure to create files (typically due to bad directory permissions) in
the cache directory fatal. Previously, such failures were silently and
- erroneously flagged as “compiler produced stdout”.
+ erroneously flagged as "`compiler produced stdout`".
- Both the `-specs=file` and `--specs=file` forms are now recognized.
versions.)
-Other
-~~~~~
+=== Other
- Corrected license header for `mdfour.c`.
-ccache 3.1.6
-------------
+== Ccache 3.1.6
+
Release date: 2011-08-21
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Rewrite argument to `--sysroot` if `CCACHE_BASEDIR` is used.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
+
+- Don't crash if `getcwd()` fails.
-- Don’t crash if `getcwd()` fails.
+- Fixed alignment of "`called for preprocessing`" counter.
-- Fixed alignment of “called for preprocessing” counter.
+== Ccache 3.1.5
-ccache 3.1.5
-------------
Release date: 2011-05-29
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
-- Added a new statistics counter named “called for preprocessing”.
+- Added a new statistics counter named "`called for preprocessing`".
- The original command line is now logged to the file specified with
`CCACHE_LOGFILE`.
- Improved order of statistics counters in `ccache -s` output.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- The `-MF`/`-MT`/`-MQ` options with concatenated argument are now handled
correctly when they are last on the command line.
- Fixed a minor memory leak.
-- Systems that lack (and don’t need to be linked with) libm are now supported.
+- Systems that lack (and don't need to be linked with) libm are now supported.
+
+== Ccache 3.1.4
-ccache 3.1.4
-------------
Release date: 2011-01-09
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Made a work-around for a bug in `gzputc()` in zlib 1.2.5.
-- Corrupt manifest files are now removed so that they won’t block direct mode
+- Corrupt manifest files are now removed so that they won't block direct mode
hits.
-- ccache now copes with file systems that don’t know about symbolic links.
+- ccache now copes with file systems that don't know about symbolic links.
-- The file handle in now correctly closed on write error when trying to create
+- The file handle is now correctly closed on write error when trying to create
a cache dir tag.
-ccache 3.1.3
-------------
+== Ccache 3.1.3
+
Release date: 2010-11-28
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- The -MFarg, -MTarg and -MQarg compiler options (i.e, without space between
option and argument) are now handled correctly.
-Other
-~~~~~
+=== Other
+
+- Portability fixes for HP-UX 11.00 and other less common systems.
-- Portability fixes for HP-UX 11.00 and other esoteric systems.
+== Ccache 3.1.2
-ccache 3.1.2
-------------
Release date: 2010-11-21
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Bail out on too hard compiler options `-fdump-*`.
- Fixed issue when parsing precompiler output on AIX.
-Other
-~~~~~
+=== Other
- Improved documentation on which information is included in the hash sum.
-- Made the “too new header file” test case work on file systems with
+- Made the "`too new header file`" test case work on file systems with
unsynchronized clocks.
- The test suite now also works on systems that lack a /dev/zero.
-ccache 3.1.1
-------------
+== Ccache 3.1.1
+
Release date: 2010-11-07
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- ccache now falls back to preprocessor mode when a non-regular include file
(device, socket, etc) has been detected so that potential hanging due to
- Fixed configure detection of ar.
- ccache development version (set by dev.mk) now works with gits whose
- `describe` command doesn’t understand `--dirty`.
+ `describe` command doesn't understand `--dirty`.
-Other
-~~~~~
+=== Other
- Minor debug log message improvements.
-ccache 3.1
-----------
+== Ccache 3.1
+
Release date: 2010-09-16
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
- Added support for hashing the output of a custom command (e.g. `%compiler%
--version`) to identify the compiler instead of stat-ing or hashing the
compiler binary. This can improve robustness when the compiler (as seen by
- ccache) actually isn’t the real compiler but another compiler wrapper.
+ ccache) actually isn't the real compiler but another compiler wrapper.
- Added support for caching compilations that use precompiled headers. (See the
manual for important instructions regarding this.)
- Reading and writing of statistics counters has been made forward-compatible
(unknown counters are retained).
-- Files are now read without using `mmap()`. This has two benefits: it’s more
+- Files are now read without using `mmap()`. This has two benefits: it's more
robust against file changes during reading and it improves performance on
- poor systems where `mmap()` doesn’t use the disk cache.
+ poor systems where `mmap()` doesn't use the disk cache.
- Added `.cp` and `.CP` as known C++ suffixes.
statistics when using the Darwin linker.)
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Non-fatal error messages are now never printed to stderr but logged instead.
- EINTR is now handled correctly.
-Other
-~~~~~
+=== Other
- Work on porting ccache to win32 (native), mostly done by Ramiro Polla. The
port is not yet finished, but will hopefully be complete in some subsequent
- New `HACKING.txt` file with some notes about ccache code conventions.
-ccache 3.0.1
-------------
+== Ccache 3.0.1
+
Release date: 2010-07-15
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
-- The statistics counter “called for link” is now correctly updated when
+- The statistics counter "`called for link`" is now correctly updated when
linking with a single object file.
- Fixed a problem with out-of-source builds.
-ccache 3.0
-----------
+== Ccache 3.0
+
Release date: 2010-06-20
or later.
-Upgrade notes
-~~~~~~~~~~~~~
+=== Upgrade notes
-- The way the hashes are calculated has changed, so you won’t get cache hits
+- The way the hashes are calculated has changed, so you won't get cache hits
for compilation results stored by older ccache versions. Because of this, you
might as well clear the old cache directory with `ccache --clear` if you
want, unless you plan to keep using an older ccache version.
-New features and enhancements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New features and enhancements
-- ccache now has a “direct mode” where it computes a hash of the source code
+- ccache now has a "`direct mode`" where it computes a hash of the source code
(including all included files) and compiler options without running the
preprocessor. By not running the preprocessor, CPU usage is reduced; the
speed is somewhere between 1 and 5 times that of ccache running in
- Object files are now optionally stored compressed in the cache. The runtime
cost is negligible, and more files will fit in the ccache directory and in
the disk cache. Set `CCACHE_COMPRESS` to enable object file compression. Note
- that you can’t use compression in combination with the hard link feature.
+ that you can't use compression in combination with the hard link feature.
- A `CCACHE_COMPILERCHECK` option has been added. This option tells ccache what
compiler-identifying information to hash to ensure that results retrieved
- from the cache are accurate. Possible values are: none (don’t hash anything),
- mtime (hash the compiler’s mtime and size) and content (hash the content of
+ from the cache are accurate. Possible values are: none (don't hash anything),
+ mtime (hash the compiler's mtime and size) and content (hash the content of
the compiler binary). The default is mtime.
- It is now possible to specify extra files whose contents should be included
in the hash sum by setting the `CCACHE_EXTRAFILES` option.
- Added support for Objective-C and Objective-C\+\+. The statistics counter
- “not a C/C++ file” has been renamed to “unsupported source language”.
+ "`not a C/C++ file`" has been renamed to "`unsupported source language`".
- Added support for the `-x` compiler option.
- Temporary files that later will be moved into the cache are now created in
the cache directory they will end up in. This makes ccache more friendly to
- Linux’s directory layout.
+ Linux's directory layout.
- Improved the test suite and added tests for most of the new functionality.
- It’s now also possible to specify a subset of tests to run.
+ It's now also possible to specify a subset of tests to run.
- Standard error output from the compiler is now only stored in the cache if
- it’s non-empty.
+ it's non-empty.
- If the compiler produces no object file or an empty object file, but gives a
zero exit status (could be due to a file system problem, a buggy program
- Added `installcheck` and `distcheck` make targets.
-- Clarified cache size limit options’ and cleanup semantics.
+- Clarified cache size limit options' and cleanup semantics.
- Improved display of cache max size values.
`-iwithprefixbefore`, `-nostdinc`, `-nostdinc++` and `-U`.
-Bug fixes
-~~~~~~~~~
+=== Bug fixes
- Various portability improvements.
`-save-temps`. Also bail out on `@file` style options.
- Errors when using multiple `-arch` compiler options are now noted as
- “unsupported compiler option”.
+ "`unsupported compiler option`".
- `-MD`/`-MMD` options without `-MT`/`-MF` are now handled correctly.
--- /dev/null
+@import url(https://fonts.googleapis.com/css?family=Montserrat|Open+Sans);
+@import url(https://cdn.jsdelivr.net/gh/asciidoctor/asciidoctor@2.0/data/stylesheets/asciidoctor-default.css); /* Default asciidoc style framework - important */
+
+:root{
+--maincolor:#ffffff;
+--primarycolor:#294172;
+--secondarycolor:#5f646c;
+--tertiarycolor:#cccccc;
+--highlightcolor:#f5f5f5;
+--sidebarbackground:#f5f5f5;
+--linkcolor:#02a;
+--linkcoloralternate:#db3279;
+--white:#ffffff;
+--black:#000000;
+}
+
+/* Text styles */
+
+body{font-family: "Open Sans", sans-serif;background-color: var(--maincolor);color:var(--black);}
+
+h1{color:var(--primarycolor) !important;font-family:"Montserrat",sans-serif;}
+h2,h3,h4,h5,h6{color:var(--secondarycolor) !important;font-family:"Montserrat",sans-serif;}
+.title{color:var(--black) !important;font-family:"Open Sans",sans-serif;font-style: normal; font-weight: normal;}
+a{text-decoration: none;}
+a:hover{color: #3958da; text-decoration: underline;}
+p{font-family: "Open Sans",sans-serif ! important}
+#toc.toc2 a:link{color:var(--linkcolor);}
+blockquote{color:var(--linkcoloralternate) !important}
+.quoteblock blockquote:before{color:var(--linkcoloralternate)}
+code{color:var(--black);background-color: var(--highlightcolor) !important}
+mark{background-color: var(--highlightcolor)} /* Text highlighting color */
+
+/* Table styles */
+th{background-color: var(--maincolor);color:var(--black) !important;}
+td{background-color: var(--maincolor);color: var(--black) !important}
+
+
+#toc.toc2{background-color:var(--sidebarbackground);}
+#toctitle{color:var(--black);}
+
+/* Responsiveness fixes */
+video {
+ max-width: 100%;
+}
+
+@media all and (max-width: 600px) {
+table {
+ width: 55vw!important;
+ font-size: 3vw;
+}
For instance, run something like this to build ccache in Ubuntu 20.04:
- misc/build-in-docker ubuntu-20-focal
+ misc/build-in-docker ubuntu-20.04
The above command will first build the Ubuntu 20.04 Docker image if needed and
finally build ccache and run the ccache test suite.
--- /dev/null
+FROM alpine:3.11
+
+RUN apk add --no-cache \
+ bash \
+ ccache \
+ clang \
+ cmake \
+ elfutils \
+ g++ \
+ gcc \
+ hiredis-dev \
+ libc-dev \
+ make \
+ perl \
+ python3 \
+ redis \
+ zstd-dev
+
+# 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
+++ /dev/null
-FROM alpine:3.12
-
-RUN apk add --no-cache \
- bash \
- ccache \
- clang \
- cmake \
- elfutils \
- g++ \
- gcc \
- libc-dev \
- make \
- perl \
- zstd-dev
-
-# 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
--- /dev/null
+FROM alpine:3.15
+
+RUN apk add --no-cache \
+ bash \
+ ccache \
+ clang \
+ cmake \
+ elfutils \
+ g++ \
+ gcc \
+ hiredis-dev \
+ libc-dev \
+ make \
+ perl \
+ python3 \
+ redis \
+ zstd-dev
+
+# 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
+++ /dev/null
-# Released 2016, this is the first release to contain cmake >= 3.4.3
-
-FROM alpine:3.4
-
-RUN apk add --no-cache \
- bash \
- ccache \
- cmake \
- g++ \
- gcc \
- libc-dev \
- make \
- perl
-
-# 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
FROM centos:7
RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \
+ && yum install -y centos-release-scl \
&& yum install -y \
- asciidoc \
autoconf \
bash \
ccache \
clang \
cmake3 \
+ devtoolset-11 \
elfutils \
gcc \
gcc-c++ \
libzstd-devel \
make \
-# Remove superfluous dependencies brought in by asciidoc:
- && rpm -e --nodeps graphviz \
+ python3 \
&& yum autoremove -y \
&& yum clean all \
&& cp /usr/bin/cmake3 /usr/bin/cmake \
&& cp /usr/bin/ctest3 /usr/bin/ctest
+
+ENTRYPOINT ["scl", "enable", "devtoolset-11", "--"]
+++ /dev/null
-FROM centos:8
-
-RUN dnf install -y epel-release \
- && dnf install -y \
- asciidoc \
- autoconf \
- bash \
- ccache \
- clang \
- cmake \
- diffutils \
- elfutils \
- gcc \
- gcc-c++ \
- libzstd-devel \
- make \
- && dnf clean all
+++ /dev/null
-FROM debian:10
-
-RUN apt-get update \
- && apt-get install -y --no-install-recommends \
- bash \
- build-essential \
- ccache \
- clang \
- cmake \
- 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
--- /dev/null
+FROM debian:11
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ bash \
+ build-essential \
+ ccache \
+ clang \
+ cmake \
+ elfutils \
+ gcc-multilib \
+ libhiredis-dev \
+ libzstd-dev \
+ python3 \
+ redis-server \
+ redis-tools \
+ && 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
+++ /dev/null
-FROM debian:9
-
-RUN apt-get update \
- && apt-get install -y --no-install-recommends \
- bash \
- build-essential \
- ccache \
- clang \
- cmake \
- 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
+++ /dev/null
-FROM fedora:32
-
-RUN dnf install -y \
- autoconf \
- bash \
- ccache \
- clang \
- cmake \
- diffutils \
- elfutils \
- findutils \
- gcc \
- gcc-c++ \
- libzstd-devel \
- make \
- && dnf clean all
--- /dev/null
+FROM fedora:36
+
+RUN dnf install -y \
+ autoconf \
+ bash \
+ ccache \
+ clang \
+ cmake \
+ diffutils \
+ elfutils \
+ findutils \
+ gcc \
+ gcc-c++ \
+ hiredis-devel \
+ libzstd-devel \
+ make \
+ python3 \
+ redis \
+ && dnf clean all
+++ /dev/null
-FROM ubuntu:14.04
-
-RUN apt-get update \
- && apt-get install -y --no-install-recommends \
- asciidoc \
- bash \
- build-essential \
- ccache \
- clang-3.4 \
- curl \
- docbook-xml \
- docbook-xsl \
- elfutils \
- g++-multilib \
- ninja-build \
- wget \
- 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
-
-# The distribution's CMake it too old (2.8.12.2).
-RUN curl -sSL https://cmake.org/files/v3.5/cmake-3.5.2-Linux-x86_64.tar.gz | sudo tar -xzC /opt \
- && cp -a /opt/cmake-3.5.2-Linux-x86_64/bin /usr/local \
- && cp -a /opt/cmake-3.5.2-Linux-x86_64/share /usr/local
+++ /dev/null
-FROM ubuntu:16.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 \
- libzstd1-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
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
- asciidoc \
+ asciidoctor \
bash \
build-essential \
ccache \
clang \
- cmake \
docbook-xml \
docbook-xsl \
elfutils \
gcc-multilib \
+ gpg \
+ libhiredis-dev \
libzstd-dev \
- xsltproc \
+ python3 \
+ redis-server \
+ redis-tools \
+ software-properties-common \
+ wget \
+ && add-apt-repository ppa:ubuntu-toolchain-r/test \
+ && apt install -y --no-install-recommends g++-9 \
+ && wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor - >/usr/share/keyrings/kitware-archive-keyring.gpg \
+ && echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ bionic main' >/etc/apt/sources.list.d/kitware.list \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends cmake \
&& rm -rf /var/lib/apt/lists/*
# Redirect all compilers to ccache.
# Non-interactive: do not set up timezone settings.
RUN apt-get update \
&& DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
- asciidoc \
+ asciidoctor \
bash \
build-essential \
ccache \
docbook-xsl \
elfutils \
gcc-multilib \
+ libhiredis-dev \
libzstd-dev \
- xsltproc \
+ python3 \
+ redis-server \
+ redis-tools \
&& rm -rf /var/lib/apt/lists/*
# Redirect all compilers to ccache.
--- /dev/null
+FROM ubuntu:22.04
+
+# Non-interactive: do not set up timezone settings.
+RUN apt-get update \
+ && DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
+ asciidoctor \
+ bash \
+ build-essential \
+ ccache \
+ clang \
+ cmake \
+ docbook-xml \
+ docbook-xsl \
+ elfutils \
+ gcc-multilib \
+ gcc-12 \
+ g++-12 \
+ libhiredis-dev \
+ libzstd-dev \
+ python3 \
+ redis-server \
+ redis-tools \
+ && 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
--- /dev/null
+0 beshort 0xCCAC ccache entry
+>2 byte x \b, format version %d
+>3 byte 0 \b, result
+>3 byte 1 \b, manifest
copyable
creat
files'
+fo
+hda
pase
seve
stoll
--- /dev/null
+#!/usr/bin/env python3
+
+# This script downloads the contents of the local cache from a Redis remote
+# storage.
+
+import redis
+import os
+
+import progress.bar
+import progress.spinner
+import humanize
+
+config = os.getenv("REDIS_CONF", "localhost")
+if ":" in config:
+ host, port = config.rsplit(":", 1)
+ sock = None
+elif config.startswith("/"):
+ host, port, sock = None, None, config
+else:
+ host, port, sock = config, 6379, None
+
+username = os.getenv("REDIS_USERNAME")
+password = os.getenv("REDIS_PASSWORD")
+context = redis.Redis(host=host, port=port, unix_socket_path=sock, password=password)
+pipe = context.pipeline(transaction=False)
+
+ccache = os.getenv("CCACHE_DIR", os.path.expanduser("~/.cache/ccache"))
+
+try:
+ count = context.info()["db0"]["keys"]
+except Exception:
+ count = None # only for showing progress
+files = result = manifest = objects = 0
+size = 0
+columns = os.get_terminal_size()[0]
+width = min(columns - 24, 100)
+bar = progress.bar.Bar(
+ "Downloading...", max=count, fill="=", suffix="%(percent).1f%%", width=width
+)
+if not count:
+ bar = progress.spinner.Spinner("Downloading... ")
+
+# Note: doesn't work with the SSDB SCAN command
+# syntax: keys key_start, key_end, limit
+for key in context.scan_iter():
+ if not key.startswith(b"ccache:"):
+ bar.next()
+ continue
+ base = key.decode().replace("ccache:", "")
+ pipe.get(key)
+ val = pipe.execute()[-1]
+ if val is None:
+ continue
+ if val[0:2] == b"\xcc\xac": # magic
+ objects += 1
+ if val[2] == 0 and val[3] == 0:
+ ext = "R"
+ result += 1
+ elif val[2] == 0 and val[3] == 1:
+ ext = "M"
+ manifest += 1
+ else:
+ bar.next()
+ continue
+ filename = os.path.join(ccache, base[0], base[1], base[2:] + ext)
+ if not os.path.exists(filename):
+ dirname = os.path.join(ccache, base[0], base[1])
+ os.makedirs(dirname, mode=0o755, exist_ok=True)
+ with open(filename, "wb") as out:
+ out.write(val)
+ files += 1
+ size += len(val)
+ bar.next()
+bar.finish()
+
+print(
+ "%d files, %d result (%d manifest) = %d objects (%s)"
+ % (files, result, manifest, objects, humanize.naturalsize(size, binary=True))
+)
if [ -n "$check" ]; then
status=1
echo "Error: $file not formatted with Clang-Format"
- echo 'Run "make format" or apply this diff:'
+ echo
+ echo 'Please run "make format" (or "ninja format"), or apply this diff:'
+ echo
git diff $cf_color --no-index "$file" "$tmp_file" \
| sed -E -e "s!^---.*!--- a/$file!" \
-e "s!^\+\+\+.*!+++ b/$file!" \
op.add_option(
"--no-compression", help="disable compression", action="store_true"
)
- op.add_option(
- "--compression-level", help="set compression level", type=int
- )
+ op.add_option("--compression-level", help="set compression level", type=int)
op.add_option(
"-d",
"--directory",
local cxx=$3
local test_cc=$4
shift 4
+ local cmake_params="$*"
+ if command -v >/dev/null ccache; then
+ cmake_params="${cmake_params} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
+ fi
echo "Build in Docker: $name CC=$cc CXX=$cxx TEST_CC=$test_cc CMAKE_PARAMS=\"$*\""
- ASM=$cc CC=$cc CXX=$cxx TEST_CC=$test_cc CMAKE_PARAMS="$*" $build_in_docker $name
+ ASM=$cc CC=$cc CXX=$cxx TEST_CC=$test_cc CMAKE_PARAMS="${cmake_params}" $build_in_docker $name
}
-# NAME CC CXX TEST_CC CMAKE_PARAMS
+# NAME CC CXX TEST_CC CMAKE_PARAMS
-build debian-9 gcc g++ gcc
-build debian-9 clang clang++ clang
+build debian-11 gcc g++ gcc
+build debian-11 clang clang++ clang
-build debian-10 gcc g++ gcc
-build debian-10 clang clang++ clang
+build ubuntu-18.04 gcc-9 g++-9 gcc
+build ubuntu-18.04 clang clang++ clang
-build ubuntu-14.04 gcc g++ gcc -DZSTD_FROM_INTERNET=ON
-build ubuntu-14.04 gcc g++ clang -DZSTD_FROM_INTERNET=ON
+build ubuntu-20.04 gcc g++ gcc
+build ubuntu-20.04 clang clang++ clang
-build ubuntu-16.04 gcc g++ gcc
-build ubuntu-16.04 clang clang++ clang
+build ubuntu-22.04 gcc g++ gcc
+build ubuntu-22.04 clang clang++ clang
-build ubuntu-18.04 gcc g++ gcc
-build ubuntu-18.04 clang clang++ clang
+build ubuntu-22.04 gcc-12 g++-12 gcc
+build ubuntu-22.04 clang clang++ clang
-build ubuntu-20.04 gcc g++ gcc
-build ubuntu-20.04 clang clang++ clang
+build centos-7 gcc g++ gcc -DHIREDIS_FROM_INTERNET=ON
+build centos-7 gcc g++ clang -DHIREDIS_FROM_INTERNET=ON
-build centos-7 gcc g++ gcc -DWARNINGS_AS_ERRORS=false
-build centos-7 gcc g++ clang -DWARNINGS_AS_ERRORS=false
+build fedora-36 gcc g++ gcc
+build fedora-36 clang clang++ clang
-build centos-8 gcc g++ gcc
-build centos-8 clang clang++ clang
+build alpine-3.11 gcc g++ gcc
+build alpine-3.11 gcc g++ clang
-build fedora-32 gcc g++ gcc
-build fedora-32 clang clang++ clang
-
-build alpine-3.4 gcc g++ gcc -DZSTD_FROM_INTERNET=ON
-build alpine-3.4 gcc g++ clang -DZSTD_FROM_INTERNET=ON
-
-build alpine-3.12 gcc g++ gcc
-build alpine-3.12 clang clang++ clang
+build alpine-3.15 gcc g++ gcc
+build alpine-3.15 clang clang++ clang
#!/bin/sh
+anonymous="^(6d5CfLQ3dYAb|bengtj|Delgan|luzpaz|rblx-kbuck|RW|vsplesk)$"
+
if [ -d .git ]; then
# Fetch full Git history if needed, e.g. when run via CI.
git fetch --unshallow 2>/dev/null
# a "Co-authored-by:" in the commit message.
(git log | grep -Po "(?<=Co-authored-by: )(.*)(?= <)"; \
git log --format="%aN") \
+ | grep -Ev "$anonymous" \
| sed 's/^/* /' \
| LANG=en_US.utf8 sort -uf \
| perl -00 -p -i -e 's/^\*.*/<STDIN> . "\n"/es' doc/AUTHORS.adoc
--- /dev/null
+#!/usr/bin/env python3
+
+# This script uploads the contents of the local cache to a Redis remote storage.
+
+import redis
+import os
+
+import progress.bar
+import humanize
+
+config = os.getenv("REDIS_CONF", "localhost")
+if ":" in config:
+ host, port = config.rsplit(":", 1)
+ sock = None
+elif config.startswith("/"):
+ host, port, sock = None, None, config
+else:
+ host, port, sock = config, 6379, None
+username = os.getenv("REDIS_USERNAME")
+password = os.getenv("REDIS_PASSWORD")
+context = redis.Redis(host=host, port=port, unix_socket_path=sock, password=password)
+pipe = context.pipeline(transaction=False)
+
+ccache = os.getenv("CCACHE_DIR", os.path.expanduser("~/.cache/ccache"))
+filelist = []
+for dirpath, dirnames, filenames in os.walk(ccache):
+ # sort by modification time, most recently used last
+ for filename in filenames:
+ if filename.endswith(".lock"):
+ continue
+ stat = os.stat(os.path.join(dirpath, filename))
+ filelist.append((stat.st_mtime, dirpath, filename, stat.st_size))
+filelist.sort()
+
+files = result = manifest = objects = 0
+size = 0
+batchsize = 0
+columns = os.get_terminal_size()[0]
+width = min(columns - 22, 100)
+bar = progress.bar.Bar(
+ "Uploading...", max=len(filelist), fill="=", suffix="%(percent).1f%%", width=width
+)
+for mtime, dirpath, filename, filesize in filelist:
+ dirname = dirpath.replace(ccache + os.path.sep, "")
+ if dirname == "tmp":
+ continue
+ elif filename == "CACHEDIR.TAG" or filename == "stats":
+ # ignore these
+ files += 1
+ else:
+ (base, ext) = filename[:-1], filename[-1:]
+ if ext == "R" or ext == "M":
+ if ext == "R":
+ result += 1
+ if ext == "M":
+ manifest += 1
+ key = "ccache:" + "".join(list(os.path.split(dirname)) + [base])
+ val = open(os.path.join(dirpath, filename), "rb").read() or None
+ if val:
+ # print("%s: %s %d" % (key, ext, len(val)))
+ pipe.setnx(key, val)
+ objects += 1
+ files += 1
+ size += filesize
+ batchsize += filesize
+ if batchsize > 64 * 1024 * 1024:
+ pipe.execute()
+ batchsize = 0
+ bar.next()
+pipe.execute()
+bar.finish()
+
+print(
+ "%d files, %d result (%d manifest) = %d objects (%s)"
+ % (files, result, manifest, objects, humanize.naturalsize(size, binary=True))
+)
-readability-implicit-bool-conversion,
-readability-magic-numbers,
-readability-else-after-return,
+ -readability-function-cognitive-complexity,
-readability-named-parameter,
-readability-qualified-auto,
-readability-redundant-declaration,
-modernize-return-braced-init-list,
-modernize-use-auto,
-modernize-use-default-member-init,
+ -modernize-use-nodiscard,
-modernize-use-trailing-return-type,
cppcoreguidelines-*,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
# If you hit a limit, please consider changing the code instead of the limit.
- key: readability-function-size.LineThreshold
- value: 700
+ value: 999999
- key: readability-function-size.StatementThreshold
value: 999999
- key: readability-function-size.BranchThreshold
- value: 170
+ value: 999999
- key: readability-function-size.ParameterThreshold
- value: 6
+ value: 7
- key: readability-function-size.NestingThreshold
value: 999999
- key: readability-function-size.VariableThreshold
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Util.hpp"
-using nonstd::nullopt;
-using nonstd::optional;
-using nonstd::string_view;
+#include <Logging.hpp>
+#include <core/exceptions.hpp>
+#include <util/file.hpp>
+#include <util/string.hpp>
Args::Args(Args&& other) noexcept : m_args(std::move(other.m_args))
{
}
Args
-Args::from_string(const std::string& command)
+Args::from_string(std::string_view command)
{
Args args;
for (const std::string& word : Util::split_into_strings(command, " \t\r\n")) {
return args;
}
-optional<Args>
-Args::from_gcc_atfile(const std::string& filename)
+std::optional<Args>
+Args::from_atfile(const std::string& filename, AtFileFormat format)
{
- std::string argtext;
- try {
- argtext = Util::read_file(filename);
- } catch (Error&) {
- return nullopt;
+ const auto argtext = util::read_file<std::string>(filename);
+ if (!argtext) {
+ LOG("Failed to read atfile {}: {}", filename, argtext.error());
+ return std::nullopt;
}
Args args;
- auto pos = argtext.c_str();
+ auto pos = argtext->c_str();
std::string argbuf;
- argbuf.resize(argtext.length() + 1);
+ argbuf.resize(argtext->length() + 1);
auto argpos = argbuf.begin();
// Used to track quoting state; if \0 we are not inside quotes. Otherwise
switch (*pos) {
case '\\':
pos++;
- if (*pos == '\0') {
- continue;
+ switch (format) {
+ case AtFileFormat::gcc:
+ if (*pos == '\0') {
+ continue;
+ }
+ break;
+ case AtFileFormat::msvc:
+ if (*pos != '"' && *pos != '\\') {
+ pos--;
+ }
+ break;
}
break;
- case '"':
case '\'':
+ if (format == AtFileFormat::msvc) {
+ break;
+ }
+ [[fallthrough]];
+
+ case '"':
if (quoting != '\0') {
if (quoting == *pos) {
quoting = '\0';
if (quoting) {
break;
}
- // Fall through.
+ [[fallthrough]];
case '\0':
// End of token
}
void
-Args::erase_last(string_view arg)
+Args::erase_last(std::string_view arg)
{
const auto it = std::find(m_args.rbegin(), m_args.rend(), arg);
if (it != m_args.rend()) {
}
void
-Args::erase_with_prefix(string_view prefix)
+Args::erase_with_prefix(std::string_view prefix)
{
m_args.erase(std::remove_if(m_args.begin(),
m_args.end(),
- [&prefix](const std::string& s) {
- return Util::starts_with(s, prefix);
+ [&prefix](const auto& s) {
+ return util::starts_with(s, prefix);
}),
m_args.end());
}
-// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include "NonCopyable.hpp"
-#include "Util.hpp"
-
-#include "third_party/nonstd/optional.hpp"
-#include "third_party/nonstd/string_view.hpp"
#include <deque>
+#include <optional>
#include <string>
+#include <string_view>
+#include <vector>
class Args
{
public:
+ enum class AtFileFormat {
+ gcc, // '\'' and '"' quote, '\\' escapes any character
+ msvc, // '"' quotes, '\\' escapes only '"' and '\\'
+ };
+
Args() = default;
Args(const Args& other) = default;
Args(Args&& other) noexcept;
static Args from_argv(int argc, const char* const* argv);
- static Args from_string(const std::string& command);
- static nonstd::optional<Args> from_gcc_atfile(const std::string& filename);
+ static Args from_string(std::string_view command);
+
+ static std::optional<Args>
+ from_atfile(const std::string& filename,
+ AtFileFormat format = AtFileFormat::gcc);
Args& operator=(const Args& other) = default;
Args& operator=(Args&& other) noexcept;
std::string to_string() const;
// Remove last argument equal to `arg`, if any.
- void erase_last(nonstd::string_view arg);
+ void erase_last(std::string_view arg);
// Remove all arguments with prefix `prefix`.
- void erase_with_prefix(nonstd::string_view prefix);
+ void erase_with_prefix(std::string_view prefix);
// Insert arguments in `args` at position `index`.
void insert(size_t index, const Args& args);
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include "Args.hpp"
+#include <optional>
#include <string>
#include <vector>
// This class holds meta-information derived from the compiler arguments.
struct ArgsInfo
{
- // The source file.
+ // The source file path.
+ std::string orig_input_file;
+
+ // The source file path, potentially rewritten into relative.
std::string input_file;
+ // The source file path run through Util::normalize_concrete_absolute_path.
+ std::string normalized_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 orig_output_obj;
+
+ // The output file being compiled to, potentially rewritten into relative.
std::string output_obj;
- // The path to the dependency file (implicit or specified with -MF).
+ // The path to the dependency file (implicit or specified with -MFdepfile,
+ // -Wp,-MD,depfile or -Wp,-MMD,depfile).
std::string output_dep;
// The path to the stack usage (implicit when using -fstack-usage).
// Split dwarf information (GCC 4.8 and up). Contains pathname if not empty.
std::string output_dwo;
+ // Assembler listing file.
+ std::string output_al;
+
+ // The .gch/.pch/.pth file used for compilation.
+ std::string included_pch_file;
+
// Language to use for the compilation target (see language.c).
std::string actual_language;
// Is the compiler being asked to output dependencies?
bool generating_dependencies = false;
- // Seen -MD or -MMD?
- bool seen_MD_MMD = false;
+ // Is the compiler being asked to output includes (MSVC /showIncludes)?
+ bool generating_includes = false;
- // Is the dependency makefile target name specified with -MT or -MQ?
- bool dependency_target_specified = false;
+ // The dependency target in the dependency file (the object file unless
+ // overridden via e.g. -MT or -MQ).
+ std::optional<std::string> dependency_target;
// Is the compiler being asked to output coverage?
bool generating_coverage = false;
// Whether to strip color codes from diagnostic messages on output.
bool strip_diagnostics_colors = false;
+ // Have we seen --?
+ bool seen_double_dash = false;
+
// Have we seen -gsplit-dwarf?
bool seen_split_dwarf = false;
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "TemporaryFile.hpp"
#include "Util.hpp"
#include "assertions.hpp"
-#include "exceptions.hpp"
+
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
AtomicFile::AtomicFile(const std::string& path, Mode mode) : m_path(path)
{
- TemporaryFile tmp_file(path + ".tmp");
+ TemporaryFile tmp_file(path);
m_stream = fdopen(tmp_file.fd.release(), mode == Mode::binary ? "w+b" : "w+");
m_tmp_path = std::move(tmp_file.path);
}
}
void
-AtomicFile::write(const std::string& data)
+AtomicFile::write(std::string_view data)
{
if (fwrite(data.data(), data.size(), 1, m_stream) != 1) {
- throw Error("failed to write data to {}: {}", m_path, strerror(errno));
+ throw core::Error(
+ FMT("failed to write data to {}: {}", m_path, strerror(errno)));
}
}
void
-AtomicFile::write(const std::vector<uint8_t>& data)
+AtomicFile::write(nonstd::span<const uint8_t> data)
{
if (fwrite(data.data(), data.size(), 1, m_stream) != 1) {
- throw Error("failed to write data to {}: {}", m_path, strerror(errno));
+ throw core::Error(
+ FMT("failed to write data to {}: {}", m_path, strerror(errno)));
}
}
m_stream = nullptr;
if (result == EOF) {
Util::unlink_tmp(m_tmp_path);
- throw Error("failed to write data to {}: {}", m_path, strerror(errno));
+ throw core::Error(
+ FMT("failed to write data to {}: {}", m_path, strerror(errno)));
}
Util::rename(m_tmp_path, m_path);
}
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
+#include <third_party/nonstd/span.hpp>
+#include <cstdint>
+#include <cstdio>
#include <string>
-#include <vector>
// This class represents a file whose data will be atomically written to a path
// by renaming a temporary file in place.
FILE* stream();
- void write(const std::string& data);
- void write(const std::vector<uint8_t>& data);
+ void write(std::string_view data);
+ void write(nonstd::span<const uint8_t> data);
// Close the temporary file and rename it to the destination file. Note: The
// destructor will not do this automatically to avoid half-written data in the
source_files
Args.cpp
AtomicFile.cpp
- CacheEntryReader.cpp
- CacheEntryWriter.cpp
- CacheFile.cpp
- Compression.cpp
- Compressor.cpp
Config.cpp
Context.cpp
- Counters.cpp
- Decompressor.cpp
Depfile.cpp
+ Fd.cpp
Hash.cpp
- Lockfile.cpp
Logging.cpp
- Manifest.cpp
- MiniTrace.cpp
- NullCompressor.cpp
- NullDecompressor.cpp
ProgressBar.cpp
- Result.cpp
- ResultDumper.cpp
- ResultExtractor.cpp
- ResultRetriever.cpp
SignalHandler.cpp
Stat.cpp
- Statistics.cpp
TemporaryFile.cpp
ThreadPool.cpp
Util.cpp
- ZstdCompressor.cpp
- ZstdDecompressor.cpp
argprocessing.cpp
assertions.cpp
ccache.cpp
- cleanup.cpp
compopt.cpp
- compress.cpp
execute.cpp
hashutil.cpp
language.cpp
- version.cpp)
+ version.cpp
+)
if(INODE_CACHE_SUPPORTED)
list(APPEND source_files InodeCache.cpp)
endif()
+if(MTR_ENABLED)
+ list(APPEND source_files MiniTrace.cpp)
+endif()
+
if(WIN32)
list(APPEND source_files Win32Util.cpp)
endif()
-add_library(ccache_lib STATIC ${source_files})
-target_compile_definitions(
- ccache_lib PUBLIC -Dnssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_NONSTD
-)
+file(GLOB headers *.hpp)
+list(APPEND source_files ${headers})
+
+add_library(ccache_framework STATIC ${source_files})
if(WIN32)
- target_link_libraries(ccache_lib PRIVATE ws2_32 "psapi")
+ list(APPEND CCACHE_EXTRA_LIBS psapi)
+endif()
- if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
- 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()
+if(CCACHE_EXTRA_LIBS)
+ target_link_libraries(
+ ccache_framework
+ PRIVATE "${CCACHE_EXTRA_LIBS}"
+ )
endif()
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(
- ccache_lib
- PRIVATE standard_settings standard_warnings ZSTD::ZSTD
- Threads::Threads third_party_lib)
+ ccache_framework
+ PRIVATE standard_settings standard_warnings ZSTD::ZSTD Threads::Threads third_party
+)
+
+target_include_directories(ccache_framework PUBLIC ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
+
+if(REDIS_STORAGE_BACKEND)
+ target_compile_definitions(ccache_framework PUBLIC -DHAVE_REDIS_STORAGE_BACKEND)
+ target_link_libraries(
+ ccache_framework
+ PUBLIC standard_settings standard_warnings HIREDIS::HIREDIS third_party
+ )
+endif()
-target_include_directories(ccache_lib PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
+add_executable(test-lockfile test_lockfile.cpp)
+target_link_libraries(test-lockfile PRIVATE standard_settings standard_warnings ccache_framework)
+add_subdirectory(core)
+add_subdirectory(storage)
add_subdirectory(third_party)
+add_subdirectory(util)
+++ /dev/null
-// Copyright (C) 2019-2021 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 "CacheEntryReader.hpp"
-
-#include "Compressor.hpp"
-#include "exceptions.hpp"
-#include "fmtmacros.hpp"
-
-#include "third_party/fmt/core.h"
-
-CacheEntryReader::CacheEntryReader(FILE* stream,
- const uint8_t* expected_magic,
- uint8_t expected_version)
-{
- uint8_t header_bytes[15];
- if (fread(header_bytes, sizeof(header_bytes), 1, stream) != 1) {
- throw Error("Error reading header");
- }
-
- memcpy(m_magic, header_bytes, sizeof(m_magic));
- m_version = header_bytes[4];
- m_compression_type = Compression::type_from_int(header_bytes[5]);
- m_compression_level = header_bytes[6];
- Util::big_endian_to_int(header_bytes + 7, m_content_size);
-
- if (memcmp(m_magic, expected_magic, sizeof(m_magic)) != 0) {
- throw Error("Bad magic value 0x{:02x}{:02x}{:02x}{:02x}",
- m_magic[0],
- m_magic[1],
- m_magic[2],
- m_magic[3]);
- }
- if (m_version != expected_version) {
- throw Error(
- "Unknown version (actual {}, expected {})", m_version, expected_version);
- }
-
- m_checksum.update(header_bytes, sizeof(header_bytes));
- m_decompressor = Decompressor::create_from_type(m_compression_type, stream);
-}
-
-void
-CacheEntryReader::dump_header(FILE* dump_stream)
-{
- 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
-CacheEntryReader::read(void* data, size_t count)
-{
- m_decompressor->read(data, count);
- m_checksum.update(data, count);
-}
-
-void
-CacheEntryReader::finalize()
-{
- uint64_t actual_digest = m_checksum.digest();
-
- uint8_t buffer[8];
- read(buffer, sizeof(buffer));
- uint64_t expected_digest;
- Util::big_endian_to_int(buffer, expected_digest);
-
- if (actual_digest != expected_digest) {
- throw Error("Incorrect checksum (actual 0x{:016x}, expected 0x{:016x})",
- actual_digest,
- expected_digest);
- }
-
- m_decompressor->finalize();
-}
+++ /dev/null
-// Copyright (C) 2019-2021 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 "system.hpp"
-
-#include "Checksum.hpp"
-#include "Decompressor.hpp"
-#include "Util.hpp"
-
-#include <memory>
-
-// This class knows how to read a cache entry with a common header and a
-// payload part that is different depending on the cache entry type (result or
-// manifest).
-class CacheEntryReader
-{
-public:
- // Constructor.
- //
- // Parameters:
- // - stream: Stream to read header and payload from.
- // - expected_magic: Expected file format magic (first four bytes of the
- // file).
- // - expected_version: Expected file format version.
- CacheEntryReader(FILE* stream,
- const uint8_t* expected_magic,
- uint8_t expected_version);
-
- // Dump header information in text format.
- //
- // Parameters:
- // - dump_stream: Stream to write to.
- void dump_header(FILE* dump_stream);
-
- // Read data into a buffer from the payload.
- //
- // Parameters:
- // - data: Buffer to write data to.
- // - count: How many bytes to write.
- //
- // Throws Error on failure.
- void read(void* data, size_t count);
-
- // Read an unsigned integer from the payload.
- //
- // Parameters:
- // - value: Variable to write to.
- //
- // Throws Error on failure.
- template<typename T> void read(T& value);
-
- // Close for reading.
- //
- // This method potentially verifies the end state after reading the cache
- // entry and throws Error if any integrity issues are found.
- void finalize();
-
- // Get size of the payload,
- uint64_t payload_size() const;
-
- // Get content magic.
- const uint8_t* magic() const;
-
- // Get content version.
- uint8_t version() const;
-
- // Get compression type.
- Compression::Type compression_type() const;
-
- // Get compression level.
- int8_t compression_level() const;
-
- // Get size of the content (header + payload + checksum).
- uint64_t content_size() const;
-
-private:
- std::unique_ptr<Decompressor> m_decompressor;
- Checksum m_checksum;
- uint8_t m_magic[4];
- uint8_t m_version;
- Compression::Type m_compression_type;
- int8_t m_compression_level;
- uint64_t m_content_size;
-};
-
-template<typename T>
-inline void
-CacheEntryReader::read(T& value)
-{
- uint8_t buffer[sizeof(T)];
- read(buffer, sizeof(T));
- Util::big_endian_to_int(buffer, value);
-}
-
-inline const uint8_t*
-CacheEntryReader::magic() const
-{
- return m_magic;
-}
-
-inline uint8_t
-CacheEntryReader::version() const
-{
- return m_version;
-}
-
-inline Compression::Type
-CacheEntryReader::compression_type() const
-{
- return m_compression_type;
-}
-
-inline int8_t
-CacheEntryReader::compression_level() const
-{
- return m_compression_level;
-}
-
-inline uint64_t
-CacheEntryReader::payload_size() const
-{
- return m_content_size - 15 - 8;
-}
-
-inline uint64_t
-CacheEntryReader::content_size() const
-{
- return m_content_size;
-}
+++ /dev/null
-// Copyright (C) 2019-2021 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 "CacheEntryWriter.hpp"
-
-CacheEntryWriter::CacheEntryWriter(FILE* stream,
- const uint8_t* magic,
- uint8_t version,
- Compression::Type compression_type,
- int8_t compression_level,
- uint64_t payload_size)
- // clang-format off
- : m_compressor(
- Compressor::create_from_type(compression_type, stream, compression_level))
-// clang-format on
-{
- uint8_t header_bytes[15];
- memcpy(header_bytes, magic, 4);
- header_bytes[4] = version;
- header_bytes[5] = static_cast<uint8_t>(compression_type);
- header_bytes[6] = m_compressor->actual_compression_level();
- uint64_t content_size = 15 + payload_size + 8;
- Util::int_to_big_endian(content_size, header_bytes + 7);
- if (fwrite(header_bytes, sizeof(header_bytes), 1, stream) != 1) {
- throw Error("Failed to write cache entry header");
- }
- m_checksum.update(header_bytes, sizeof(header_bytes));
-}
-
-void
-CacheEntryWriter::write(const void* data, size_t count)
-{
- m_compressor->write(data, count);
- m_checksum.update(data, count);
-}
-
-void
-CacheEntryWriter::finalize()
-{
- uint8_t buffer[8];
- Util::int_to_big_endian(m_checksum.digest(), buffer);
- m_compressor->write(buffer, sizeof(buffer));
- m_compressor->finalize();
-}
+++ /dev/null
-// Copyright (C) 2019-2021 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 "system.hpp"
-
-#include "Checksum.hpp"
-#include "Compressor.hpp"
-#include "Util.hpp"
-
-#include <memory>
-
-// This class knows how to write a cache entry with a common header and a
-// payload part that is different depending on the cache entry type (result or
-// manifest).
-class CacheEntryWriter
-{
-public:
- // Constructor.
- //
- // Parameters:
- // - stream: Stream to write header + payload to.
- // - magic: File format magic (first four bytes of the file).
- // - version: File format version.
- // - compression_type: Compression type to use.
- // - compression_level: Compression level to use.
- // - payload_size: Payload size.
- CacheEntryWriter(FILE* stream,
- const uint8_t* magic,
- uint8_t version,
- Compression::Type compression_type,
- int8_t compression_level,
- uint64_t payload_size);
-
- // Write data to the payload from a buffer.
- //
- // Parameters:
- // - data: Data to write.
- // - count: Size of data to write.
- //
- // Throws Error on failure.
- void write(const void* data, size_t count);
-
- // Write an unsigned integer to the payload.
- //
- // Parameters:
- // - value: Value to write.
- //
- // Throws Error on failure.
- template<typename T> void write(T value);
-
- // Close for writing.
- //
- // This method potentially verifies the end state after writing the cache
- // entry and throws Error if any integrity issues are found.
- void finalize();
-
-private:
- std::unique_ptr<Compressor> m_compressor;
- Checksum m_checksum;
-};
-
-template<typename T>
-inline void
-CacheEntryWriter::write(T value)
-{
- uint8_t buffer[sizeof(T)];
- Util::int_to_big_endian(value, buffer);
- write(buffer, sizeof(T));
-}
+++ /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
-
-#include "CacheFile.hpp"
-
-#include "Manifest.hpp"
-#include "Result.hpp"
-#include "Util.hpp"
-
-const Stat&
-CacheFile::lstat() const
-{
- if (!m_stat) {
- m_stat = Stat::lstat(m_path);
- }
-
- return *m_stat;
-}
-
-CacheFile::Type
-CacheFile::type() const
-{
- if (Util::ends_with(m_path, Manifest::k_file_suffix)) {
- return Type::manifest;
- } else if (Util::ends_with(m_path, Result::k_file_suffix)) {
- return Type::result;
- } else {
- return Type::unknown;
- }
-}
+++ /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 "system.hpp"
-
-#include "Stat.hpp"
-
-#include "third_party/nonstd/optional.hpp"
-
-#include <string>
-
-class CacheFile
-{
-public:
- enum class Type { result, manifest, unknown };
-
- explicit CacheFile(const std::string& path);
-
- const Stat& lstat() const;
- const std::string& path() const;
- Type type() const;
-
-private:
- std::string m_path;
- mutable nonstd::optional<Stat> m_stat;
-};
-
-inline CacheFile::CacheFile(const std::string& path) : m_path(path)
-{
-}
-
-inline const std::string&
-CacheFile::path() const
-{
- return m_path;
-}
+++ /dev/null
-// Copyright (C) 2019 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 "system.hpp"
-
-#ifdef USE_XXH_DISPATCH
-# include "third_party/xxh_x86dispatch.h"
-#else
-# include "third_party/xxhash.h"
-#endif
-
-class Checksum
-{
-public:
- Checksum();
- ~Checksum();
-
- void reset();
- void update(const void* data, size_t length);
- uint64_t digest() const;
-
-private:
- XXH3_state_t* m_state;
-};
-
-inline Checksum::Checksum() : m_state(XXH3_createState())
-{
- reset();
-}
-
-inline Checksum::~Checksum()
-{
- XXH3_freeState(m_state);
-}
-
-inline void
-Checksum::reset()
-{
- XXH3_64bits_reset(m_state);
-}
-
-inline void
-Checksum::update(const void* data, size_t length)
-{
- XXH3_64bits_update(m_state, data, length);
-}
-
-inline uint64_t
-Checksum::digest() const
-{
- return XXH3_64bits_digest(m_state);
-}
+++ /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
-
-#include "Compression.hpp"
-
-#include "Config.hpp"
-#include "Context.hpp"
-#include "assertions.hpp"
-#include "exceptions.hpp"
-
-namespace Compression {
-
-int8_t
-level_from_config(const Config& config)
-{
- return config.compression() ? config.compression_level() : 0;
-}
-
-Type
-type_from_config(const Config& config)
-{
- return config.compression() ? Type::zstd : Type::none;
-}
-
-Type
-type_from_int(uint8_t type)
-{
- switch (type) {
- case static_cast<uint8_t>(Type::none):
- return Type::none;
-
- case static_cast<uint8_t>(Type::zstd):
- return Type::zstd;
- }
-
- throw Error("Unknown type: {}", type);
-}
-
-std::string
-type_to_string(Type type)
-{
- switch (type) {
- case Type::none:
- return "none";
-
- case Type::zstd:
- return "zstd";
- }
-
- ASSERT(false);
-}
-
-} // namespace Compression
+++ /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 "system.hpp"
-
-#include <string>
-
-class Config;
-
-namespace Compression {
-
-enum class Type : uint8_t {
- none = 0,
- zstd = 1,
-};
-
-int8_t level_from_config(const Config& config);
-
-Type type_from_config(const Config& config);
-
-Type type_from_int(uint8_t type);
-
-std::string type_to_string(Compression::Type type);
-
-} // namespace Compression
+++ /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
-
-#include "Compressor.hpp"
-
-#include "NullCompressor.hpp"
-#include "StdMakeUnique.hpp"
-#include "ZstdCompressor.hpp"
-#include "assertions.hpp"
-
-std::unique_ptr<Compressor>
-Compressor::create_from_type(Compression::Type type,
- FILE* stream,
- int8_t compression_level)
-{
- switch (type) {
- case Compression::Type::none:
- return std::make_unique<NullCompressor>(stream);
-
- case Compression::Type::zstd:
- return std::make_unique<ZstdCompressor>(stream, compression_level);
- }
-
- ASSERT(false);
-}
+++ /dev/null
-// Copyright (C) 2019 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 "system.hpp"
-
-#include "Compression.hpp"
-
-#include <memory>
-
-class Compressor
-{
-public:
- virtual ~Compressor() = default;
-
- // Create a compressor for the specified type.
- //
- // Parameters:
- // - type: The type.
- // - stream: The stream to write to.
- // - compression_level: Desired compression level.
- static std::unique_ptr<Compressor> create_from_type(Compression::Type type,
- FILE* stream,
- int8_t compression_level);
-
- // Get the actual compression level used for the compressed stream.
- virtual int8_t actual_compression_level() const = 0;
-
- // Write data from a buffer to the compressed stream.
- //
- // Parameters:
- // - data: Data to write.
- // - count: Size of data to write.
- //
- // Throws Error on failure.
- virtual void write(const void* data, size_t count) = 0;
-
- // Write an unsigned integer to the compressed stream.
- //
- // Parameters:
- // - value: Value to write.
- //
- // Throws Error on failure.
- template<typename T> void write(T value);
-
- // Finalize compression.
- //
- // This method checks that the end state of the compressed stream is correct
- // and throws Error if not.
- virtual void finalize() = 0;
-};
-// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Config.hpp"
#include "AtomicFile.hpp"
-#include "Compression.hpp"
-#include "Sloppiness.hpp"
+#include "MiniTrace.hpp"
#include "Util.hpp"
#include "assertions.hpp"
-#include "exceptions.hpp"
-#include "fmtmacros.hpp"
+
+#include <UmaskScope.hpp>
+#include <core/exceptions.hpp>
+#include <core/types.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+#include <util/Tokenizer.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+#include <util/path.hpp>
+#include <util/string.hpp>
#include "third_party/fmt/core.h"
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
#include <algorithm>
#include <cassert>
#include <fstream>
#include <unordered_map>
#include <vector>
-using nonstd::nullopt;
-using nonstd::optional;
+#ifndef environ
+DLLIMPORT extern char** environ;
+#endif
+
+// Make room for binary patching at install time.
+const char k_sysconfdir[4096 + 1] = SYSCONFDIR;
namespace {
log_file,
max_files,
max_size,
+ msvc_dep_prefix,
+ namespace_,
path,
pch_external_checksum,
prefix_command,
read_only,
read_only_direct,
recache,
+ remote_only,
+ remote_storage,
+ reshare,
run_second_cpp,
sloppiness,
stats,
+ stats_log,
temporary_dir,
umask,
};
-const std::unordered_map<std::string, ConfigItem> k_config_key_table = {
- {"absolute_paths_in_stderr", ConfigItem::absolute_paths_in_stderr},
- {"base_dir", ConfigItem::base_dir},
- {"cache_dir", ConfigItem::cache_dir},
- {"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},
- {"debug", ConfigItem::debug},
- {"debug_dir", ConfigItem::debug_dir},
- {"depend_mode", ConfigItem::depend_mode},
- {"direct_mode", ConfigItem::direct_mode},
- {"disable", ConfigItem::disable},
- {"extra_files_to_hash", ConfigItem::extra_files_to_hash},
- {"file_clone", ConfigItem::file_clone},
- {"hard_link", ConfigItem::hard_link},
- {"hash_dir", ConfigItem::hash_dir},
- {"ignore_headers_in_manifest", ConfigItem::ignore_headers_in_manifest},
- {"ignore_options", ConfigItem::ignore_options},
- {"inode_cache", ConfigItem::inode_cache},
- {"keep_comments_cpp", ConfigItem::keep_comments_cpp},
- {"limit_multiple", ConfigItem::limit_multiple},
- {"log_file", ConfigItem::log_file},
- {"max_files", ConfigItem::max_files},
- {"max_size", ConfigItem::max_size},
- {"path", ConfigItem::path},
- {"pch_external_checksum", ConfigItem::pch_external_checksum},
- {"prefix_command", ConfigItem::prefix_command},
- {"prefix_command_cpp", ConfigItem::prefix_command_cpp},
- {"read_only", ConfigItem::read_only},
- {"read_only_direct", ConfigItem::read_only_direct},
- {"recache", ConfigItem::recache},
- {"run_second_cpp", ConfigItem::run_second_cpp},
- {"sloppiness", ConfigItem::sloppiness},
- {"stats", ConfigItem::stats},
- {"temporary_dir", ConfigItem::temporary_dir},
- {"umask", ConfigItem::umask},
+enum class ConfigKeyType { normal, alias };
+
+struct ConfigKeyTableEntry
+{
+ ConfigItem item;
+ std::optional<std::string> alias = std::nullopt;
+};
+
+const std::unordered_map<std::string, ConfigKeyTableEntry> k_config_key_table =
+ {
+ {"absolute_paths_in_stderr", {ConfigItem::absolute_paths_in_stderr}},
+ {"base_dir", {ConfigItem::base_dir}},
+ {"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}},
+ {"debug", {ConfigItem::debug}},
+ {"debug_dir", {ConfigItem::debug_dir}},
+ {"depend_mode", {ConfigItem::depend_mode}},
+ {"direct_mode", {ConfigItem::direct_mode}},
+ {"disable", {ConfigItem::disable}},
+ {"extra_files_to_hash", {ConfigItem::extra_files_to_hash}},
+ {"file_clone", {ConfigItem::file_clone}},
+ {"hard_link", {ConfigItem::hard_link}},
+ {"hash_dir", {ConfigItem::hash_dir}},
+ {"ignore_headers_in_manifest", {ConfigItem::ignore_headers_in_manifest}},
+ {"ignore_options", {ConfigItem::ignore_options}},
+ {"inode_cache", {ConfigItem::inode_cache}},
+ {"keep_comments_cpp", {ConfigItem::keep_comments_cpp}},
+ {"limit_multiple", {ConfigItem::limit_multiple}},
+ {"log_file", {ConfigItem::log_file}},
+ {"max_files", {ConfigItem::max_files}},
+ {"max_size", {ConfigItem::max_size}},
+ {"msvc_dep_prefix", {ConfigItem::msvc_dep_prefix}},
+ {"namespace", {ConfigItem::namespace_}},
+ {"path", {ConfigItem::path}},
+ {"pch_external_checksum", {ConfigItem::pch_external_checksum}},
+ {"prefix_command", {ConfigItem::prefix_command}},
+ {"prefix_command_cpp", {ConfigItem::prefix_command_cpp}},
+ {"read_only", {ConfigItem::read_only}},
+ {"read_only_direct", {ConfigItem::read_only_direct}},
+ {"recache", {ConfigItem::recache}},
+ {"remote_only", {ConfigItem::remote_only}},
+ {"remote_storage", {ConfigItem::remote_storage}},
+ {"reshare", {ConfigItem::reshare}},
+ {"run_second_cpp", {ConfigItem::run_second_cpp}},
+ {"secondary_storage", {ConfigItem::remote_storage, "remote_storage"}},
+ {"sloppiness", {ConfigItem::sloppiness}},
+ {"stats", {ConfigItem::stats}},
+ {"stats_log", {ConfigItem::stats_log}},
+ {"temporary_dir", {ConfigItem::temporary_dir}},
+ {"umask", {ConfigItem::umask}},
};
const std::unordered_map<std::string, std::string> k_env_variable_table = {
{"LOGFILE", "log_file"},
{"MAXFILES", "max_files"},
{"MAXSIZE", "max_size"},
+ {"MSVC_DEP_PREFIX", "msvc_dep_prefix"},
+ {"NAMESPACE", "namespace"},
{"PATH", "path"},
{"PCH_EXTSUM", "pch_external_checksum"},
{"PREFIX", "prefix_command"},
{"READONLY", "read_only"},
{"READONLY_DIRECT", "read_only_direct"},
{"RECACHE", "recache"},
+ {"REMOTE_ONLY", "remote_only"},
+ {"REMOTE_STORAGE", "remote_storage"},
+ {"RESHARE", "reshare"},
+ {"SECONDARY_STORAGE", "remote_storage"}, // Alias for CCACHE_REMOTE_STORAGE
{"SLOPPINESS", "sloppiness"},
{"STATS", "stats"},
+ {"STATSLOG", "stats_log"},
{"TEMPDIR", "temporary_dir"},
{"UMASK", "umask"},
};
bool
parse_bool(const std::string& value,
- const optional<std::string> env_var_key,
+ const std::optional<std::string> env_var_key,
bool negate)
{
if (env_var_key) {
std::string lower_value = Util::to_lowercase(value);
if (value == "0" || lower_value == "false" || lower_value == "disable"
|| lower_value == "no") {
- throw Error(
- "invalid boolean environment variable value \"{}\" (did you mean to"
- " set \"CCACHE_{}{}=true\"?)",
- value,
- negate ? "" : "NO",
- *env_var_key);
+ throw core::Error(
+ FMT("invalid boolean environment variable value \"{}\" (did you mean to"
+ " set \"CCACHE_{}{}=true\"?)",
+ value,
+ negate ? "" : "NO",
+ *env_var_key));
}
return !negate;
} else if (value == "true") {
} else if (value == "false") {
return false;
} else {
- throw Error("not a boolean value: \"{}\"", value);
+ throw core::Error(FMT("not a boolean value: \"{}\"", value));
}
}
return value ? "true" : "false";
}
-double
-parse_double(const std::string& value)
-{
- size_t end;
- double result;
- try {
- result = std::stod(value, &end);
- } catch (std::exception& e) {
- throw Error(e.what());
- }
- if (end != value.size()) {
- throw Error("invalid floating point: \"{}\"", value);
- }
- return result;
-}
-
std::string
format_cache_size(uint64_t value)
{
{
if (value == "clang") {
return CompilerType::clang;
+ } else if (value == "clang-cl") {
+ return CompilerType::clang_cl;
} else if (value == "gcc") {
return CompilerType::gcc;
+ } else if (value == "icl") {
+ return CompilerType::icl;
+ } else if (value == "msvc") {
+ return CompilerType::msvc;
} 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
+core::Sloppiness
parse_sloppiness(const std::string& value)
{
- size_t start = 0;
- size_t end = 0;
- uint32_t result = 0;
- while (end != std::string::npos) {
- end = value.find_first_of(", ", start);
- std::string token =
- Util::strip_whitespace(value.substr(start, end - start));
- if (token == "file_stat_matches") {
- result |= SLOPPY_FILE_STAT_MATCHES;
+ core::Sloppiness result;
+
+ for (const auto token : util::Tokenizer(value, ", ")) {
+ if (token == "clang_index_store") {
+ result.enable(core::Sloppy::clang_index_store);
+ } else if (token == "file_stat_matches") {
+ result.enable(core::Sloppy::file_stat_matches);
} else if (token == "file_stat_matches_ctime") {
- result |= SLOPPY_FILE_STAT_MATCHES_CTIME;
+ result.enable(core::Sloppy::file_stat_matches_ctime);
+ } else if (token == "gcno_cwd") {
+ result.enable(core::Sloppy::gcno_cwd);
} else if (token == "include_file_ctime") {
- result |= SLOPPY_INCLUDE_FILE_CTIME;
+ result.enable(core::Sloppy::include_file_ctime);
} else if (token == "include_file_mtime") {
- result |= SLOPPY_INCLUDE_FILE_MTIME;
- } else if (token == "system_headers" || token == "no_system_headers") {
- result |= SLOPPY_SYSTEM_HEADERS;
- } else if (token == "pch_defines") {
- result |= SLOPPY_PCH_DEFINES;
- } else if (token == "time_macros") {
- result |= SLOPPY_TIME_MACROS;
- } else if (token == "clang_index_store") {
- result |= SLOPPY_CLANG_INDEX_STORE;
+ result.enable(core::Sloppy::include_file_mtime);
+ } else if (token == "ivfsoverlay") {
+ result.enable(core::Sloppy::ivfsoverlay);
} else if (token == "locale") {
- result |= SLOPPY_LOCALE;
+ result.enable(core::Sloppy::locale);
} else if (token == "modules") {
- result |= SLOPPY_MODULES;
- } else if (token == "ivfsoverlay") {
- result |= SLOPPY_IVFSOVERLAY;
+ result.enable(core::Sloppy::modules);
+ } else if (token == "pch_defines") {
+ result.enable(core::Sloppy::pch_defines);
+ } else if (token == "random_seed") {
+ result.enable(core::Sloppy::random_seed);
+ } else if (token == "system_headers" || token == "no_system_headers") {
+ result.enable(core::Sloppy::system_headers);
+ } else if (token == "time_macros") {
+ result.enable(core::Sloppy::time_macros);
} // else: ignore unknown value for forward compatibility
- start = value.find_first_not_of(", ", end);
}
+
return result;
}
std::string
-format_sloppiness(uint32_t sloppiness)
+format_sloppiness(core::Sloppiness sloppiness)
{
std::string result;
- if (sloppiness & SLOPPY_INCLUDE_FILE_MTIME) {
- result += "include_file_mtime, ";
- }
- if (sloppiness & SLOPPY_INCLUDE_FILE_CTIME) {
- result += "include_file_ctime, ";
- }
- if (sloppiness & SLOPPY_TIME_MACROS) {
- result += "time_macros, ";
- }
- if (sloppiness & SLOPPY_PCH_DEFINES) {
- result += "pch_defines, ";
+ if (sloppiness.is_enabled(core::Sloppy::clang_index_store)) {
+ result += "clang_index_store, ";
}
- if (sloppiness & SLOPPY_FILE_STAT_MATCHES) {
+ if (sloppiness.is_enabled(core::Sloppy::file_stat_matches)) {
result += "file_stat_matches, ";
}
- if (sloppiness & SLOPPY_FILE_STAT_MATCHES_CTIME) {
+ if (sloppiness.is_enabled(core::Sloppy::file_stat_matches_ctime)) {
result += "file_stat_matches_ctime, ";
}
- if (sloppiness & SLOPPY_SYSTEM_HEADERS) {
- result += "system_headers, ";
+ if (sloppiness.is_enabled(core::Sloppy::gcno_cwd)) {
+ result += "gcno_cwd, ";
}
- if (sloppiness & SLOPPY_CLANG_INDEX_STORE) {
- result += "clang_index_store, ";
+ if (sloppiness.is_enabled(core::Sloppy::include_file_ctime)) {
+ result += "include_file_ctime, ";
}
- if (sloppiness & SLOPPY_LOCALE) {
+ if (sloppiness.is_enabled(core::Sloppy::include_file_mtime)) {
+ result += "include_file_mtime, ";
+ }
+ if (sloppiness.is_enabled(core::Sloppy::ivfsoverlay)) {
+ result += "ivfsoverlay, ";
+ }
+ if (sloppiness.is_enabled(core::Sloppy::locale)) {
result += "locale, ";
}
- if (sloppiness & SLOPPY_MODULES) {
+ if (sloppiness.is_enabled(core::Sloppy::modules)) {
result += "modules, ";
}
- if (sloppiness & SLOPPY_IVFSOVERLAY) {
- result += "ivfsoverlay, ";
+ if (sloppiness.is_enabled(core::Sloppy::pch_defines)) {
+ result += "pch_defines, ";
+ }
+ if (sloppiness.is_enabled(core::Sloppy::random_seed)) {
+ result += "random_seed, ";
+ }
+ if (sloppiness.is_enabled(core::Sloppy::system_headers)) {
+ result += "system_headers, ";
+ }
+ if (sloppiness.is_enabled(core::Sloppy::time_macros)) {
+ result += "time_macros, ";
}
if (!result.empty()) {
// Strip last ", ".
return result;
}
-uint32_t
-parse_umask(const std::string& value)
-{
- if (value.empty()) {
- return std::numeric_limits<uint32_t>::max();
- }
-
- size_t end;
- uint32_t result = std::stoul(value, &end, 8);
- if (end != value.size()) {
- throw Error("not an octal integer: \"{}\"", value);
- }
- return result;
-}
-
std::string
-format_umask(uint32_t umask)
+format_umask(std::optional<mode_t> umask)
{
- if (umask == std::numeric_limits<uint32_t>::max()) {
- return {};
+ if (umask) {
+ return FMT("{:03o}", *umask);
} else {
- return FMT("{:03o}", umask);
+ return {};
}
}
void
verify_absolute_path(const std::string& value)
{
- if (!Util::is_absolute_path(value)) {
- throw Error("not an absolute path: \"{}\"", value);
+ if (!util::is_absolute_path(value)) {
+ throw core::Error(FMT("not an absolute path: \"{}\"", value));
}
}
std::string* value,
std::string* error_message)
{
- std::string stripped_line = Util::strip_whitespace(line);
+ std::string stripped_line = util::strip_whitespace(line);
if (stripped_line.empty() || stripped_line[0] == '#') {
return true;
}
}
*key = stripped_line.substr(0, equal_pos);
*value = stripped_line.substr(equal_pos + 1);
- *key = Util::strip_whitespace(*key);
- *value = Util::strip_whitespace(*value);
+ *key = util::strip_whitespace(*key);
+ *value = util::strip_whitespace(*value);
return true;
}
std::string value;
std::string error_message;
if (!parse_line(line, &key, &value, &error_message)) {
- throw Error(error_message);
+ throw core::Error(error_message);
}
config_line_handler(line, key, value);
- } catch (const Error& e) {
- throw Error("{}:{}: {}", path, line_number, e.what());
+ } catch (const core::Error& e) {
+ throw core::Error(FMT("{}:{}: {}", path, line_number, e.what()));
}
}
return true;
} // namespace
+#ifndef _WIN32
+static std::string
+default_cache_dir(const std::string& home_dir)
+{
+# ifdef __APPLE__
+ return home_dir + "/Library/Caches/ccache";
+# else
+ return home_dir + "/.cache/ccache";
+# endif
+}
+
+static std::string
+default_config_dir(const std::string& home_dir)
+{
+# ifdef __APPLE__
+ return home_dir + "/Library/Preferences/ccache";
+# else
+ return home_dir + "/.config/ccache";
+# endif
+}
+#endif
+
std::string
compiler_type_to_string(CompilerType compiler_type)
{
switch (compiler_type) {
case CompilerType::auto_guess:
return "auto";
+ case CompilerType::clang_cl:
+ return "clang-cl";
CASE(clang);
CASE(gcc);
+ CASE(icl);
+ CASE(msvc);
CASE(nvcc);
CASE(other);
- CASE(pump);
}
#undef CASE
ASSERT(false);
}
+void
+Config::read()
+{
+ const std::string home_dir = Util::get_home_directory();
+ const std::string legacy_ccache_dir = Util::make_path(home_dir, ".ccache");
+ const bool legacy_ccache_dir_exists =
+ Stat::stat(legacy_ccache_dir).is_directory();
+#ifdef _WIN32
+ const char* const env_appdata = getenv("APPDATA");
+ const char* const env_local_appdata = getenv("LOCALAPPDATA");
+#else
+ const char* const env_xdg_cache_home = getenv("XDG_CACHE_HOME");
+ const char* const env_xdg_config_home = getenv("XDG_CONFIG_HOME");
+#endif
+
+ const char* env_ccache_configpath = getenv("CCACHE_CONFIGPATH");
+ if (env_ccache_configpath) {
+ set_config_path(env_ccache_configpath);
+ } else {
+ // Only used for ccache tests:
+ const char* const env_ccache_configpath2 = getenv("CCACHE_CONFIGPATH2");
+
+ std::string sysconfdir = Util::make_path(k_sysconfdir);
+#ifdef _WIN32
+ if (const char* program_data = getenv("ALLUSERSPROFILE"))
+ sysconfdir = Util::make_path(program_data, "ccache");
+#endif
+
+ set_system_config_path(env_ccache_configpath2
+ ? env_ccache_configpath2
+ : Util::make_path(sysconfdir, "ccache.conf"));
+ MTR_BEGIN("config", "conf_read_system");
+ // A missing config file in SYSCONFDIR is OK so don't check return value.
+ update_from_file(system_config_path());
+ MTR_END("config", "conf_read_system");
+
+ const char* const env_ccache_dir = getenv("CCACHE_DIR");
+ std::string config_dir;
+ if (env_ccache_dir && *env_ccache_dir) {
+ config_dir = env_ccache_dir;
+ } else if (!cache_dir().empty() && !env_ccache_dir) {
+ config_dir = cache_dir();
+ } else if (legacy_ccache_dir_exists) {
+ config_dir = legacy_ccache_dir;
+#ifdef _WIN32
+ } else if (env_local_appdata
+ && Stat::stat(
+ Util::make_path(env_local_appdata, "ccache", "ccache.conf"))) {
+ config_dir = Util::make_path(env_local_appdata, "ccache");
+ } else if (env_appdata
+ && Stat::stat(
+ Util::make_path(env_appdata, "ccache", "ccache.conf"))) {
+ config_dir = Util::make_path(env_appdata, "ccache");
+ } else if (env_local_appdata) {
+ config_dir = Util::make_path(env_local_appdata, "ccache");
+ } else {
+ throw core::Fatal(
+ "could not find configuration file and the LOCALAPPDATA environment"
+ " variable is not set");
+ }
+#else
+ } else if (env_xdg_config_home) {
+ config_dir = Util::make_path(env_xdg_config_home, "ccache");
+ } else {
+ config_dir = default_config_dir(home_dir);
+ }
+#endif
+ set_config_path(Util::make_path(config_dir, "ccache.conf"));
+ }
+
+ const std::string& cache_dir_before_config_file_was_read = cache_dir();
+
+ MTR_BEGIN("config", "conf_read");
+ update_from_file(config_path());
+ MTR_END("config", "conf_read");
+
+ // Ignore cache_dir set in configuration file
+ set_cache_dir(cache_dir_before_config_file_was_read);
+
+ MTR_BEGIN("config", "conf_update_from_environment");
+ update_from_environment();
+ // (cache_dir is set above if CCACHE_DIR is set.)
+ MTR_END("config", "conf_update_from_environment");
+
+ if (cache_dir().empty()) {
+ if (legacy_ccache_dir_exists) {
+ set_cache_dir(legacy_ccache_dir);
+#ifdef _WIN32
+ } else if (env_local_appdata) {
+ set_cache_dir(Util::make_path(env_local_appdata, "ccache"));
+ } else {
+ throw core::Fatal(
+ "could not find cache directory and the LOCALAPPDATA environment"
+ " variable is not set");
+ }
+#else
+ } else if (env_xdg_cache_home) {
+ set_cache_dir(Util::make_path(env_xdg_cache_home, "ccache"));
+ } else {
+ set_cache_dir(default_cache_dir(home_dir));
+ }
+#endif
+ }
+ // else: cache_dir was set explicitly via environment or via system config.
+
+ // We have now determined config.cache_dir and populated the rest of config in
+ // prio order (1. environment, 2. cache-specific config, 3. system config).
+}
+
const std::string&
-Config::primary_config_path() const
+Config::config_path() const
{
- return m_primary_config_path;
+ return m_config_path;
}
const std::string&
-Config::secondary_config_path() const
+Config::system_config_path() const
{
- return m_secondary_config_path;
+ return m_system_config_path;
}
void
-Config::set_primary_config_path(std::string path)
+Config::set_config_path(std::string path)
{
- m_primary_config_path = std::move(path);
+ m_config_path = std::move(path);
}
void
-Config::set_secondary_config_path(std::string path)
+Config::set_system_config_path(std::string path)
{
- m_secondary_config_path = std::move(path);
+ m_system_config_path = std::move(path);
}
bool
Config::update_from_file(const std::string& path)
{
- return parse_config_file(path,
- [&](const std::string& /*line*/,
- const std::string& key,
- const std::string& value) {
- if (!key.empty()) {
- set_item(key, value, nullopt, false, path);
- }
- });
+ return parse_config_file(
+ path, [&](const auto& /*line*/, const auto& key, const auto& value) {
+ if (!key.empty()) {
+ this->set_item(key, value, std::nullopt, false, path);
+ }
+ });
}
void
for (char** env = environ; *env; ++env) {
std::string setting = *env;
const std::string prefix = "CCACHE_";
- if (!Util::starts_with(setting, prefix)) {
+ if (!util::starts_with(setting, prefix)) {
continue;
}
size_t equal_pos = setting.find('=');
std::string key = setting.substr(prefix.size(), equal_pos - prefix.size());
std::string value = setting.substr(equal_pos + 1);
- bool negate = Util::starts_with(key, "NO");
+ bool negate = util::starts_with(key, "NO");
if (negate) {
key = key.substr(2);
}
try {
set_item(config_key, value, key, negate, "environment");
- } catch (const Error& e) {
- throw Error("CCACHE_{}{}: {}", negate ? "NO" : "", key, e.what());
+ } catch (const core::Error& e) {
+ throw core::Error(
+ FMT("CCACHE_{}{}: {}", negate ? "NO" : "", key, e.what()));
}
}
}
{
auto it = k_config_key_table.find(key);
if (it == k_config_key_table.end()) {
- throw Error("unknown configuration option \"{}\"", key);
+ throw core::Error(FMT("unknown configuration option \"{}\"", key));
}
- switch (it->second) {
+ switch (it->second.item) {
case ConfigItem::absolute_paths_in_stderr:
return format_bool(m_absolute_paths_in_stderr);
case ConfigItem::max_size:
return format_cache_size(m_max_size);
+ case ConfigItem::msvc_dep_prefix:
+ return m_msvc_dep_prefix;
+
+ case ConfigItem::namespace_:
+ return m_namespace;
+
case ConfigItem::path:
return m_path;
case ConfigItem::recache:
return format_bool(m_recache);
+ case ConfigItem::remote_only:
+ return format_bool(m_remote_only);
+
+ case ConfigItem::remote_storage:
+ return m_remote_storage;
+
+ case ConfigItem::reshare:
+ return format_bool(m_reshare);
+
case ConfigItem::run_second_cpp:
return format_bool(m_run_second_cpp);
case ConfigItem::stats:
return format_bool(m_stats);
+ case ConfigItem::stats_log:
+ return m_stats_log;
+
case ConfigItem::temporary_dir:
return m_temporary_dir;
void
Config::set_value_in_file(const std::string& path,
const std::string& key,
- const std::string& value)
+ const std::string& value) const
{
+ UmaskScope umask_scope(m_umask);
+
if (k_config_key_table.find(key) == k_config_key_table.end()) {
- throw Error("unknown configuration option \"{}\"", key);
+ throw core::Error(FMT("unknown configuration option \"{}\"", key));
}
// Verify that the value is valid; set_item will throw if not.
Config dummy_config;
- dummy_config.set_item(key, value, nullopt, false, "");
+ dummy_config.set_item(key, value, std::nullopt, false, "");
const auto resolved_path = Util::real_path(path);
const auto st = Stat::stat(resolved_path);
if (!st) {
Util::ensure_dir_exists(Util::dir_name(resolved_path));
- try {
- Util::write_file(resolved_path, "");
- } catch (const Error& e) {
- throw Error("failed to write to {}: {}", resolved_path, e.what());
+ const auto result = util::write_file(resolved_path, "");
+ if (!result) {
+ throw core::Error(
+ FMT("failed to write to {}: {}", resolved_path, result.error()));
}
}
AtomicFile output(resolved_path, AtomicFile::Mode::text);
bool found = false;
- if (!parse_config_file(path,
- [&](const std::string& c_line,
- const std::string& c_key,
- const std::string& /*c_value*/) {
- if (c_key == key) {
- output.write(FMT("{} = {}\n", key, value));
- found = true;
- } else {
- output.write(FMT("{}\n", c_line));
- }
- })) {
- throw Error("failed to open {}: {}", path, strerror(errno));
+ if (!parse_config_file(
+ path,
+ [&](const auto& c_line, const auto& c_key, const auto& /*c_value*/) {
+ if (c_key == key) {
+ output.write(FMT("{} = {}\n", key, value));
+ found = true;
+ } else {
+ output.write(FMT("{}\n", c_line));
+ }
+ })) {
+ throw core::Error(FMT("failed to open {}: {}", path, strerror(errno)));
}
if (!found) {
std::vector<std::string> keys;
keys.reserve(k_config_key_table.size());
- for (const auto& item : k_config_key_table) {
- keys.emplace_back(item.first);
+ for (const auto& [key, entry] : k_config_key_table) {
+ if (!entry.alias) {
+ keys.emplace_back(key);
+ }
}
std::sort(keys.begin(), keys.end());
for (const auto& key : keys) {
void
Config::set_item(const std::string& key,
const std::string& value,
- const optional<std::string>& env_var_key,
+ const std::optional<std::string>& env_var_key,
bool negate,
const std::string& origin)
{
return;
}
- switch (it->second) {
+ switch (it->second.item) {
case ConfigItem::absolute_paths_in_stderr:
m_absolute_paths_in_stderr = parse_bool(value, env_var_key, negate);
break;
m_base_dir = Util::expand_environment_variables(value);
if (!m_base_dir.empty()) { // The empty string means "disable"
verify_absolute_path(m_base_dir);
- m_base_dir = Util::normalize_absolute_path(m_base_dir);
+ m_base_dir = Util::normalize_abstract_absolute_path(m_base_dir);
}
break;
m_compression = parse_bool(value, env_var_key, negate);
break;
- case ConfigItem::compression_level: {
- m_compression_level =
- Util::parse_signed(value, INT8_MIN, INT8_MAX, "compression_level");
+ case ConfigItem::compression_level:
+ m_compression_level = util::value_or_throw<core::Error>(
+ util::parse_signed(value, INT8_MIN, INT8_MAX, "compression_level"));
break;
- }
case ConfigItem::cpp_extension:
m_cpp_extension = value;
break;
case ConfigItem::limit_multiple:
- m_limit_multiple = Util::clamp(parse_double(value), 0.0, 1.0);
+ m_limit_multiple = std::clamp(
+ util::value_or_throw<core::Error>(util::parse_double(value)), 0.0, 1.0);
break;
case ConfigItem::log_file:
break;
case ConfigItem::max_files:
- m_max_files = Util::parse_unsigned(value, nullopt, nullopt, "max_files");
+ m_max_files = util::value_or_throw<core::Error>(
+ util::parse_unsigned(value, std::nullopt, std::nullopt, "max_files"));
break;
case ConfigItem::max_size:
m_max_size = Util::parse_size(value);
break;
+ case ConfigItem::msvc_dep_prefix:
+ m_msvc_dep_prefix = Util::expand_environment_variables(value);
+ break;
+
+ case ConfigItem::namespace_:
+ m_namespace = Util::expand_environment_variables(value);
+ break;
+
case ConfigItem::path:
m_path = Util::expand_environment_variables(value);
break;
m_recache = parse_bool(value, env_var_key, negate);
break;
+ case ConfigItem::remote_only:
+ m_remote_only = parse_bool(value, env_var_key, negate);
+ break;
+
+ case ConfigItem::remote_storage:
+ m_remote_storage = Util::expand_environment_variables(value);
+ break;
+
+ case ConfigItem::reshare:
+ m_reshare = parse_bool(value, env_var_key, negate);
+ break;
+
case ConfigItem::run_second_cpp:
m_run_second_cpp = parse_bool(value, env_var_key, negate);
break;
m_stats = parse_bool(value, env_var_key, negate);
break;
+ case ConfigItem::stats_log:
+ m_stats_log = Util::expand_environment_variables(value);
+ break;
+
case ConfigItem::temporary_dir:
m_temporary_dir = Util::expand_environment_variables(value);
m_temporary_dir_configured_explicitly = true;
break;
case ConfigItem::umask:
- m_umask = parse_umask(value);
+ if (!value.empty()) {
+ const auto umask = util::parse_umask(value);
+ if (!umask) {
+ throw core::Error(umask.error());
+ }
+ m_umask = *umask;
+ }
break;
}
- m_origins.emplace(key, origin);
+ const std::string canonical_key = it->second.alias ? *it->second.alias : key;
+ const auto& [element, inserted] = m_origins.emplace(canonical_key, origin);
+ if (!inserted) {
+ element->second = origin;
+ }
}
void
Config::check_key_tables_consistency()
{
- for (const auto& item : k_env_variable_table) {
- if (k_config_key_table.find(item.second) == k_config_key_table.end()) {
- throw Error(
- "env var {} mapped to {} which is missing from k_config_key_table",
- item.first,
- item.second);
+ for (const auto& [key, value] : k_env_variable_table) {
+ if (k_config_key_table.find(value) == k_config_key_table.end()) {
+ throw core::Error(
+ FMT("env var {} mapped to {} which is missing from k_config_key_table",
+ key,
+ value));
}
}
}
std::string
-Config::default_temporary_dir(const std::string& cache_dir)
+Config::default_temporary_dir() const
{
-#ifdef HAVE_GETEUID
- std::string user_tmp_dir = FMT("/run/user/{}", geteuid());
- if (Stat::stat(user_tmp_dir).is_directory()) {
- return user_tmp_dir + "/ccache-tmp";
- }
+ static const std::string run_user_tmp_dir = [] {
+#ifndef _WIN32
+ const char* const xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
+ if (xdg_runtime_dir && Stat::stat(xdg_runtime_dir).is_directory()) {
+ auto dir = FMT("{}/ccache-tmp", xdg_runtime_dir);
+ if (Util::create_dir(dir) && access(dir.c_str(), W_OK) == 0) {
+ return dir;
+ }
+ }
#endif
- return cache_dir + "/tmp";
+ return std::string();
+ }();
+ return !run_user_tmp_dir.empty() ? run_user_tmp_dir : m_cache_dir + "/tmp";
}
-// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include "NonCopyable.hpp"
-#include "Util.hpp"
-#include "third_party/nonstd/optional.hpp"
+#include <core/Sloppiness.hpp>
+
+#include <sys/types.h>
+#include <cstdint>
#include <functional>
#include <limits>
+#include <optional>
#include <string>
#include <unordered_map>
-enum class CompilerType { auto_guess, clang, gcc, nvcc, other, pump };
+enum class CompilerType {
+ auto_guess,
+ clang,
+ clang_cl,
+ gcc,
+ icl,
+ msvc,
+ nvcc,
+ other
+};
std::string compiler_type_to_string(CompilerType compiler_type);
-class Config
+class Config : NonCopyable
{
public:
Config() = default;
- Config(Config&) = default;
- Config& operator=(const Config&) = default;
+
+ void read();
bool absolute_paths_in_stderr() const;
const std::string& base_dir() const;
const std::string& log_file() const;
uint64_t max_files() const;
uint64_t max_size() const;
+ const std::string& msvc_dep_prefix() const;
const std::string& path() const;
bool pch_external_checksum() const;
const std::string& prefix_command() const;
bool read_only() const;
bool read_only_direct() const;
bool recache() const;
+ bool remote_only() const;
+ const std::string& remote_storage() const;
+ bool reshare() const;
bool run_second_cpp() const;
- uint32_t sloppiness() const;
+ core::Sloppiness sloppiness() const;
bool stats() const;
+ const std::string& stats_log() const;
+ const std::string& namespace_() const;
const std::string& temporary_dir() const;
- uint32_t umask() const;
+ std::optional<mode_t> umask() const;
+
+ // Return true for Clang and clang-cl.
+ bool is_compiler_group_clang() const;
+
+ // Return true for MSVC (cl.exe), clang-cl, and icl.
+ bool is_compiler_group_msvc() const;
+
+ std::string default_temporary_dir() const;
void set_base_dir(const std::string& value);
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 value);
- void set_depend_mode(bool value);
+ void set_cpp_extension(const std::string& value);
void set_debug(bool value);
+ void set_depend_mode(bool value);
void set_direct_mode(bool value);
+ void set_file_clone(bool value);
+ void set_hard_link(bool value);
void set_ignore_options(const std::string& value);
void set_inode_cache(bool value);
void set_max_files(uint64_t value);
void set_max_size(uint64_t value);
+ void set_msvc_dep_prefix(const std::string& value);
void set_run_second_cpp(bool value);
+ void set_temporary_dir(const std::string& value);
// Where to write configuration changes.
- const std::string& primary_config_path() const;
- // Secondary, read-only configuration file (if any).
- const std::string& secondary_config_path() const;
+ const std::string& config_path() const;
+ // System (read-only) configuration file (if any).
+ const std::string& system_config_path() const;
- void set_primary_config_path(std::string path);
- void set_secondary_config_path(std::string path);
+ void set_config_path(std::string path);
+ void set_system_config_path(std::string path);
using ItemVisitor = std::function<void(const std::string& key,
const std::string& value,
void visit_items(const ItemVisitor& item_visitor) const;
- static void set_value_in_file(const std::string& path,
- const std::string& key,
- const std::string& value);
+ void set_value_in_file(const std::string& path,
+ const std::string& key,
+ const std::string& value) const;
// Called from unit tests.
static void check_key_tables_consistency();
private:
- std::string m_primary_config_path;
- std::string m_secondary_config_path;
+ std::string m_config_path;
+ std::string m_system_config_path;
bool m_absolute_paths_in_stderr = false;
std::string m_base_dir;
bool m_hash_dir = true;
std::string m_ignore_headers_in_manifest;
std::string m_ignore_options;
- bool m_inode_cache = false;
+ bool m_inode_cache = true;
bool m_keep_comments_cpp = false;
double m_limit_multiple = 0.8;
std::string m_log_file;
uint64_t m_max_files = 0;
uint64_t m_max_size = 5ULL * 1000 * 1000 * 1000;
+ std::string m_msvc_dep_prefix = "Note: including file:";
std::string m_path;
bool m_pch_external_checksum = false;
std::string m_prefix_command;
bool m_read_only = false;
bool m_read_only_direct = false;
bool m_recache = false;
+ bool m_reshare = false;
bool m_run_second_cpp = true;
- uint32_t m_sloppiness = 0;
+ bool m_remote_only = false;
+ std::string m_remote_storage;
+ core::Sloppiness m_sloppiness;
bool m_stats = true;
+ std::string m_stats_log;
+ std::string m_namespace;
std::string m_temporary_dir;
- uint32_t m_umask = std::numeric_limits<uint32_t>::max(); // Don't set umask
+ std::optional<mode_t> m_umask;
bool m_temporary_dir_configured_explicitly = false;
void set_item(const std::string& key,
const std::string& value,
- const nonstd::optional<std::string>& env_var_key,
+ const std::optional<std::string>& env_var_key,
bool negate,
const std::string& origin);
-
- static std::string default_temporary_dir(const std::string& cache_dir);
};
inline bool
return m_compiler_type;
}
+inline bool
+Config::is_compiler_group_clang() const
+{
+ return m_compiler_type == CompilerType::clang
+ || m_compiler_type == CompilerType::clang_cl;
+}
+
+inline bool
+Config::is_compiler_group_msvc() const
+{
+ return m_compiler_type == CompilerType::msvc
+ || m_compiler_type == CompilerType::clang_cl
+ || m_compiler_type == CompilerType::icl;
+}
+
inline bool
Config::compression() const
{
return m_max_size;
}
+inline const std::string&
+Config::msvc_dep_prefix() const
+{
+ return m_msvc_dep_prefix;
+}
+
inline const std::string&
Config::path() const
{
return m_recache;
}
+inline bool
+Config::reshare() const
+{
+ return m_reshare;
+}
+
inline bool
Config::run_second_cpp() const
{
return m_run_second_cpp;
}
-inline uint32_t
+inline bool
+Config::remote_only() const
+{
+ return m_remote_only;
+}
+
+inline const std::string&
+Config::remote_storage() const
+{
+ return m_remote_storage;
+}
+
+inline core::Sloppiness
Config::sloppiness() const
{
return m_sloppiness;
return m_stats;
}
+inline const std::string&
+Config::stats_log() const
+{
+ return m_stats_log;
+}
+
+inline const std::string&
+Config::namespace_() const
+{
+ return m_namespace;
+}
+
inline const std::string&
Config::temporary_dir() const
{
return m_temporary_dir;
}
-inline uint32_t
+inline std::optional<mode_t>
Config::umask() const
{
return m_umask;
{
m_cache_dir = value;
if (!m_temporary_dir_configured_explicitly) {
- m_temporary_dir = default_temporary_dir(m_cache_dir);
+ m_temporary_dir = default_temporary_dir();
}
}
m_direct_mode = value;
}
+inline void
+Config::set_file_clone(const bool value)
+{
+ m_file_clone = value;
+}
+
+inline void
+Config::set_hard_link(const bool value)
+{
+ m_hard_link = value;
+}
+
inline void
Config::set_ignore_options(const std::string& value)
{
m_max_size = value;
}
+inline void
+Config::set_msvc_dep_prefix(const std::string& value)
+{
+ m_msvc_dep_prefix = value;
+}
+
inline void
Config::set_run_second_cpp(bool value)
{
m_run_second_cpp = value;
}
+
+inline void
+Config::set_temporary_dir(const std::string& value)
+{
+ m_temporary_dir = value;
+}
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Context.hpp"
-#include "Counters.hpp"
#include "Logging.hpp"
#include "SignalHandler.hpp"
#include "Util.hpp"
#include "hashutil.hpp"
+#include <Win32Util.hpp>
+#include <core/wincompat.hpp>
+#include <util/TimePoint.hpp>
+#include <util/path.hpp>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
#include <algorithm>
#include <string>
#include <vector>
-using nonstd::string_view;
-
Context::Context()
: actual_cwd(Util::get_actual_cwd()),
- apparent_cwd(Util::get_apparent_cwd(actual_cwd))
+ apparent_cwd(Util::get_apparent_cwd(actual_cwd)),
+ storage(config)
#ifdef INODE_CACHE_SUPPORTED
,
inode_cache(config)
#endif
{
+ time_of_invocation = util::TimePoint::now();
+}
+
+void
+Context::initialize()
+{
+ config.read();
+ Logging::init(config);
+
+ ignore_header_paths =
+ util::split_path_list(config.ignore_headers_in_manifest());
+ set_ignore_options(Util::split_into_strings(config.ignore_options(), " "));
+
+ // Set default umask for all files created by ccache from now on (if
+ // configured to). This is intentionally done after calling Logging::init so
+ // that the log file won't be affected by the umask but before creating the
+ // initial configuration file. The intention is that all files and directories
+ // in the cache directory should be affected by the configured umask and that
+ // no other files and directories should.
+ if (config.umask()) {
+ original_umask = Util::set_umask(*config.umask());
+ }
}
Context::~Context()
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include "Args.hpp"
#include "ArgsInfo.hpp"
#include "Config.hpp"
-#include "Counters.hpp"
#include "Digest.hpp"
#include "File.hpp"
#include "MiniTrace.hpp"
#include "NonCopyable.hpp"
-#include "Sloppiness.hpp"
#ifdef INODE_CACHE_SUPPORTED
# include "InodeCache.hpp"
#endif
-#include "third_party/nonstd/optional.hpp"
-#include "third_party/nonstd/string_view.hpp"
+#include <core/Manifest.hpp>
+#include <storage/Storage.hpp>
+#include <util/TimePoint.hpp>
+#include <ctime>
+#include <optional>
#include <string>
+#include <string_view>
#include <unordered_map>
#include <vector>
Context();
~Context();
+ // Read configuration, initialize logging, etc. Typically not called from unit
+ // tests.
+ void initialize();
+
ArgsInfo args_info;
Config config;
// The original argument list.
Args orig_args;
- // Name (represented as a hash) of the file containing the manifest for the
- // cached result.
- const nonstd::optional<Digest>& manifest_name() const;
-
- // Full path to the file containing the manifest (cachedir/a/b/cdef[...]M), if
- // any.
- const nonstd::optional<std::string>& manifest_path() const;
-
- // Name (represented as a hash) of the file containing the cached result.
- const nonstd::optional<Digest>& result_name() const;
-
- // Full path to the file containing the result (cachedir/a/b/cdef[...]R).
- const nonstd::optional<std::string>& result_path() const;
+ // Time of ccache invocation.
+ util::TimePoint time_of_invocation;
// Time of compilation. Used to see if include files have changed after
// compilation.
- time_t time_of_compilation = 0;
+ util::TimePoint time_of_compilation;
// Files included by the preprocessor and their hashes.
std::unordered_map<std::string, Digest> included_files;
- // Uses absolute path for some include files.
- bool has_absolute_include_headers = false;
-
// Have we tried and failed to get colored diagnostics?
bool diagnostics_color_failed = false;
// The name of the temporary preprocessed file.
std::string i_tmpfile;
- // The name of the cpp stderr file.
- std::string cpp_stderr;
-
- // The .gch/.pch/.pth file used for compilation.
- std::string included_pch_file;
+ // The preprocessor's stderr output.
+ util::Bytes cpp_stderr_data;
// Headers (or directories with headers) to ignore in manifest mode.
std::vector<std::string> ignore_header_paths;
+ // Storage (fronting local and remote storage backends).
+ storage::Storage storage;
+
+ // Direct mode manifest.
+ core::Manifest manifest;
+
#ifdef INODE_CACHE_SUPPORTED
// InodeCache that caches source file hashes when enabled.
mutable InodeCache inode_cache;
#endif
- // Statistics updates which get written into the statistics file belonging to
- // the result.
- Counters counter_updates;
-
- // Statistics updates which get written into the statistics file belonging to
- // the manifest.
- Counters manifest_counter_updates;
-
// PID of currently executing compiler that we have started, if any. 0 means
// no ongoing compilation.
pid_t compiler_pid = 0;
// Original umask before applying the `umask`/`CCACHE_UMASK` configuration, or
// `nullopt` if there is no such configuration.
- nonstd::optional<mode_t> original_umask;
+ std::optional<mode_t> original_umask;
#ifdef MTR_ENABLED
// Internal tracing.
std::unique_ptr<MiniTrace> mini_trace;
#endif
- void set_manifest_name(const Digest& name);
- void set_manifest_path(const std::string& path);
- void set_result_name(const Digest& name);
- void set_result_path(const std::string& path);
+ // Whether we have added "/showIncludes" ourselves since it's missing and
+ // depend mode is enabled.
+ bool auto_depend_mode = false;
// Register a temporary file to remove at program exit.
void register_pending_tmp_file(const std::string& path);
private:
- nonstd::optional<Digest> m_manifest_name;
- nonstd::optional<std::string> m_manifest_path;
-
- nonstd::optional<Digest> m_result_name;
- nonstd::optional<std::string> m_result_path;
-
// Options to ignore for the hash.
std::vector<std::string> m_ignore_options;
void unlink_pending_tmp_files_signal_safe(); // called from signal handler
};
-inline const nonstd::optional<Digest>&
-Context::manifest_name() const
-{
- return m_manifest_name;
-}
-
-inline const nonstd::optional<std::string>&
-Context::manifest_path() const
-{
- return m_manifest_path;
-}
-
-inline const nonstd::optional<Digest>&
-Context::result_name() const
-{
- return m_result_name;
-}
-
-inline const nonstd::optional<std::string>&
-Context::result_path() const
-{
- return m_result_path;
-}
-
inline const std::vector<std::string>&
Context::ignore_options() const
{
return m_ignore_options;
}
-
-inline void
-Context::set_manifest_name(const Digest& name)
-{
- m_manifest_name = name;
-}
-
-inline void
-Context::set_manifest_path(const std::string& path)
-{
- m_manifest_path = path;
-}
-
-inline void
-Context::set_result_name(const Digest& name)
-{
- m_result_name = name;
-}
-
-inline void
-Context::set_result_path(const std::string& path)
-{
- m_result_path = path;
-}
+++ /dev/null
-// Copyright (C) 2010-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 "Counters.hpp"
-
-#include "Statistic.hpp"
-#include "assertions.hpp"
-
-#include <algorithm>
-
-Counters::Counters() : m_counters(static_cast<size_t>(Statistic::END))
-{
-}
-
-uint64_t
-Counters::get(Statistic statistic) const
-{
- const auto index = static_cast<size_t>(statistic);
- ASSERT(index < static_cast<size_t>(Statistic::END));
- return index < m_counters.size() ? m_counters[index] : 0;
-}
-
-void
-Counters::set(Statistic statistic, uint64_t value)
-{
- const auto index = static_cast<size_t>(statistic);
- ASSERT(index < static_cast<size_t>(Statistic::END));
- m_counters[index] = value;
-}
-
-uint64_t
-Counters::get_raw(size_t index) const
-{
- ASSERT(index < size());
- return m_counters[index];
-}
-
-void
-Counters::set_raw(size_t index, uint64_t value)
-{
- if (index >= m_counters.size()) {
- m_counters.resize(index + 1);
- }
- m_counters[index] = value;
-}
-
-void
-Counters::increment(Statistic statistic, int64_t value)
-{
- const auto i = static_cast<size_t>(statistic);
- if (i >= m_counters.size()) {
- m_counters.resize(i + 1);
- }
- auto& counter = m_counters[i];
- counter =
- std::max(static_cast<int64_t>(0), static_cast<int64_t>(counter + value));
-}
-
-void
-Counters::increment(const Counters& other)
-{
- m_counters.resize(std::max(size(), other.size()));
- for (size_t i = 0; i < other.size(); ++i) {
- auto& counter = m_counters[i];
- counter = std::max(static_cast<int64_t>(0),
- static_cast<int64_t>(counter + other.m_counters[i]));
- }
-}
-
-size_t
-Counters::size() const
-{
- return m_counters.size();
-}
-
-bool
-Counters::all_zero() const
-{
- return !std::any_of(
- m_counters.begin(), m_counters.end(), [](unsigned v) { return v != 0; });
-}
+++ /dev/null
-// Copyright (C) 2010-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 "system.hpp"
-
-#include <vector>
-
-enum class Statistic;
-
-// A simple wrapper around a vector of integers used for the statistics
-// counters.
-class Counters
-{
-public:
- Counters();
-
- uint64_t get(Statistic statistic) const;
- void set(Statistic statistic, uint64_t value);
-
- uint64_t get_raw(size_t index) const;
- void set_raw(size_t index, uint64_t value);
-
- void increment(Statistic statistic, int64_t value = 1);
- void increment(const Counters& other);
-
- size_t size() const;
-
- // Return true if all counters are zero, false otherwise.
- bool all_zero() const;
-
-private:
- std::vector<uint64_t> m_counters;
-};
+++ /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
-
-#include "Decompressor.hpp"
-
-#include "NullDecompressor.hpp"
-#include "StdMakeUnique.hpp"
-#include "ZstdDecompressor.hpp"
-#include "assertions.hpp"
-
-std::unique_ptr<Decompressor>
-Decompressor::create_from_type(Compression::Type type, FILE* stream)
-{
- switch (type) {
- case Compression::Type::none:
- return std::make_unique<NullDecompressor>(stream);
-
- case Compression::Type::zstd:
- return std::make_unique<ZstdDecompressor>(stream);
- }
-
- ASSERT(false);
-}
+++ /dev/null
-// Copyright (C) 2019 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 "system.hpp"
-
-#include "Compression.hpp"
-
-#include <memory>
-
-class Decompressor
-{
-public:
- virtual ~Decompressor() = default;
-
- // Create a decompressor for the specified type.
- //
- // Parameters:
- // - type: The type.
- // - stream: The stream to read from.
- static std::unique_ptr<Decompressor> create_from_type(Compression::Type type,
- FILE* stream);
-
- // Read data into a buffer from the compressed stream.
- //
- // Parameters:
- // - data: Buffer to write decompressed data to.
- // - count: How many bytes to write.
- //
- // Throws Error on failure.
- virtual void read(void* data, size_t count) = 0;
-
- // Finalize decompression.
- //
- // This method checks that the end state of the compressed stream is correct
- // and throws Error if not.
- virtual void finalize() = 0;
-};
-// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Logging.hpp"
#include "assertions.hpp"
+#include <core/exceptions.hpp>
+#include <util/file.hpp>
+#include <util/path.hpp>
+
+#include <algorithm>
+
static inline bool
is_blank(const std::string& s)
{
namespace Depfile {
std::string
-escape_filename(nonstd::string_view filename)
+escape_filename(std::string_view filename)
{
std::string result;
result.reserve(filename.size());
return result;
}
-nonstd::optional<std::string>
-rewrite_paths(const Context& ctx, const std::string& file_content)
+std::optional<std::string>
+rewrite_source_paths(const Context& ctx, std::string_view 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;
+ return std::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")) {
+ bool seen_target_token = false;
+
+ using util::Tokenizer;
+ for (const auto line : Tokenizer(file_content,
+ "\n",
+ Tokenizer::Mode::include_empty,
+ Tokenizer::IncludeDelimiter::yes)) {
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
+ DEBUG_ASSERT(!line.empty()); // line.empty() -> no tokens
+ DEBUG_ASSERT(!tokens[i].empty());
+
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)) {
+ if (seen_target_token && 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);
} else {
adjusted_file_content.append(token.begin(), token.end());
}
+
+ if (tokens[i].back() == ':') {
+ seen_target_token = true;
+ }
}
- adjusted_file_content.push_back('\n');
}
if (content_rewritten) {
return adjusted_file_content;
} else {
- return nonstd::nullopt;
+ return std::nullopt;
}
}
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());
+ const auto file_content = util::read_file<std::string>(output_dep);
+ if (!file_content) {
+ LOG("Failed to read dependency file {}: {}",
+ output_dep,
+ file_content.error());
return;
}
- const auto new_content = rewrite_paths(ctx, file_content);
+ const auto new_content = rewrite_source_paths(ctx, *file_content);
if (new_content) {
- Util::write_file(output_dep, *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)
+tokenize(std::string_view file_content)
{
// A dependency file uses Makefile syntax. This is not perfect parser but
// should be enough for parsing a regular dependency file.
+ //
+ // Note that this is pretty complex because of Windows paths that can be
+ // identical to a target-colon-prerequisite without spaces (e.g. cat:/meow vs.
+ // c:/meow).
+ //
+ // Here are tests on Windows on how GNU Make 4.3 handles different scenarios:
+ //
+ // cat:/meow -> sees "cat" and "/meow"
+ // cat:\meow -> sees "cat" and "\meow"
+ // cat:\ meow -> sees "cat" and " meow"
+ // cat:c:/meow -> sees "cat" and "c:/meow"
+ // cat:c:\meow -> sees "cat" and "c:\meow"
+ // cat:c: -> target pattern contains no '%'. Stop.
+ // cat:c:\ -> target pattern contains no '%'. Stop.
+ // cat:c:/ -> sees "cat" and "c:/"
+ // cat:c:meow -> target pattern contains no '%'. Stop.
+ // c:c:/meow -> sees "c" and "c:/meow"
+ // c:c:\meow -> sees "c" and "c:\meow"
+ // c:z:\meow -> sees "c" and "z:\meow"
+ // c:cd:\meow -> target pattern contains no '%'. Stop.
+ //
+ // Thus, if there is a colon and the previous token is one character long and
+ // the following character is a slash (forward or backward), then it is
+ // interpreted as a Windows path.
std::vector<std::string> result;
const size_t length = file_content.size();
size_t p = 0;
while (p < length) {
- // Each token is separated by whitespace.
- if (isspace(file_content[p])) {
+ char c = file_content[p];
+
+ if (c == ':' && p + 1 < length && !is_blank(token) && token.length() == 1) {
+ const char next = file_content[p + 1];
+ if (next == '/' || next == '\\') {
+ // It's a Windows path, so the colon is not a separator and instead
+ // added to the token.
+ token.push_back(c);
+ ++p;
+ continue;
+ }
+ }
+
+ // Each token is separated by whitespace or a colon.
+ if (isspace(c) || c == ':') {
+ // Chomp all spaces before next character.
while (p < length && isspace(file_content[p])) {
++p;
}
if (!is_blank(token)) {
+ // If there were spaces between a token and the colon, add the colon the
+ // token to make sure it is seen as a target and not as a dependency.
+ if (p < length) {
+ const char next = file_content[p];
+ if (next == ':') {
+ token.push_back(next);
+ ++p;
+ // Chomp all spaces before next character.
+ while (p < length && isspace(file_content[p])) {
+ ++p;
+ }
+ }
+ }
result.push_back(token);
}
token.clear();
continue;
}
- char c = file_content[p];
switch (c) {
case '\\':
if (p + 1 < length) {
++p;
break;
// Backslash followed by newline is interpreted like a space, so simply
- // the backslash.
+ // discard the backslash.
case '\n':
++p;
continue;
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Digest.hpp"
-#include "third_party/nonstd/optional.hpp"
-#include "third_party/nonstd/string_view.hpp"
-
+#include <optional>
#include <string>
+#include <string_view>
#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);
+std::string escape_filename(std::string_view filename);
+
+std::optional<std::string> rewrite_source_paths(const Context& ctx,
+ std::string_view file_content);
+
void make_paths_relative_in_output_dep(const Context& ctx);
-std::vector<std::string> tokenize(nonstd::string_view file_content);
+
+// Tokenize `file_content` into a list of files, where the first token is the
+// target and ends with a colon.
+std::vector<std::string> tokenize(std::string_view file_content);
} // namespace Depfile
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include "Util.hpp"
#include "third_party/fmt/core.h"
+#include <cstdint>
#include <string>
// Digest represents the binary form of the final digest (AKA hash or checksum)
--- /dev/null
+// Copyright (C) 2021 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 "Fd.hpp"
+
+#include <core/wincompat.hpp>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+bool
+Fd::close()
+{
+ return m_fd != -1 && ::close(release()) == 0;
+}
#pragma once
-#include "system.hpp"
-
#include "NonCopyable.hpp"
#include "assertions.hpp"
return *this;
}
-inline bool
-Fd::close()
-{
- return m_fd != -1 && ::close(release()) == 0;
-}
-
inline int
Fd::release()
{
#pragma once
-#include "system.hpp"
-
#include "NonCopyable.hpp"
+#include <cstdio>
#include <string>
class File : public NonCopyable
{
public:
File() = default;
+ File(FILE* file);
File(const std::string& path, const char* mode);
File(File&& other) noexcept;
~File();
private:
FILE* m_file = nullptr;
+ bool m_owned = false;
};
+inline File::File(FILE* const file)
+{
+ m_file = file;
+ m_owned = false;
+}
+
inline File::File(const std::string& path, const char* mode)
{
open(path, mode);
}
-inline File::File(File&& other) noexcept : m_file(other.m_file)
+inline File::File(File&& other) noexcept
+ : m_file(other.m_file),
+ m_owned(other.m_owned)
{
other.m_file = nullptr;
+ other.m_owned = false;
}
inline File::~File()
File::operator=(File&& other) noexcept
{
m_file = other.m_file;
+ m_owned = other.m_owned;
other.m_file = nullptr;
+ other.m_owned = false;
return *this;
}
{
close();
m_file = fopen(path.c_str(), mode);
+ m_owned = true;
}
inline void
File::close()
{
- if (m_file) {
+ if (m_file && m_owned) {
fclose(m_file);
m_file = nullptr;
}
+ m_owned = false;
}
inline File::operator bool() const
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include <functional>
class Finalizer
+++ /dev/null
-// Copyright (C) 2019 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 "system.hpp"
-
-#include "third_party/fmt/core.h"
-#include "third_party/nonstd/string_view.hpp"
-
-// Specialization of fmt::formatter for nonstd::string_view.
-namespace fmt {
-
-template<> struct formatter<nonstd::string_view>
-{
- template<typename ParseContext>
- constexpr auto
- parse(ParseContext& ctx) const -> decltype(ctx.begin())
- {
- return ctx.begin();
- }
-
- template<typename FormatContext>
- auto
- format(const nonstd::string_view& sv, FormatContext& ctx)
- -> decltype(ctx.out())
- {
- return format_to(ctx.out(), "{}", fmt::string_view(sv.data(), sv.size()));
- }
-};
-
-} // namespace fmt
-// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Logging.hpp"
#include "fmtmacros.hpp"
-using nonstd::string_view;
+#include <core/wincompat.hpp>
+#include <util/file.hpp>
-const string_view HASH_DELIMITER("\000cCaChE\000", 8);
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+const std::string_view HASH_DELIMITER("\000cCaChE\000", 8);
Hash::Hash()
{
}
void
-Hash::enable_debug(string_view section_name,
+Hash::enable_debug(std::string_view section_name,
FILE* debug_binary,
FILE* debug_text)
{
}
Hash&
-Hash::hash_delimiter(string_view type)
+Hash::hash_delimiter(std::string_view type)
{
hash_buffer(HASH_DELIMITER);
hash_buffer(type);
- hash_buffer(string_view("", 1)); // NUL
+ hash_buffer(std::string_view("\x00", 1));
add_debug_text("### ");
add_debug_text(type);
add_debug_text("\n");
Hash&
Hash::hash(const void* data, size_t size, HashType hash_type)
{
- string_view buffer(static_cast<const char*>(data), size);
+ std::string_view buffer(static_cast<const char*>(data), size);
hash_buffer(buffer);
switch (hash_type) {
}
Hash&
-Hash::hash(string_view data)
+Hash::hash(std::string_view data)
{
hash(data.data(), data.length());
return *this;
Hash&
Hash::hash(int64_t x)
{
- hash_buffer(string_view(reinterpret_cast<const char*>(&x), sizeof(x)));
+ hash_buffer(std::string_view(reinterpret_cast<const char*>(&x), sizeof(x)));
add_debug_text(FMT("{}\n", x));
return *this;
}
-bool
+nonstd::expected<void, std::string>
Hash::hash_fd(int fd)
{
- return Util::read_fd(
+ return util::read_fd(
fd, [this](const void* data, size_t size) { hash(data, size); });
}
-bool
+nonstd::expected<void, std::string>
Hash::hash_file(const std::string& path)
{
Fd fd(open(path.c_str(), O_RDONLY | O_BINARY));
if (!fd) {
LOG("Failed to open {}: {}", path, strerror(errno));
- return false;
+ return nonstd::make_unexpected(strerror(errno));
}
- bool ret = hash_fd(*fd);
- return ret;
+ return hash_fd(*fd);
}
void
-Hash::hash_buffer(string_view buffer)
+Hash::hash_buffer(std::string_view buffer)
{
blake3_hasher_update(&m_hasher, buffer.data(), buffer.size());
if (!buffer.empty() && m_debug_binary) {
}
void
-Hash::add_debug_text(string_view text)
+Hash::add_debug_text(std::string_view text)
{
if (!text.empty() && m_debug_text) {
(void)fwrite(text.data(), 1, text.length(), m_debug_text);
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include "Digest.hpp"
#include "third_party/blake3/blake3.h"
-#include "third_party/nonstd/string_view.hpp"
+#include <third_party/nonstd/expected.hpp>
+
+#include <cstdint>
+#include <cstdio>
+#include <string_view>
// This class represents a hash state.
class Hash
Hash& operator=(const Hash& other) = default;
// Enable debug logging of the hashed input to a binary and a text file.
- void enable_debug(nonstd::string_view section_name,
+ void enable_debug(std::string_view section_name,
FILE* debug_binary,
FILE* debug_text);
// conditional hashing of information in a safe way (e.g., if we want to
// hash information X if CCACHE_A is set and information Y if CCACHE_B is
// set, there should never be a hash collision risk).
- Hash& hash_delimiter(nonstd::string_view type);
+ Hash& hash_delimiter(std::string_view type);
// Add bytes to the hash.
//
//
// If hash debugging is enabled, the string is written to the text input file
// followed by a newline.
- Hash& hash(nonstd::string_view data);
+ Hash& hash(std::string_view data);
// Add an integer to the hash.
//
// text input file followed by a newline.
Hash& hash(int64_t x);
- // Add contents read from an open file descriptor to the hash.
+ // Add file contents to the hash.
//
// If hash debugging is enabled, the data is written verbatim to the text
// input file.
- //
- // Returns true on success, otherwise false.
- bool hash_fd(int fd);
+ nonstd::expected<void, std::string> hash_file(const std::string& path);
- // Add file contents to the hash.
+ // Add contents read from an open file descriptor to the hash.
//
// If hash debugging is enabled, the data is written verbatim to the text
// input file.
- //
- // Returns true on success, otherwise false.
- bool hash_file(const std::string& path);
+ nonstd::expected<void, std::string> hash_fd(int fd);
+
+ // Add `text` to the text debug file.
+ void add_debug_text(std::string_view text);
private:
blake3_hasher m_hasher;
FILE* m_debug_binary = nullptr;
FILE* m_debug_text = nullptr;
- void hash_buffer(nonstd::string_view buffer);
- void add_debug_text(nonstd::string_view text);
+ void hash_buffer(std::string_view buffer);
};
-// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "InodeCache.hpp"
#include "Config.hpp"
+#include "Digest.hpp"
#include "Fd.hpp"
#include "Finalizer.hpp"
#include "Hash.hpp"
#include "Util.hpp"
#include "fmtmacros.hpp"
-#include <atomic>
+#include <util/TimePoint.hpp>
+
+#include <fcntl.h>
#include <libgen.h>
#include <sys/mman.h>
+#include <unistd.h>
+
+#ifdef HAVE_LINUX_FS_H
+# include <linux/magic.h>
+# include <sys/statfs.h>
+#elif defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
+# include <sys/mount.h>
+# include <sys/param.h>
+#endif
+
+#include <atomic>
#include <type_traits>
// The inode cache resides on a file that is mapped into shared memory by
static_assert(Digest::size() == 20,
"Increment version number if size of digest is changed.");
-static_assert(IS_TRIVIALLY_COPYABLE(Digest),
+static_assert(std::is_trivially_copyable<Digest>::value,
"Digest is expected to be trivially copyable.");
static_assert(
- static_cast<int>(InodeCache::ContentType::binary) == 0,
- "Numeric value is part of key, increment version number if changed.");
-static_assert(
- static_cast<int>(InodeCache::ContentType::code) == 1,
+ static_cast<int>(InodeCache::ContentType::raw) == 0,
"Numeric value is part of key, increment version number if changed.");
static_assert(
- static_cast<int>(InodeCache::ContentType::code_with_sloppy_time_macros) == 2,
- "Numeric value is part of key, increment version number if changed.");
-static_assert(
- static_cast<int>(InodeCache::ContentType::precompiled_header) == 3,
+ static_cast<int>(InodeCache::ContentType::checked_for_temporal_macros) == 1,
"Numeric value is part of key, increment version number if changed.");
+const void* MMAP_FAILED = reinterpret_cast<void*>(-1); // NOLINT: Must cast here
+
+bool
+fd_is_on_known_to_work_file_system(int fd)
+{
+ bool known_to_work = false;
+ struct statfs buf;
+ if (fstatfs(fd, &buf) != 0) {
+ LOG("fstatfs failed: {}", strerror(errno));
+ } else {
+#ifdef HAVE_LINUX_FS_H
+ // statfs's f_type field is a signed 32-bit integer on some platforms. Large
+ // values therefore cause narrowing warnings, so cast the value to a large
+ // unsigned type.
+ const auto f_type = static_cast<uintmax_t>(buf.f_type);
+ switch (f_type) {
+ // Is a filesystem you know works with the inode cache missing in this
+ // list? Please submit an issue or pull request to the ccache project.
+ case 0x9123683e: // BTRFS_SUPER_MAGIC
+ case 0xef53: // EXT2_SUPER_MAGIC
+ case 0x01021994: // TMPFS_MAGIC
+ case 0x58465342: // XFS_SUPER_MAGIC
+ known_to_work = true;
+ break;
+ default:
+ LOG("Filesystem type 0x{:x} not known to work for the inode cache",
+ f_type);
+ }
+#elif defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) // macOS X and some BSDs
+ static const std::vector<std::string> known_to_work_filesystems = {
+ // Is a filesystem you know works with the inode cache missing in this
+ // list? Please submit an issue or pull request to the ccache project.
+ "apfs",
+ "xfs",
+ };
+ if (std::find(known_to_work_filesystems.begin(),
+ known_to_work_filesystems.end(),
+ buf.f_fstypename)
+ != known_to_work_filesystems.end()) {
+ known_to_work = true;
+ } else {
+ LOG("Filesystem type {} not known to work for the inode cache",
+ buf.f_fstypename);
+ }
+#else
+# error Inconsistency: INODE_CACHE_SUPPORTED is set but we should not get here
+#endif
+ }
+ if (!known_to_work) {
+ LOG_RAW("Not using the inode cache");
+ }
+ return known_to_work;
+}
+
} // namespace
struct InodeCache::Key
dev_t st_dev;
ino_t st_ino;
mode_t st_mode;
-#ifdef HAVE_STRUCT_STAT_ST_MTIM
timespec st_mtim;
-#else
- time_t st_mtim;
-#endif
-#ifdef HAVE_STRUCT_STAT_ST_CTIM
timespec st_ctim; // Included for sanity checking.
-#else
- time_t st_ctim; // Included for sanity checking.
-#endif
- off_t st_size; // Included for sanity checking.
- bool sloppy_time_macros;
+ off_t st_size; // Included for sanity checking.
};
struct InodeCache::Entry
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(
- "Inode cache not supported because the cache file is located on nfs: {}",
- inode_cache_file);
+ if (!fd_is_on_known_to_work_file_system(*fd)) {
return false;
}
SharedRegion* sr = reinterpret_cast<SharedRegion*>(mmap(
nullptr, sizeof(SharedRegion), PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0));
fd.close();
- if (sr == reinterpret_cast<void*>(-1)) {
+ if (sr == MMAP_FAILED) {
LOG("Failed to mmap {}: {}", inode_cache_file, strerror(errno));
return false;
}
}
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;
}
return false;
}
+ // See comment for InodeCache::InodeCache why this check is done.
+ auto now = util::TimePoint::now();
+ if (now - stat.ctime() < m_min_age || now - stat.mtime() < m_min_age) {
+ LOG("Too new ctime or mtime of {}, not considering for inode cache", path);
+ return false;
+ }
+
Key key;
memset(&key, 0, sizeof(Key));
key.type = type;
key.st_dev = stat.device();
key.st_ino = stat.inode();
key.st_mode = stat.mode();
-#ifdef HAVE_STRUCT_STAT_ST_MTIM
- key.st_mtim = stat.mtim();
-#else
- key.st_mtim = stat.mtime();
-#endif
-#ifdef HAVE_STRUCT_STAT_ST_CTIM
- key.st_ctim = stat.ctim();
-#else
- key.st_ctim = stat.ctime();
-#endif
+ key.st_mtim = stat.mtime().to_timespec();
+ key.st_ctim = stat.ctime().to_timespec();
key.st_size = stat.size();
Hash hash;
bool
InodeCache::create_new_file(const std::string& filename)
{
- 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.
TemporaryFile tmp_file(filename);
Finalizer temp_file_remover([&] { unlink(tmp_file.path.c_str()); });
- bool is_nfs;
- if (Util::is_nfs_fd(*tmp_file.fd, &is_nfs) == 0 && is_nfs) {
- LOG(
- "Inode cache not supported because the cache file would be located on"
- " nfs: {}",
- filename);
+ if (!fd_is_on_known_to_work_file_system(*tmp_file.fd)) {
return false;
}
int err = Util::fallocate(*tmp_file.fd, sizeof(SharedRegion));
MAP_SHARED,
*tmp_file.fd,
0));
- if (sr == reinterpret_cast<void*>(-1)) {
+ if (sr == MMAP_FAILED) {
LOG("Failed to mmap new inode cache: {}", strerror(errno));
return false;
}
return false;
}
+ LOG("Created a new inode cache {}", filename);
return true;
}
return false;
}
-InodeCache::InodeCache(const Config& config) : m_config(config)
+InodeCache::InodeCache(const Config& config, util::Duration min_age)
+ : m_config(config),
+ // CCACHE_DISABLE_INODE_CACHE_MIN_AGE is only for testing purposes; see
+ // test/suites/inode_cache.bash.
+ m_min_age(getenv("CCACHE_DISABLE_INODE_CACHE_MIN_AGE") ? util::Duration(0)
+ : min_age)
{
}
InodeCache::~InodeCache()
{
if (m_sr) {
+ LOG("Accumulated stats for inode cache: hits={}, misses={}, errors={}",
+ m_sr->hits.load(),
+ m_sr->misses.load(),
+ m_sr->errors.load());
munmap(m_sr, sizeof(SharedRegion));
}
}
+bool
+InodeCache::available(int fd)
+{
+ return fd_is_on_known_to_work_file_system(fd);
+}
+
bool
InodeCache::get(const std::string& path,
ContentType type,
}
bool found = false;
- const bool success = with_bucket(key_digest, [&](Bucket* const bucket) {
+ const bool success = with_bucket(key_digest, [&](const auto bucket) {
for (uint32_t i = 0; i < k_num_entries; ++i) {
if (bucket->entries[i].key_digest == key_digest) {
if (i > 0) {
return false;
}
- LOG("inode cache {}: {}", found ? "hit" : "miss", path);
-
if (m_config.debug()) {
+ LOG("Inode cache {}: {}", found ? "hit" : "miss", path);
if (found) {
++m_sr->hits;
} else {
++m_sr->misses;
}
- LOG("Accumulated stats for inode cache: hits={}, misses={}, errors={}",
- m_sr->hits.load(),
- m_sr->misses.load(),
- m_sr->errors.load());
}
return found;
}
return false;
}
- const bool success = with_bucket(key_digest, [&](Bucket* const bucket) {
+ const bool success = with_bucket(key_digest, [&](const auto bucket) {
memmove(&bucket->entries[1],
&bucket->entries[0],
sizeof(Entry) * (k_num_entries - 1));
return false;
}
- LOG("inode cache insert: {}", path);
-
+ if (m_config.debug()) {
+ LOG("Inode cache insert: {}", path);
+ }
return true;
}
if (unlink(file.c_str()) != 0) {
return false;
}
+ LOG("Dropped inode cache {}", file);
if (m_sr) {
munmap(m_sr, sizeof(SharedRegion));
m_sr = nullptr;
std::string
InodeCache::get_file()
{
- return FMT("{}/inode-cache.v{}", m_config.temporary_dir(), k_version);
+ const uint8_t arch_bits = 8 * sizeof(void*);
+ return FMT(
+ "{}/inode-cache-{}.v{}", m_config.temporary_dir(), arch_bits, k_version);
}
int64_t
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
-#include "config.h"
+#include <util/Duration.hpp>
+#include <cstdint>
#include <functional>
#include <string>
class InodeCache
{
public:
- // Specifies in which role a file was hashed, since the hash result does not
- // only depend on the actual content but also what we used it for. Source code
- // files are scanned for macros while binary files are not as one example.
+ // Specifies in which mode a file was hashed since the hash result does not
+ // only depend on the actual content but also on operations that were
+ // performed that affect the return value. For example, source code files are
+ // normally scanned for macros while binary files are not.
enum class ContentType {
- binary = 0,
- code = 1,
- code_with_sloppy_time_macros = 2,
- precompiled_header = 3,
+ // The file was not scanned for temporal macros.
+ raw = 0,
+ // The file was checked for temporal macros (see check_for_temporal_macros
+ // in hashutil).
+ checked_for_temporal_macros = 1,
};
- InodeCache(const Config& config);
+ // `min_age` specifies how old a file must be to be put in the cache. The
+ // reason for this is that there is a race condition that consists of these
+ // events:
+ //
+ // 1. A file is written with content C1, size S and timestamp (ctime/mtime) T.
+ // 2. Ccache hashes the file content and asks the inode cache to store the
+ // digest with a hash of S and T (and some other data) as the key.
+ // 3. The file is quickly thereafter written with content C2 without changing
+ // size S and timestamp T. The timestamp is not updated since the file
+ // writes are made within a time interval smaller than the granularity of
+ // the clock used for file system timestamps. At the time of writing, a
+ // common granularity on a Linux system is 0.004 s (250 Hz).
+ // 4. The inode cache is asked for the file digest and the inode cache
+ // delivers a digest of C1 even though the file's content is C2.
+ //
+ // To avoid the race condition, the inode cache only caches inodes whose
+ // timestamp was updated more than `min_age` ago. The default value is a
+ // conservative 2 seconds since not all file systems have subsecond
+ // resolution.
+ InodeCache(const Config& config, util::Duration min_age = util::Duration(2));
~InodeCache();
+ // Return whether it's possible to use the inode cache on the filesystem
+ // associated with `fd`.
+ static bool available(int fd);
+
// Get saved hash digest and return value from a previous call to
- // hash_source_code_file().
+ // do_hash_file() in hashutil.cpp.
//
// Returns true if saved values could be retrieved from the cache, false
// otherwise.
Digest& file_digest,
int* return_value = nullptr);
- // Put hash digest and return value from a successful call to
- // hash_source_code_file().
+ // Put hash digest and return value from a successful call to do_hash_file()
+ // in hashutil.cpp.
//
// Returns true if values could be stored in the cache, false otherwise.
bool put(const std::string& path,
using BucketHandler = std::function<void(Bucket* bucket)>;
bool mmap_file(const std::string& inode_cache_file);
- static bool
- hash_inode(const std::string& path, ContentType type, Digest& digest);
+ bool hash_inode(const std::string& path, ContentType type, Digest& digest);
bool with_bucket(const Digest& key_digest,
const BucketHandler& bucket_handler);
static bool create_new_file(const std::string& filename);
bool initialize();
const Config& m_config;
+ util::Duration m_min_age;
struct SharedRegion* m_sr = nullptr;
bool m_failed = false;
};
+++ /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 "Lockfile.hpp"
-
-#include "Logging.hpp"
-#include "Util.hpp"
-#include "fmtmacros.hpp"
-
-#ifdef _WIN32
-# include "Win32Util.hpp"
-#endif
-
-#include "third_party/fmt/core.h"
-
-#include <algorithm>
-#include <sstream>
-#include <thread>
-
-namespace {
-
-#ifndef _WIN32
-
-bool
-do_acquire_posix(const std::string& lockfile, uint32_t staleness_limit)
-{
- const uint32_t max_to_sleep = 10000; // Microseconds.
- uint32_t to_sleep = 1000; // Microseconds.
- uint32_t slept = 0; // Microseconds.
- std::string initial_content;
-
- std::stringstream ss;
- ss << Util::get_hostname() << ':' << getpid() << ':'
- << std::this_thread::get_id();
- const auto content_prefix = ss.str();
-
- while (true) {
- auto my_content = FMT("{}:{}", content_prefix, time(nullptr));
-
- if (symlink(my_content.c_str(), lockfile.c_str()) == 0) {
- // We got the lock.
- return true;
- }
-
- int saved_errno = 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))) {
- // OK. Retry.
- continue;
- }
- }
-
- if (saved_errno == EPERM) {
- // The file system does not support symbolic links. We have no choice but
- // to grant the lock anyway.
- return true;
- }
-
- if (saved_errno != EEXIST) {
- // Directory doesn't exist or isn't writable?
- return false;
- }
-
- std::string content = Util::read_link(lockfile);
- if (content.empty()) {
- if (errno == ENOENT) {
- // The symlink was removed after the symlink() call above, so retry
- // acquiring it.
- continue;
- } else {
- 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",
- 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);
-
- if (initial_content.empty()) {
- initial_content = content;
- }
-
- if (slept <= staleness_limit) {
- 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);
- return false;
- } else {
- // The lock seems to be stale -- break it and try again.
- LOG("lockfile_acquire: breaking {}", lockfile);
- if (!Util::unlink_tmp(lockfile)) {
- LOG("Failed to unlink {}: {}", lockfile, strerror(errno));
- return false;
- }
- to_sleep = 1000;
- slept = 0;
- initial_content.clear();
- }
- }
-}
-
-#else // !_WIN32
-
-HANDLE
-do_acquire_win32(const std::string& lockfile, uint32_t staleness_limit)
-{
- unsigned to_sleep = 1000; // Microseconds.
- unsigned max_to_sleep = 10000; // Microseconds.
- unsigned slept = 0; // Microseconds.
- HANDLE handle;
-
- while (true) {
- DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE;
- handle = CreateFile(lockfile.c_str(),
- GENERIC_WRITE, // desired access
- 0, // shared mode (0 = not shared)
- nullptr, // security attributes
- CREATE_ALWAYS, // creation disposition
- flags, // flags and attributes
- nullptr // template file
- );
- if (handle != INVALID_HANDLE_VALUE) {
- break;
- }
-
- DWORD error = GetLastError();
- LOG("lockfile_acquire: CreateFile {}: {} ({})",
- lockfile,
- Win32Util::error_message(error),
- error);
- if (error == ERROR_PATH_NOT_FOUND) {
- // Directory doesn't exist?
- if (Util::create_dir(Util::dir_name(lockfile))) {
- // OK. Retry.
- continue;
- }
- }
-
- // ERROR_SHARING_VIOLATION: lock already held.
- // ERROR_ACCESS_DENIED: maybe pending delete.
- if (error != ERROR_SHARING_VIOLATION && error != ERROR_ACCESS_DENIED) {
- // Fatal error, give up.
- break;
- }
-
- if (slept > staleness_limit) {
- LOG("lockfile_acquire: gave up acquiring {}", lockfile);
- break;
- }
-
- 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);
- }
-
- return handle;
-}
-
-#endif // !_WIN32
-
-} // namespace
-
-Lockfile::Lockfile(const std::string& path, uint32_t staleness_limit)
- : m_lockfile(path + ".lock")
-{
-#ifndef _WIN32
- m_acquired = do_acquire_posix(m_lockfile, staleness_limit);
-#else
- m_handle = do_acquire_win32(m_lockfile, staleness_limit);
-#endif
- if (acquired()) {
- LOG("Acquired lock {}", m_lockfile);
- } else {
- LOG("Failed to acquire lock {}", m_lockfile);
- }
-}
-
-Lockfile::~Lockfile()
-{
- if (acquired()) {
- LOG("Releasing lock {}", m_lockfile);
-#ifndef _WIN32
- if (!Util::unlink_tmp(m_lockfile)) {
- LOG("Failed to unlink {}: {}", m_lockfile, strerror(errno));
- }
-#else
- CloseHandle(m_handle);
-#endif
- }
-}
+++ /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
-
-#pragma once
-
-#include "system.hpp"
-
-#include <string>
-
-class Lockfile
-{
-public:
- // Acquire a lock on `path`. Break the lock (or give up, depending on
- // implementation) after `staleness_limit` Microseconds.
- Lockfile(const std::string& path, uint32_t staleness_limit = 2000000);
-
- // Release the lock if acquired.
- ~Lockfile();
-
- // Return whether the lockfile was acquired successfully.
- bool acquired() const;
-
-private:
- std::string m_lockfile;
-#ifndef _WIN32
- bool m_acquired = false;
-#else
- HANDLE m_handle = nullptr;
-#endif
-};
-
-inline bool
-Lockfile::acquired() const
-{
-#ifndef _WIN32
- return m_acquired;
-#else
- return m_handle != INVALID_HANDLE_VALUE;
-#endif
-}
// Copyright (C) 2002 Andrew Tridgell
-// Copyright (C) 2009-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Config.hpp"
#include "File.hpp"
#include "Util.hpp"
-#include "exceptions.hpp"
+#include "Win32Util.hpp"
#include "execute.hpp"
#include "fmtmacros.hpp"
+#include <core/wincompat.hpp>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
#ifdef HAVE_SYSLOG_H
# include <syslog.h>
#endif
-#ifdef HAVE_SYS_TIME_H
-# include <sys/time.h>
-#endif
#ifdef __linux__
# ifdef HAVE_SYS_IOCTL_H
# endif
#endif
-#ifdef _WIN32
-# include <psapi.h>
-# include <sys/locking.h>
-# include <tchar.h>
-#endif
-
-using nonstd::string_view;
-
namespace {
// Logfile path and file handle, read from Config::log_file().
File logfile;
// Whether to use syslog() instead.
-bool use_syslog;
+bool use_syslog = false;
// Buffer used for logs in debug mode.
std::string debug_log_buffer;
print_fatal_error_and_exit()
{
// Note: Can't throw Fatal since that would lead to recursion.
- PRINT(stderr,
- "ccache: error: Failed to write to {}: {}\n",
- logfile_path,
- strerror(errno));
+ try {
+ PRINT(stderr,
+ "ccache: error: Failed to write to {}: {}\n",
+ logfile_path,
+ strerror(errno));
+ } catch (std::runtime_error&) {
+ // Ignore since we can't do anything about it.
+ }
exit(EXIT_FAILURE);
}
void
-do_log(string_view message, bool bulk)
+do_log(std::string_view message, bool bulk)
{
static char prefix[200];
if (!bulk) {
char timestamp[100];
- struct timeval tv;
- gettimeofday(&tv, nullptr);
- auto tm = Util::localtime(tv.tv_sec);
+ auto now = util::TimePoint::now();
+ auto tm = Util::localtime(now);
if (tm) {
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S", &*tm);
} else {
snprintf(timestamp,
sizeof(timestamp),
"%llu",
- static_cast<long long unsigned int>(tv.tv_sec));
+ static_cast<long long unsigned int>(now.sec()));
}
snprintf(prefix,
sizeof(prefix),
"[%s.%06d %-5d] ",
timestamp,
- static_cast<int>(tv.tv_usec),
+ static_cast<unsigned int>(now.nsec_decimal_part() / 1000),
static_cast<int>(getpid()));
}
}
void
-log(string_view message)
+log(std::string_view message)
{
if (!enabled()) {
return;
}
void
-bulk_log(string_view message)
+bulk_log(std::string_view message)
{
if (!enabled()) {
return;
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
-#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 <optional>
#include <string>
+#include <string_view>
#include <utility>
// Log a raw message (plus a newline character).
#define LOG_RAW(message_) \
do { \
if (Logging::enabled()) { \
- Logging::log(nonstd::string_view(message_)); \
+ Logging::log(std::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.
+// least one placeholder. `format` is checked at compile time.
#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.
+// is checked at compile time.
#define BULK_LOG(format_, ...) \
do { \
if (Logging::enabled()) { \
bool enabled();
// Log `message` (plus a newline character).
-void log(nonstd::string_view message);
+void log(std::string_view message);
// Log `message` (plus a newline character) without flushing and with a reused
// timestamp.
-void bulk_log(nonstd::string_view message);
+void bulk_log(std::string_view message);
// Write the current log memory buffer `path`.
void dump_log(const std::string& path);
+++ /dev/null
-// Copyright (C) 2009-2021 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 "Manifest.hpp"
-
-#include "AtomicFile.hpp"
-#include "CacheEntryReader.hpp"
-#include "CacheEntryWriter.hpp"
-#include "Checksum.hpp"
-#include "Config.hpp"
-#include "Context.hpp"
-#include "Digest.hpp"
-#include "File.hpp"
-#include "Hash.hpp"
-#include "Logging.hpp"
-#include "Sloppiness.hpp"
-#include "StdMakeUnique.hpp"
-#include "fmtmacros.hpp"
-#include "hashutil.hpp"
-
-// Manifest data format
-// ====================
-//
-// Integers are big-endian.
-//
-// <manifest> ::= <header> <body> <epilogue
-// <header> ::= <magic> <version> <compr_type> <compr_level>
-// <content_len>
-// <magic> ::= 4 bytes ("cCrS")
-// <version> ::= uint8_t
-// <compr_type> ::= <compr_none> | <compr_zstd>
-// <compr_none> ::= 0 (uint8_t)
-// <compr_zstd> ::= 1 (uint8_t)
-// <compr_level> ::= int8_t
-// <content_len> ::= uint64_t ; size of file if stored uncompressed
-// <body> ::= <paths> <includes> <results> ; body is potentially
-// ; compressed
-// <paths> ::= <n_paths> <path_entry>*
-// <n_paths> ::= uint32_t
-// <path_entry> ::= <path_len> <path>
-// <path_len> ::= uint16_t
-// <path> ::= path_len bytes
-// <includes> ::= <n_includes> <include_entry>*
-// <n_includes> ::= uint32_t
-// <include_entry> ::= <path_index> <digest> <fsize> <mtime> <ctime>
-// <path_index> ::= uint32_t
-// <digest> ::= Digest::size() bytes
-// <fsize> ::= uint64_t ; file size
-// <mtime> ::= int64_t ; modification time
-// <ctime> ::= int64_t ; status change time
-// <results> ::= <n_results> <result>*
-// <n_results> ::= uint32_t
-// <result> ::= <n_indexes> <include_index>* <name>
-// <n_indexes> ::= uint32_t
-// <include_index> ::= uint32_t
-// <name> ::= Digest::size() bytes
-// <epilogue> ::= <checksum>
-// <checksum> ::= uint64_t ; XXH3 of content bytes
-//
-// Sketch of concrete layout:
-
-// <magic> 4 bytes
-// <version> 1 byte
-// <compr_type> 1 byte
-// <compr_level> 1 byte
-// <content_len> 8 bytes
-// --- [potentially compressed from here] -------------------------------------
-// <n_paths> 4 bytes
-// <path_len> 2 bytes
-// <path> path_len bytes
-// ...
-// ----------------------------------------------------------------------------
-// <n_includes> 4 bytes
-// <path_index> 4 bytes
-// <digest> Digest::size() bytes
-// <fsize> 8 bytes
-// <mtime> 8 bytes
-// <ctime> 8 bytes
-// ...
-// ----------------------------------------------------------------------------
-// <n_results> 4 bytes
-// <n_indexes> 4 bytes
-// <include_index> 4 bytes
-// ...
-// <name> Digest::size() bytes
-// ...
-// checksum 8 bytes
-//
-//
-// Version history
-// ===============
-//
-// 1: Introduced in ccache 3.0. (Files are always compressed with gzip.)
-// 2: Introduced in ccache 4.0.
-
-using nonstd::nullopt;
-using nonstd::optional;
-
-const uint32_t k_max_manifest_entries = 100;
-const uint32_t k_max_manifest_file_info_entries = 10000;
-
-namespace {
-
-struct FileInfo
-{
- // Index to n_files.
- uint32_t index;
- // Digest of referenced file.
- Digest digest;
- // Size of referenced file.
- uint64_t fsize;
- // mtime of referenced file.
- int64_t mtime;
- // ctime of referenced file.
- int64_t ctime;
-};
-
-bool
-operator==(const FileInfo& lhs, const FileInfo& rhs)
-{
- return lhs.index == rhs.index && lhs.digest == rhs.digest
- && lhs.fsize == rhs.fsize && lhs.mtime == rhs.mtime
- && lhs.ctime == rhs.ctime;
-}
-
-} // namespace
-
-namespace std {
-
-template<> struct hash<FileInfo>
-{
- size_t
- operator()(const FileInfo& file_info) const
- {
- static_assert(sizeof(FileInfo) == 48, "unexpected size"); // No padding.
- Checksum checksum;
- checksum.update(&file_info, sizeof(file_info));
- return checksum.digest();
- }
-};
-
-} // namespace std
-
-namespace {
-
-struct ResultEntry
-{
- // Indexes to file_infos.
- std::vector<uint32_t> file_info_indexes;
-
- // Name of the result.
- 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.
- std::vector<std::string> files;
-
- // Information about referenced include files.
- std::vector<FileInfo> file_infos;
-
- // Result names plus references to include file infos.
- std::vector<ResultEntry> results;
-
- bool
- add_result_entry(
- const Digest& result_digest,
- const std::unordered_map<std::string, Digest>& included_files,
- time_t time_of_compilation,
- bool save_timestamp)
- {
- std::unordered_map<std::string, uint32_t /*index*/> mf_files;
- for (uint32_t i = 0; i < files.size(); ++i) {
- mf_files.emplace(files[i], i);
- }
-
- std::unordered_map<FileInfo, uint32_t /*index*/> mf_file_infos;
- for (uint32_t i = 0; i < file_infos.size(); ++i) {
- mf_file_infos.emplace(file_infos[i], i);
- }
-
- std::vector<uint32_t> file_info_indexes;
- file_info_indexes.reserve(included_files.size());
-
- for (const auto& item : included_files) {
- file_info_indexes.push_back(get_file_info_index(item.first,
- item.second,
- mf_files,
- mf_file_infos,
- time_of_compilation,
- save_timestamp));
- }
-
- 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:
- uint32_t
- get_file_info_index(
- const std::string& path,
- const Digest& digest,
- const std::unordered_map<std::string, uint32_t>& mf_files,
- const std::unordered_map<FileInfo, uint32_t>& mf_file_infos,
- time_t time_of_compilation,
- bool save_timestamp)
- {
- struct FileInfo fi;
-
- auto f_it = mf_files.find(path);
- if (f_it != mf_files.end()) {
- fi.index = f_it->second;
- } else {
- files.push_back(path);
- fi.index = files.size() - 1;
- }
-
- fi.digest = digest;
-
- // file_stat.{m,c}time() have a resolution of 1 second, so we can cache the
- // file's mtime and ctime only if they're at least one second older than
- // time_of_compilation.
- //
- // file_stat.ctime() may be 0, so we have to check time_of_compilation
- // against MAX(mtime, ctime).
- //
- // ccache only reads mtime/ctime if file_stat_match sloppiness is enabled,
- // so mtimes/ctimes are stored as a dummy value (-1) if not enabled. This
- // reduces the number of file_info entries for the common case.
-
- auto file_stat = Stat::stat(path, Stat::OnError::log);
- if (file_stat) {
- if (save_timestamp
- && time_of_compilation
- > std::max(file_stat.mtime(), file_stat.ctime())) {
- fi.mtime = file_stat.mtime();
- fi.ctime = file_stat.ctime();
- } else {
- fi.mtime = -1;
- fi.ctime = -1;
- }
- fi.fsize = file_stat.size();
- } else {
- fi.mtime = -1;
- fi.ctime = -1;
- fi.fsize = 0;
- }
-
- auto fi_it = mf_file_infos.find(fi);
- if (fi_it != mf_file_infos.end()) {
- return fi_it->second;
- } else {
- file_infos.push_back(fi);
- return file_infos.size() - 1;
- }
- }
-};
-
-struct FileStats
-{
- uint64_t size;
- int64_t mtime;
- int64_t ctime;
-};
-
-std::unique_ptr<ManifestData>
-read_manifest(const std::string& path, FILE* dump_stream = nullptr)
-{
- File file(path, "rb");
- if (!file) {
- return {};
- }
-
- CacheEntryReader reader(file.get(), Manifest::k_magic, Manifest::k_version);
-
- if (dump_stream) {
- reader.dump_header(dump_stream);
- }
-
- auto mf = std::make_unique<ManifestData>();
-
- uint32_t entry_count;
- reader.read(entry_count);
- for (uint32_t i = 0; i < entry_count; ++i) {
- mf->files.emplace_back();
- auto& entry = mf->files.back();
-
- uint16_t length;
- reader.read(length);
- entry.assign(length, 0);
- reader.read(&entry[0], length);
- }
-
- reader.read(entry_count);
- for (uint32_t i = 0; i < entry_count; ++i) {
- mf->file_infos.emplace_back();
- auto& entry = mf->file_infos.back();
-
- reader.read(entry.index);
- reader.read(entry.digest.bytes(), Digest::size());
- reader.read(entry.fsize);
- reader.read(entry.mtime);
- reader.read(entry.ctime);
- }
-
- reader.read(entry_count);
- for (uint32_t i = 0; i < entry_count; ++i) {
- mf->results.emplace_back();
- auto& entry = mf->results.back();
-
- uint32_t file_info_count;
- reader.read(file_info_count);
- for (uint32_t j = 0; j < file_info_count; ++j) {
- uint32_t file_info_index;
- reader.read(file_info_index);
- entry.file_info_indexes.push_back(file_info_index);
- }
- reader.read(entry.name.bytes(), Digest::size());
- }
-
- reader.finalize();
- return mf;
-}
-
-bool
-write_manifest(const Config& config,
- const std::string& path,
- const ManifestData& mf)
-{
- uint64_t payload_size = 0;
- payload_size += 4; // n_files
- for (const auto& file : mf.files) {
- payload_size += 2 + file.length();
- }
- payload_size += 4; // n_file_infos
- payload_size += mf.file_infos.size() * (4 + Digest::size() + 8 + 8 + 8);
- payload_size += 4; // n_results
- for (const auto& result : mf.results) {
- payload_size += 4; // n_file_info_indexes
- payload_size += result.file_info_indexes.size() * 4;
- payload_size += Digest::size();
- }
-
- AtomicFile atomic_manifest_file(path, AtomicFile::Mode::binary);
- CacheEntryWriter writer(atomic_manifest_file.stream(),
- Manifest::k_magic,
- Manifest::k_version,
- Compression::type_from_config(config),
- Compression::level_from_config(config),
- payload_size);
- writer.write<uint32_t>(mf.files.size());
- for (const auto& file : mf.files) {
- writer.write<uint16_t>(file.length());
- writer.write(file.data(), file.length());
- }
-
- writer.write<uint32_t>(mf.file_infos.size());
- for (const auto& file_info : mf.file_infos) {
- writer.write<uint32_t>(file_info.index);
- writer.write(file_info.digest.bytes(), Digest::size());
- writer.write(file_info.fsize);
- writer.write(file_info.mtime);
- writer.write(file_info.ctime);
- }
-
- writer.write<uint32_t>(mf.results.size());
- for (const auto& result : mf.results) {
- writer.write<uint32_t>(result.file_info_indexes.size());
- for (auto index : result.file_info_indexes) {
- writer.write(index);
- }
- writer.write(result.name.bytes(), Digest::size());
- }
-
- writer.finalize();
- atomic_manifest_file.commit();
- return true;
-}
-
-bool
-verify_result(const Context& ctx,
- const ManifestData& mf,
- const ResultEntry& result,
- std::unordered_map<std::string, FileStats>& stated_files,
- std::unordered_map<std::string, Digest>& hashed_files)
-{
- for (uint32_t file_info_index : result.file_info_indexes) {
- const auto& fi = mf.file_infos[file_info_index];
- const auto& path = mf.files[fi.index];
-
- auto stated_files_iter = stated_files.find(path);
- if (stated_files_iter == stated_files.end()) {
- auto file_stat = Stat::stat(path, Stat::OnError::log);
- if (!file_stat) {
- return false;
- }
- FileStats st;
- st.size = file_stat.size();
- st.mtime = file_stat.mtime();
- st.ctime = file_stat.ctime();
- stated_files_iter = stated_files.emplace(path, st).first;
- }
- const FileStats& fs = stated_files_iter->second;
-
- if (fi.fsize != fs.size) {
- return false;
- }
-
- // 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.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);
- 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);
- continue;
- } else {
- LOG("mtime/ctime miss for {}", path);
- }
- } else {
- if (fi.mtime == fs.mtime) {
- LOG("mtime hit for {}", path);
- continue;
- } else {
- LOG("mtime miss for {}", path);
- }
- }
- }
-
- auto hashed_files_iter = hashed_files.find(path);
- if (hashed_files_iter == hashed_files.end()) {
- Hash hash;
- int ret = hash_source_code_file(ctx, hash, path, fs.size);
- if (ret & HASH_SOURCE_CODE_ERROR) {
- LOG("Failed hashing {}", path);
- return false;
- }
- if (ret & HASH_SOURCE_CODE_FOUND_TIME) {
- return false;
- }
-
- Digest actual = hash.digest();
- hashed_files_iter = hashed_files.emplace(path, actual).first;
- }
-
- if (fi.digest != hashed_files_iter->second) {
- return false;
- }
- }
-
- return true;
-}
-
-} // namespace
-
-namespace Manifest {
-
-const std::string k_file_suffix = "M";
-const uint8_t k_magic[4] = {'c', 'C', 'm', 'F'};
-const uint8_t k_version = 2;
-
-// Try to get the result name from a manifest file. Returns nullopt on failure.
-optional<Digest>
-get(const Context& ctx, const std::string& path)
-{
- std::unique_ptr<ManifestData> mf;
- try {
- mf = read_manifest(path);
- if (mf) {
- // Update modification timestamp to save files from LRU cleanup.
- Util::update_mtime(path);
- } else {
- LOG_RAW("No such manifest file");
- return nullopt;
- }
- } catch (const Error& e) {
- LOG("Error: {}", e.what());
- return nullopt;
- }
-
- std::unordered_map<std::string, FileStats> stated_files;
- std::unordered_map<std::string, Digest> hashed_files;
-
- // Check newest result first since it's a bit more likely to match.
- for (uint32_t i = mf->results.size(); i > 0; i--) {
- if (verify_result(
- ctx, *mf, mf->results[i - 1], stated_files, hashed_files)) {
- return mf->results[i - 1].name;
- }
- }
-
- return nullopt;
-}
-
-// Put the result name into a manifest file given a set of included files.
-// Returns true on success, otherwise false.
-bool
-put(const Config& config,
- const std::string& path,
- const Digest& result_name,
- const std::unordered_map<std::string, Digest>& included_files,
-
- time_t time_of_compilation,
- bool save_timestamp)
-{
- // We don't bother to acquire a lock when writing the manifest to disk. A
- // race between two processes will only result in one lost entry, which is
- // not a big deal, and it's also very unlikely.
-
- std::unique_ptr<ManifestData> mf;
- try {
- mf = read_manifest(path);
- if (!mf) {
- // Manifest file didn't exist.
- mf = std::make_unique<ManifestData>();
- }
- } catch (const Error& e) {
- LOG("Error: {}", e.what());
- // Manifest file was corrupt, ignore.
- mf = std::make_unique<ManifestData>();
- }
-
- if (mf->results.size() > k_max_manifest_entries) {
- // Normally, there shouldn't be many result entries in the manifest since
- // new entries are added only if an include file has changed but not the
- // source file, and you typically change source files more often than
- // header files. However, it's certainly possible to imagine cases where
- // the manifest will grow large (for instance, a generated header file that
- // changes for every build), and this must be taken care of since
- // processing an ever growing manifest eventually will take too much time.
- // 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",
- 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",
- k_max_manifest_file_info_entries);
- mf = std::make_unique<ManifestData>();
- }
-
- bool added = mf->add_result_entry(
- result_name, included_files, time_of_compilation, save_timestamp);
-
- if (added) {
- try {
- write_manifest(config, path, *mf);
- return true;
- } catch (const Error& e) {
- LOG("Error: {}", e.what());
- }
- } else {
- LOG_RAW("The entry already exists in the manifest, not adding");
- }
- return false;
-}
-
-bool
-dump(const std::string& path, FILE* stream)
-{
- std::unique_ptr<ManifestData> mf;
- try {
- mf = read_manifest(path, stream);
- } catch (const Error& e) {
- PRINT(stream, "Error: {}\n", e.what());
- return false;
- }
-
- if (!mf) {
- PRINT(stream, "Error: No such file: {}\n", path);
- return false;
- }
-
- PRINT(stream, "File paths ({}):\n", mf->files.size());
- for (size_t i = 0; i < mf->files.size(); ++i) {
- PRINT(stream, " {}: {}\n", i, mf->files[i]);
- }
- PRINT(stream, "File infos ({}):\n", mf->file_infos.size());
- for (size_t i = 0; i < mf->file_infos.size(); ++i) {
- 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);
- }
- PRINT(stream, "Results ({}):\n", mf->results.size());
- for (size_t i = 0; i < mf->results.size(); ++i) {
- PRINT(stream, " {}:\n", i);
- PRINT_RAW(stream, " File info indexes:");
- for (uint32_t file_info_index : mf->results[i].file_info_indexes) {
- PRINT(stream, " {}", file_info_index);
- }
- PRINT_RAW(stream, "\n");
- PRINT(stream, " Name: {}\n", mf->results[i].name.to_string());
- }
-
- return true;
-}
-
-} // namespace Manifest
+++ /dev/null
-// Copyright (C) 2009-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 "system.hpp"
-
-#include "third_party/nonstd/optional.hpp"
-
-#include <string>
-#include <unordered_map>
-
-class Config;
-class Context;
-class Digest;
-
-namespace Manifest {
-
-extern const std::string k_file_suffix;
-extern const uint8_t k_magic[4];
-extern const uint8_t k_version;
-
-nonstd::optional<Digest> get(const Context& ctx, const std::string& path);
-bool put(const Config& config,
- const std::string& path,
- const Digest& result_name,
- const std::unordered_map<std::string, Digest>& included_files,
- time_t time_of_compilation,
- bool save_timestamp);
-bool dump(const std::string& path, FILE* stream);
-
-} // namespace Manifest
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-#include "system.hpp"
+#include "MiniTrace.hpp"
-#ifdef MTR_ENABLED
+#include "ArgsInfo.hpp"
+#include "TemporaryFile.hpp"
+#include "Util.hpp"
+#include "fmtmacros.hpp"
-# include "ArgsInfo.hpp"
-# include "MiniTrace.hpp"
-# include "TemporaryFile.hpp"
-# include "Util.hpp"
-# include "fmtmacros.hpp"
+#include <core/wincompat.hpp>
+#include <util/TimePoint.hpp>
-# ifdef HAVE_SYS_TIME_H
-# include <sys/time.h>
-# endif
+#include <limits.h> // NOLINT: PATH_MAX is defined in limits.h
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
namespace {
std::string
get_system_tmp_dir()
{
-# ifndef _WIN32
+#ifndef _WIN32
const char* tmpdir = getenv("TMPDIR");
if (tmpdir) {
return tmpdir;
}
-# else
+#else
static char dirbuf[PATH_MAX];
DWORD retval = GetTempPath(PATH_MAX, dirbuf);
if (retval > 0 && retval < PATH_MAX) {
return dirbuf;
}
-# endif
+#endif
return "/tmp";
}
-double
-time_seconds()
-{
-# ifdef HAVE_GETTIMEOFDAY
- struct timeval tv;
- gettimeofday(&tv, nullptr);
- return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
-# else
- return (double)time(nullptr);
-# endif
-}
-
} // namespace
MiniTrace::MiniTrace(const ArgsInfo& args_info)
m_tmp_trace_file = tmp_file.path;
mtr_init(m_tmp_trace_file.c_str());
- MTR_INSTANT_C("", "", "time", FMT("{:f}", time_seconds()).c_str());
+ auto now = util::TimePoint::now();
+ m_start_time = FMT("{}.{:06}", now.sec(), now.nsec_decimal_part() / 1000);
+ MTR_INSTANT_C("", "", "time", m_start_time.c_str());
MTR_META_PROCESS_NAME("ccache");
MTR_START("program", "ccache", m_trace_id);
}
}
Util::unlink_tmp(m_tmp_trace_file);
}
-
-#endif
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include "third_party/minitrace.h"
-#ifdef MTR_ENABLED
-
-# include <string>
+#include <string>
struct ArgsInfo;
const ArgsInfo& m_args_info;
const void* const m_trace_id;
std::string m_tmp_trace_file;
+ std::string m_start_time;
};
-
-#endif
+++ /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
-
-#include "NullCompressor.hpp"
-
-#include "exceptions.hpp"
-
-NullCompressor::NullCompressor(FILE* stream) : m_stream(stream)
-{
-}
-
-int8_t
-NullCompressor::actual_compression_level() const
-{
- return 0;
-}
-
-void
-NullCompressor::write(const void* data, size_t count)
-{
- if (fwrite(data, 1, count, m_stream) != count) {
- throw Error("failed to write to uncompressed stream");
- }
-}
-
-void
-NullCompressor::finalize()
-{
- if (fflush(m_stream) != 0) {
- throw Error("failed to finalize uncompressed stream");
- }
-}
+++ /dev/null
-// Copyright (C) 2019 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 "system.hpp"
-
-#include "Compressor.hpp"
-#include "NonCopyable.hpp"
-
-// A compressor of an uncompressed stream.
-class NullCompressor : public Compressor, NonCopyable
-{
-public:
- // Parameters:
- // - stream: The file to write data to.
- explicit NullCompressor(FILE* stream);
-
- int8_t actual_compression_level() const override;
- void write(const void* data, size_t count) override;
- void finalize() override;
-
-private:
- FILE* m_stream;
-};
+++ /dev/null
-// Copyright (C) 2019 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 "NullDecompressor.hpp"
-
-#include "exceptions.hpp"
-
-NullDecompressor::NullDecompressor(FILE* stream) : m_stream(stream)
-{
-}
-
-void
-NullDecompressor::read(void* data, size_t count)
-{
- if (fread(data, count, 1, m_stream) != 1) {
- throw Error("failed to read from uncompressed stream");
- }
-}
-
-void
-NullDecompressor::finalize()
-{
- if (fgetc(m_stream) != EOF) {
- throw Error("garbage data at end of uncompressed stream");
- }
-}
+++ /dev/null
-// Copyright (C) 2019 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 "system.hpp"
-
-#include "Decompressor.hpp"
-#include "NonCopyable.hpp"
-
-// A decompressor of an uncompressed stream.
-class NullDecompressor : public Decompressor, NonCopyable
-{
-public:
- // Parameters:
- // - stream: The file to read data from.
- explicit NullDecompressor(FILE* stream);
-
- void read(void* data, size_t count) override;
- void finalize() override;
-
-private:
- FILE* m_stream;
-};
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "fmtmacros.hpp"
+#include <core/wincompat.hpp>
+
#include "third_party/fmt/core.h"
-#ifndef _WIN32
+#ifdef _WIN32
+#else
# include <sys/ioctl.h>
#endif
# include <termios.h>
#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
#include <algorithm>
namespace {
-// Copyright (C) 2019 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
+#include <cstddef>
+#include <cstdint>
#include <string>
class ProgressBar
+++ /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
-
-#include "Result.hpp"
-
-#include "AtomicFile.hpp"
-#include "CacheEntryReader.hpp"
-#include "CacheEntryWriter.hpp"
-#include "Config.hpp"
-#include "Context.hpp"
-#include "Fd.hpp"
-#include "File.hpp"
-#include "Logging.hpp"
-#include "Stat.hpp"
-#include "Statistic.hpp"
-#include "Util.hpp"
-#include "exceptions.hpp"
-#include "fmtmacros.hpp"
-
-#include <algorithm>
-
-// Result data format
-// ==================
-//
-// Integers are big-endian.
-//
-// <result> ::= <header> <body> <epilogue>
-// <header> ::= <magic> <version> <compr_type> <compr_level>
-// <content_len>
-// <magic> ::= 4 bytes ("cCrS")
-// <version> ::= uint8_t
-// <compr_type> ::= <compr_none> | <compr_zstd>
-// <compr_none> ::= 0 (uint8_t)
-// <compr_zstd> ::= 1 (uint8_t)
-// <compr_level> ::= int8_t
-// <content_len> ::= uint64_t ; size of file if stored uncompressed
-// <body> ::= <n_entries> <entry>* ; potentially compressed
-// <n_entries> ::= uint8_t
-// <entry> ::= <embedded_file_entry> | <raw_file_entry>
-// <embedded_file_entry> ::= <embedded_file_marker> <suffix_len> <suffix>
-// <data_len> <data>
-// <embedded_file_marker> ::= 0 (uint8_t)
-// <embedded_file_type> ::= uint8_t
-// <data_len> ::= uint64_t
-// <data> ::= data_len bytes
-// <raw_file_entry> ::= <raw_file_marker> <suffix_len> <suffix> <file_len>
-// <raw_file_marker> ::= 1 (uint8_t)
-// <file_len> ::= uint64_t
-// <epilogue> ::= <checksum>
-// <checksum> ::= uint64_t ; XXH3 of content bytes
-//
-// Sketch of concrete layout:
-//
-// <magic> 4 bytes
-// <version> 1 byte
-// <compr_type> 1 byte
-// <compr_level> 1 byte
-// <content_len> 8 bytes
-// --- [potentially compressed from here] -------------------------------------
-// <n_entries> 1 byte
-// <embedded_file_marker> 1 byte
-// <embedded_file_type> 1 byte
-// <data_len> 8 bytes
-// <data> data_len bytes
-// ...
-// <ref_marker> 1 byte
-// <key_len> 1 byte
-// <key> key_len bytes
-// ...
-// checksum 8 bytes
-//
-//
-// Version history
-// ===============
-//
-// 1: Introduced in ccache 4.0.
-
-using nonstd::nullopt;
-using nonstd::optional;
-using nonstd::string_view;
-
-namespace {
-
-// File data stored inside the result file.
-const uint8_t k_embedded_file_marker = 0;
-
-// File stored as-is in the file system.
-const uint8_t k_raw_file_marker = 1;
-
-std::string
-get_raw_file_path(string_view result_path, uint32_t entry_number)
-{
- const auto prefix = result_path.substr(
- 0, result_path.length() - Result::k_file_suffix.length());
- return FMT("{}{}W", prefix, entry_number);
-}
-
-bool
-should_store_raw_file(const Config& config, Result::FileType type)
-{
- if (!config.file_clone() && !config.hard_link()) {
- return false;
- }
-
- // Only store object files as raw files since there are several problems with
- // storing other file types:
- //
- // 1. The compiler unlinks object files before writing to them but it doesn't
- // unlink .d files, so just it's possible to corrupt .d files just by
- // running the compiler (see ccache issue 599).
- // 2. .d files cause trouble for automake if hard-linked (see ccache issue
- // 378).
- // 3. It's unknown how the compiler treats other file types, so better safe
- // than sorry.
- //
- // It would be possible to store all files in raw form for the file_clone case
- // and only hard link object files. However, most likely it's only object
- // files that become large enough that it's of interest to clone or hard link
- // them, so we keep things simple for now. This will also save i-nodes in the
- // cache.
- return type == Result::FileType::object;
-}
-
-} // namespace
-
-namespace Result {
-
-const std::string k_file_suffix = "R";
-const uint8_t k_magic[4] = {'c', 'C', 'r', 'S'};
-const uint8_t k_version = 1;
-const char* const k_unknown_file_type = "<unknown type>";
-
-const char*
-file_type_to_string(FileType type)
-{
- switch (type) {
- case FileType::object:
- return ".o";
-
- case FileType::dependency:
- return ".d";
-
- case FileType::stderr_output:
- return "<stderr>";
-
- case FileType::coverage_unmangled:
- return ".gcno-unmangled";
-
- case FileType::stackusage:
- return ".su";
-
- case FileType::diagnostic:
- return ".dia";
-
- case FileType::dwarf_object:
- return ".dwo";
-
- case FileType::coverage_mangled:
- return ".gcno-mangled";
- }
-
- return k_unknown_file_type;
-}
-
-std::string
-gcno_file_in_mangled_form(const Context& ctx)
-{
- const auto& output_obj = ctx.args_info.output_obj;
- const std::string abs_output_obj =
- Util::is_absolute_path(output_obj)
- ? 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");
-}
-
-std::string
-gcno_file_in_unmangled_form(const Context& ctx)
-{
- return Util::change_extension(ctx.args_info.output_obj, ".gcno");
-}
-
-Result::Reader::Reader(const std::string& result_path)
- : m_result_path(result_path)
-{
-}
-
-optional<std::string>
-Result::Reader::read(Consumer& consumer)
-{
- LOG("Reading result {}", m_result_path);
-
- try {
- if (read_result(consumer)) {
- return nullopt;
- } else {
- return "No such result file";
- }
- } catch (const Error& e) {
- return e.what();
- }
-}
-
-bool
-Reader::read_result(Consumer& consumer)
-{
- File file(m_result_path, "rb");
- if (!file) {
- // Cache miss.
- return false;
- }
-
- CacheEntryReader cache_entry_reader(file.get(), k_magic, k_version);
-
- consumer.on_header(cache_entry_reader);
-
- uint8_t n_entries;
- cache_entry_reader.read(n_entries);
-
- uint32_t i;
- for (i = 0; i < n_entries; ++i) {
- read_entry(cache_entry_reader, i, consumer);
- }
-
- if (i != n_entries) {
- throw Error("Too few entries (read {}, expected {})", i, n_entries);
- }
-
- cache_entry_reader.finalize();
- return true;
-}
-
-void
-Reader::read_entry(CacheEntryReader& cache_entry_reader,
- uint32_t entry_number,
- Reader::Consumer& consumer)
-{
- uint8_t marker;
- cache_entry_reader.read(marker);
-
- switch (marker) {
- case k_embedded_file_marker:
- case k_raw_file_marker:
- break;
-
- default:
- throw Error("Unknown entry type: {}", marker);
- }
-
- UnderlyingFileTypeInt type;
- cache_entry_reader.read(type);
- FileType file_type = FileType(type);
-
- uint64_t file_len;
- cache_entry_reader.read(file_len);
-
- if (marker == k_embedded_file_marker) {
- consumer.on_entry_start(entry_number, file_type, file_len, nullopt);
-
- uint8_t buf[READ_BUFFER_SIZE];
- size_t remain = file_len;
- while (remain > 0) {
- size_t n = std::min(remain, sizeof(buf));
- cache_entry_reader.read(buf, n);
- consumer.on_entry_data(buf, n);
- remain -= n;
- }
- } else {
- ASSERT(marker == k_raw_file_marker);
-
- auto raw_path = get_raw_file_path(m_result_path, entry_number);
- auto st = Stat::stat(raw_path, Stat::OnError::throw_error);
- if (st.size() != file_len) {
- throw Error("Bad file size of {} (actual {} bytes, expected {} bytes)",
- raw_path,
- st.size(),
- file_len);
- }
-
- consumer.on_entry_start(entry_number, file_type, file_len, raw_path);
- }
-
- consumer.on_entry_end();
-}
-
-Writer::Writer(Context& ctx, const std::string& result_path)
- : m_ctx(ctx),
- m_result_path(result_path)
-{
-}
-
-void
-Writer::write(FileType file_type, const std::string& file_path)
-{
- m_entries_to_write.emplace_back(file_type, file_path);
-}
-
-optional<std::string>
-Writer::finalize()
-{
- try {
- do_finalize();
- return nullopt;
- } catch (const Error& e) {
- return e.what();
- }
-}
-
-void
-Writer::do_finalize()
-{
- uint64_t payload_size = 0;
- payload_size += 1; // n_entries
- for (const auto& pair : m_entries_to_write) {
- const auto& path = pair.second;
- auto st = Stat::stat(path, Stat::OnError::throw_error);
-
- payload_size += 1; // embedded_file_marker
- payload_size += 1; // embedded_file_type
- payload_size += 8; // data_len
- payload_size += st.size(); // data
- }
-
- AtomicFile atomic_result_file(m_result_path, AtomicFile::Mode::binary);
- CacheEntryWriter writer(atomic_result_file.stream(),
- k_magic,
- k_version,
- Compression::type_from_config(m_ctx.config),
- Compression::level_from_config(m_ctx.config),
- payload_size);
-
- writer.write<uint8_t>(m_entries_to_write.size());
-
- uint32_t entry_number = 0;
- for (const auto& pair : m_entries_to_write) {
- const auto file_type = pair.first;
- const auto& path = pair.second;
- 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 {}",
- store_raw ? "raw" : "embedded",
- entry_number,
- file_type_to_string(file_type),
- file_size,
- path);
-
- writer.write<uint8_t>(store_raw ? k_raw_file_marker
- : k_embedded_file_marker);
- writer.write(UnderlyingFileTypeInt(file_type));
- writer.write(file_size);
-
- if (store_raw) {
- write_raw_file_entry(path, entry_number);
- } else {
- write_embedded_file_entry(writer, path, file_size);
- }
-
- ++entry_number;
- }
-
- writer.finalize();
- atomic_result_file.commit();
-}
-
-void
-Result::Writer::write_embedded_file_entry(CacheEntryWriter& writer,
- const std::string& path,
- uint64_t file_size)
-{
- Fd file(open(path.c_str(), O_RDONLY | O_BINARY));
- if (!file) {
- throw Error("Failed to open {} for reading", path);
- }
-
- uint64_t remain = file_size;
- while (remain > 0) {
- uint8_t buf[READ_BUFFER_SIZE];
- size_t n = std::min(remain, static_cast<uint64_t>(sizeof(buf)));
- ssize_t bytes_read = read(*file, buf, n);
- if (bytes_read == -1) {
- if (errno == EINTR) {
- continue;
- }
- throw Error("Error reading from {}: {}", path, strerror(errno));
- }
- if (bytes_read == 0) {
- throw Error("Error reading from {}: end of file", path);
- }
- writer.write(buf, bytes_read);
- remain -= bytes_read;
- }
-}
-
-void
-Result::Writer::write_raw_file_entry(const std::string& path,
- uint32_t entry_number)
-{
- const auto raw_file = get_raw_file_path(m_result_path, entry_number);
- const auto old_stat = Stat::stat(raw_file);
- try {
- Util::clone_hard_link_or_copy_file(m_ctx, path, raw_file, true);
- } catch (Error& e) {
- throw Error(
- "Failed to store {} as raw file {}: {}", path, raw_file, e.what());
- }
- const auto new_stat = Stat::stat(raw_file);
- m_ctx.counter_updates.increment(
- Statistic::cache_size_kibibyte,
- Util::size_change_kibibyte(old_stat, new_stat));
- m_ctx.counter_updates.increment(Statistic::files_in_cache,
- (new_stat ? 1 : 0) - (old_stat ? 1 : 0));
-}
-
-} // namespace Result
+++ /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 "system.hpp"
-
-#include "third_party/nonstd/optional.hpp"
-
-#include <map>
-#include <string>
-#include <vector>
-
-class CacheEntryReader;
-class CacheEntryWriter;
-class Context;
-
-namespace Result {
-
-extern const std::string k_file_suffix;
-extern const uint8_t k_magic[4];
-extern const uint8_t k_version;
-
-extern const char* const k_unknown_file_type;
-
-using UnderlyingFileTypeInt = uint8_t;
-enum class FileType : UnderlyingFileTypeInt {
- // These values are written into the cache result file. This means they must
- // never be changed or removed unless the result file version is incremented.
- // Adding new values is OK.
-
- // The main output specified with -o or implicitly from the input filename.
- object = 0,
-
- // Dependency file specified with -MF or implicitly from the output filename.
- dependency = 1,
-
- // Text sent to standard output.
- stderr_output = 2,
-
- // Coverage notes file generated by -ftest-coverage with filename in unmangled
- // form, i.e. output file but with a .gcno extension.
- coverage_unmangled = 3,
-
- // Stack usage file generated by -fstack-usage, i.e. output file but with a
- // .su extension.
- stackusage = 4,
-
- // Diagnostics output file specified by --serialize-diagnostics.
- diagnostic = 5,
-
- // DWARF object file geenrated by -gsplit-dwarf, i.e. output file but with a
- // .dwo extension.
- dwarf_object = 6,
-
- // Coverage notes file generated by -ftest-coverage with filename in mangled
- // form, i.e. full output file path but with a .gcno extension and with
- // slashes replaced with hashes.
- coverage_mangled = 7,
-};
-
-const char* file_type_to_string(FileType type);
-
-std::string gcno_file_in_mangled_form(const Context& ctx);
-std::string gcno_file_in_unmangled_form(const Context& ctx);
-
-// This class knows how to read a result cache entry.
-class Reader
-{
-public:
- Reader(const std::string& result_path);
-
- class Consumer
- {
- public:
- virtual ~Consumer() = default;
-
- virtual void on_header(CacheEntryReader& cache_entry_reader) = 0;
- virtual void on_entry_start(uint32_t entry_number,
- FileType file_type,
- uint64_t file_len,
- nonstd::optional<std::string> raw_file) = 0;
- virtual void on_entry_data(const uint8_t* data, size_t size) = 0;
- virtual void on_entry_end() = 0;
- };
-
- // Returns error message on error, otherwise nonstd::nullopt.
- nonstd::optional<std::string> read(Consumer& consumer);
-
-private:
- const std::string m_result_path;
-
- bool read_result(Consumer& consumer);
- void read_entry(CacheEntryReader& cache_entry_reader,
- uint32_t entry_number,
- Reader::Consumer& consumer);
-};
-
-// This class knows how to write a result cache entry.
-class Writer
-{
-public:
- Writer(Context& ctx, const std::string& result_path);
-
- // Register a file to include in the result. Does not throw.
- void write(FileType file_type, const std::string& file_path);
-
- // Write registered files to the result. Returns an error message on error.
- nonstd::optional<std::string> finalize();
-
-private:
- Context& m_ctx;
- const std::string m_result_path;
- std::vector<std::pair<FileType, std::string>> m_entries_to_write;
-
- void do_finalize();
- static void write_embedded_file_entry(CacheEntryWriter& writer,
- const std::string& path,
- uint64_t file_size);
- void write_raw_file_entry(const std::string& path, uint32_t entry_number);
-};
-
-} // namespace Result
+++ /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 "ResultDumper.hpp"
-
-#include "CacheEntryReader.hpp"
-#include "Context.hpp"
-#include "Logging.hpp"
-#include "fmtmacros.hpp"
-
-using nonstd::optional;
-
-ResultDumper::ResultDumper(FILE* stream) : m_stream(stream)
-{
-}
-
-void
-ResultDumper::on_header(CacheEntryReader& cache_entry_reader)
-{
- cache_entry_reader.dump_header(m_stream);
-}
-
-void
-ResultDumper::on_entry_start(uint32_t entry_number,
- Result::FileType file_type,
- uint64_t file_len,
- optional<std::string> raw_file)
-{
- PRINT(m_stream,
- "{} file #{}: {} ({} bytes)\n",
- raw_file ? "Raw" : "Embedded",
- entry_number,
- Result::file_type_to_string(file_type),
- file_len);
-}
-
-void
-ResultDumper::on_entry_data(const uint8_t* /*data*/, size_t /*size*/)
-{
-}
-
-void
-ResultDumper::on_entry_end()
-{
-}
+++ /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
-
-#pragma once
-
-#include "system.hpp"
-
-#include "Result.hpp"
-
-// This class dumps information about the result entry to `stream`.
-class ResultDumper : public Result::Reader::Consumer
-{
-public:
- ResultDumper(FILE* stream);
-
- void on_header(CacheEntryReader& cache_entry_reader) override;
- void on_entry_start(uint32_t entry_number,
- Result::FileType file_type,
- uint64_t file_len,
- nonstd::optional<std::string> raw_file) override;
- void on_entry_data(const uint8_t* data, size_t size) override;
- void on_entry_end() override;
-
-private:
- FILE* m_stream;
-};
+++ /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 "ResultExtractor.hpp"
-
-#include "Util.hpp"
-#include "fmtmacros.hpp"
-
-ResultExtractor::ResultExtractor(const std::string& directory)
- : m_directory(directory)
-{
-}
-
-void
-ResultExtractor::on_header(CacheEntryReader& /*cache_entry_reader*/)
-{
-}
-
-void
-ResultExtractor::on_entry_start(uint32_t /*entry_number*/,
- Result::FileType file_type,
- uint64_t /*file_len*/,
- nonstd::optional<std::string> raw_file)
-{
- std::string suffix = Result::file_type_to_string(file_type);
- if (suffix == Result::k_unknown_file_type) {
- suffix = FMT(".type_{}", file_type);
- } else if (suffix[0] == '<') {
- suffix[0] = '.';
- suffix.resize(suffix.length() - 1);
- }
-
- m_dest_path = FMT("{}/ccache-result{}", m_directory, suffix);
-
- if (!raw_file) {
- m_dest_fd = Fd(
- open(m_dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666));
- if (!m_dest_fd) {
- throw Error(
- "Failed to open {} for writing: {}", m_dest_path, strerror(errno));
- }
- } else {
- try {
- Util::copy_file(*raw_file, m_dest_path, false);
- } catch (Error& e) {
- throw Error(
- "Failed to copy {} to {}: {}", *raw_file, m_dest_path, e.what());
- }
- }
-}
-
-void
-ResultExtractor::on_entry_data(const uint8_t* data, size_t size)
-{
- ASSERT(m_dest_fd);
-
- try {
- Util::write_fd(*m_dest_fd, data, size);
- } catch (Error& e) {
- throw Error("Failed to write to {}: {}", m_dest_path, e.what());
- }
-}
-
-void
-ResultExtractor::on_entry_end()
-{
- if (m_dest_fd) {
- m_dest_fd.close();
- }
-}
+++ /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
-
-#pragma once
-
-#include "system.hpp"
-
-#include "Fd.hpp"
-#include "Result.hpp"
-
-class Context;
-
-// This class extracts the parts of a result entry to a directory.
-class ResultExtractor : public Result::Reader::Consumer
-{
-public:
- ResultExtractor(const std::string& directory);
-
- void on_header(CacheEntryReader& cache_entry_reader) override;
- void on_entry_start(uint32_t entry_number,
- Result::FileType file_type,
- uint64_t file_len,
- nonstd::optional<std::string> raw_file) override;
- void on_entry_data(const uint8_t* data, size_t size) override;
- void on_entry_end() override;
-
-private:
- const std::string m_directory;
- Fd m_dest_fd;
- std::string m_dest_path;
-};
+++ /dev/null
-// Copyright (C) 2020-2021 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 "ResultRetriever.hpp"
-
-#include "Context.hpp"
-#include "Depfile.hpp"
-#include "Logging.hpp"
-
-using Result::FileType;
-
-ResultRetriever::ResultRetriever(Context& ctx, bool rewrite_dependency_target)
- : m_ctx(ctx),
- m_rewrite_dependency_target(rewrite_dependency_target)
-{
-}
-
-void
-ResultRetriever::on_header(CacheEntryReader& /*cache_entry_reader*/)
-{
-}
-
-void
-ResultRetriever::on_entry_start(uint32_t entry_number,
- FileType file_type,
- uint64_t file_len,
- nonstd::optional<std::string> raw_file)
-{
- LOG("Reading {} entry #{} {} ({} bytes)",
- raw_file ? "raw" : "embedded",
- entry_number,
- Result::file_type_to_string(file_type),
- file_len);
-
- std::string dest_path;
- m_dest_file_type = file_type;
-
- switch (file_type) {
- case FileType::object:
- dest_path = m_ctx.args_info.output_obj;
- break;
-
- case FileType::dependency:
- // Dependency file: Open destination file but accumulate data in m_dest_data
- // and write it in on_entry_end.
- if (m_ctx.args_info.generating_dependencies) {
- dest_path = m_ctx.args_info.output_dep;
- m_dest_data.reserve(file_len);
- }
- break;
-
- case FileType::stderr_output:
- // Stderr data: Don't open a destination file. Instead accumulate it in
- // m_dest_data and write it in on_entry_end.
- m_dest_data.reserve(file_len);
- break;
-
- case FileType::coverage_unmangled:
- if (m_ctx.args_info.generating_coverage) {
- dest_path = Util::change_extension(m_ctx.args_info.output_obj, ".gcno");
- }
- break;
-
- case FileType::stackusage:
- if (m_ctx.args_info.generating_stackusage) {
- dest_path = m_ctx.args_info.output_su;
- }
- break;
-
- case FileType::diagnostic:
- if (m_ctx.args_info.generating_diagnostics) {
- dest_path = m_ctx.args_info.output_dia;
- }
- break;
-
- case FileType::dwarf_object:
- if (m_ctx.args_info.seen_split_dwarf
- && m_ctx.args_info.output_obj != "/dev/null") {
- dest_path = m_ctx.args_info.output_dwo;
- }
- break;
-
- case FileType::coverage_mangled:
- if (m_ctx.args_info.generating_coverage) {
- dest_path = Result::gcno_file_in_mangled_form(m_ctx);
- }
- break;
- }
-
- if (file_type == FileType::stderr_output) {
- // Written in on_entry_end.
- } else if (dest_path.empty()) {
- LOG_RAW("Not writing");
- } else if (dest_path == "/dev/null") {
- LOG_RAW("Not writing to /dev/null");
- } else if (raw_file) {
- Util::clone_hard_link_or_copy_file(m_ctx, *raw_file, dest_path, false);
-
- // Update modification timestamp to save the file from LRU cleanup (and, if
- // hard-linked, to make the object file newer than the source file).
- Util::update_mtime(*raw_file);
- } else {
- LOG("Writing 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) {
- throw Error(
- "Failed to open {} for writing: {}", dest_path, strerror(errno));
- }
- m_dest_path = dest_path;
- }
-}
-
-void
-ResultRetriever::on_entry_data(const uint8_t* data, size_t size)
-{
- ASSERT(!(m_dest_file_type == FileType::stderr_output && m_dest_fd));
-
- if (m_dest_file_type == FileType::stderr_output
- || (m_dest_file_type == FileType::dependency && !m_dest_path.empty())) {
- m_dest_data.append(reinterpret_cast<const char*>(data), size);
- } else if (m_dest_fd) {
- try {
- Util::write_fd(*m_dest_fd, data, size);
- } catch (Error& e) {
- throw Error("Failed to write to {}: {}", m_dest_path, e.what());
- }
- }
-}
-
-void
-ResultRetriever::on_entry_end()
-{
- if (m_dest_file_type == FileType::stderr_output) {
- LOG("Writing to file descriptor {}", STDERR_FILENO);
- Util::send_to_stderr(m_ctx, m_dest_data);
- } else if (m_dest_file_type == FileType::dependency && !m_dest_path.empty()) {
- write_dependency_file();
- }
-
- if (m_dest_fd) {
- m_dest_fd.close();
- }
- m_dest_path.clear();
- m_dest_data.clear();
-}
-
-void
-ResultRetriever::write_dependency_file()
-{
- try {
- size_t start_pos = 0;
- if (m_rewrite_dependency_target) {
- size_t colon_pos = m_dest_data.find(':');
- if (colon_pos != std::string::npos) {
- 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;
- }
- }
-
- Util::write_fd(*m_dest_fd,
- m_dest_data.data() + start_pos,
- m_dest_data.length() - start_pos);
- } catch (Error& e) {
- throw Error("Failed to write to {}: {}", m_dest_path, e.what());
- }
-}
+++ /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
-
-#pragma once
-
-#include "system.hpp"
-
-#include "Fd.hpp"
-#include "Result.hpp"
-
-class Context;
-
-// This class retrieves a result entry to the local file system.
-class ResultRetriever : public Result::Reader::Consumer
-{
-public:
- ResultRetriever(Context& ctx, bool rewrite_dependency_target);
-
- void on_header(CacheEntryReader& cache_entry_reader) override;
- void on_entry_start(uint32_t entry_number,
- Result::FileType file_type,
- uint64_t file_len,
- nonstd::optional<std::string> raw_file) override;
- void on_entry_data(const uint8_t* data, size_t size) override;
- void on_entry_end() override;
-
-private:
- Context& m_ctx;
- Result::FileType m_dest_file_type;
- Fd m_dest_fd;
- std::string m_dest_path;
-
- // Collects the full data of stderr output (since we want to potentially strip
- // color codes which could span chunk boundaries) or dependency data (since we
- // potentially want to rewrite the dependency target which in theory can span
- // a chunk boundary).
- std::string m_dest_data;
-
- // Whether to rewrite the first part of the dependency file data to the
- // destination object file.
- const bool m_rewrite_dependency_target;
-
- void write_dependency_file();
-};
# include "assertions.hpp"
# include <signal.h> // NOLINT: sigaddset et al are defined in signal.h
+# include <sys/types.h>
+# include <sys/wait.h>
+# include <unistd.h>
namespace {
# ifdef SIGQUIT
register_signal_handler(SIGQUIT);
# endif
+
+ signal(SIGPIPE, SIG_IGN); // NOLINT: This is no error, clang-tidy
}
SignalHandler::~SignalHandler()
#pragma once
-#include "system.hpp"
-
class Context;
class SignalHandler
+++ /dev/null
-// Copyright (C) 2021 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
-
-enum Sloppiness {
- SLOPPY_INCLUDE_FILE_MTIME = 1 << 0,
- SLOPPY_INCLUDE_FILE_CTIME = 1 << 1,
- SLOPPY_TIME_MACROS = 1 << 2,
- SLOPPY_PCH_DEFINES = 1 << 3,
- // Allow us to match files based on their stats (size, mtime, ctime), without
- // looking at their contents.
- SLOPPY_FILE_STAT_MATCHES = 1 << 4,
- // Allow us to not include any system headers in the manifest include files,
- // similar to -MM versus -M for dependencies.
- SLOPPY_SYSTEM_HEADERS = 1 << 5,
- // Allow us to ignore ctimes when comparing file stats, so we can fake mtimes
- // if we want to (it is much harder to fake ctimes, requires changing clock)
- SLOPPY_FILE_STAT_MATCHES_CTIME = 1 << 6,
- // Allow us to not include the -index-store-path option in the manifest hash.
- SLOPPY_CLANG_INDEX_STORE = 1 << 7,
- // Ignore locale settings.
- SLOPPY_LOCALE = 1 << 8,
- // Allow caching even if -fmodules is used.
- SLOPPY_MODULES = 1 << 9,
- // Ignore virtual file system (VFS) overlay file.
- SLOPPY_IVFSOVERLAY = 1 << 10,
-};
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Stat.hpp"
-#ifdef _WIN32
-# include "Win32Util.hpp"
-
-# include "third_party/win32/winerror_to_errno.h"
-#endif
-
#include "Finalizer.hpp"
#include "Logging.hpp"
+#include "Win32Util.hpp"
+
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+
+#ifdef _WIN32
+# include <third_party/win32/winerror_to_errno.h>
+#endif
namespace {
} else {
m_errno = errno;
if (on_error == OnError::throw_error) {
- throw Error("failed to stat {}: {}", path, strerror(errno));
+ throw core::Error(FMT("failed to stat {}: {}", path, strerror(errno)));
}
if (on_error == OnError::log) {
LOG("Failed to stat {}: {}", path, strerror(errno));
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
+#include <core/wincompat.hpp>
+#include <util/TimePoint.hpp>
-#include "exceptions.hpp"
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <cstdint>
+#include <ctime>
#include <string>
+#ifdef _WIN32
+# ifndef S_IFIFO
+# define S_IFIFO 0x1000
+# endif
+# ifndef S_IFBLK
+# define S_IFBLK 0x6000
+# endif
+# ifndef S_IFLNK
+# define S_IFLNK 0xA000
+# endif
+# ifndef S_ISREG
+# define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
+# endif
+# ifndef S_ISFIFO
+# define S_ISFIFO(m) (((m)&S_IFMT) == S_IFIFO)
+# endif
+# ifndef S_ISCHR
+# define S_ISCHR(m) (((m)&S_IFMT) == S_IFCHR)
+# endif
+# ifndef S_ISLNK
+# define S_ISLNK(m) (((m)&S_IFMT) == S_IFLNK)
+# endif
+# ifndef S_ISBLK
+# define S_ISBLK(m) (((m)&S_IFMT) == S_IFBLK)
+# endif
+#endif
+
class Stat
{
public:
uint32_t st_reparse_tag;
};
#else
- // Use of typedef needed to suppress a spurious 'declaration does not declare
- // anything' warning in old GCC.
- typedef struct ::stat stat_t; // NOLINT(modernize-use-using)
+ using stat_t = struct stat;
#endif
using dev_t = decltype(stat_t{}.st_dev);
dev_t device() const;
ino_t inode() const;
mode_t mode() const;
- time_t ctime() const;
- time_t mtime() const;
+ util::TimePoint atime() const;
+ util::TimePoint ctime() const;
+ util::TimePoint mtime() const;
uint64_t size() const;
uint64_t size_on_disk() const;
uint32_t reparse_tag() const;
#endif
- timespec ctim() const;
- timespec mtim() const;
-
protected:
using StatFunction = int (*)(const char*, stat_t*);
inline bool
Stat::same_inode_as(const Stat& other) const
{
- return device() == other.device() && inode() == other.inode();
+ return m_errno == 0 && device() == other.device() && inode() == other.inode();
}
inline int
return m_stat.st_mode;
}
-inline time_t
+inline util::TimePoint
+Stat::atime() const
+{
+#if defined(_WIN32) || defined(HAVE_STRUCT_STAT_ST_ATIM)
+ return util::TimePoint(m_stat.st_atim);
+#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+ return util::TimePoint(m_stat.st_atimespec);
+#else
+ return util::TimePoint(m_stat.st_atime, 0);
+#endif
+}
+
+inline util::TimePoint
Stat::ctime() const
{
- return ctim().tv_sec;
+#if defined(_WIN32) || defined(HAVE_STRUCT_STAT_ST_CTIM)
+ return util::TimePoint(m_stat.st_ctim);
+#elif defined(HAVE_STRUCT_STAT_ST_CTIMESPEC)
+ return util::TimePoint(m_stat.st_ctimespec);
+#else
+ return util::TimePoint(m_stat.st_ctime, 0);
+#endif
}
-inline time_t
+inline util::TimePoint
Stat::mtime() const
{
- return mtim().tv_sec;
+#if defined(_WIN32) || defined(HAVE_STRUCT_STAT_ST_MTIM)
+ return util::TimePoint(m_stat.st_mtim);
+#elif defined(HAVE_STRUCT_STAT_ST_CTIMESPEC)
+ return util::TimePoint(m_stat.st_mtimespec);
+#else
+ return util::TimePoint(m_stat.st_mtime, 0);
+#endif
}
inline uint64_t
return m_stat.st_reparse_tag;
}
#endif
-
-inline timespec
-Stat::ctim() const
-{
-#if defined(_WIN32) || defined(HAVE_STRUCT_STAT_ST_CTIM)
- return m_stat.st_ctim;
-#else
- return {m_stat.st_ctime, 0};
-#endif
-}
-
-inline timespec
-Stat::mtim() const
-{
-#if defined(_WIN32) || defined(HAVE_STRUCT_STAT_ST_MTIM)
- return m_stat.st_mtim;
-#else
- return {m_stat.st_mtime, 0};
-#endif
-}
+++ /dev/null
-// Copyright (C) 2021 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
-
-// Statistics fields in storage order.
-enum class Statistic {
- none = 0,
- compiler_produced_stdout = 1,
- compile_failed = 2,
- internal_error = 3,
- cache_miss = 4,
- preprocessor_error = 5,
- could_not_find_compiler = 6,
- missing_cache_file = 7,
- preprocessed_cache_hit = 8,
- bad_compiler_arguments = 9,
- called_for_link = 10,
- files_in_cache = 11,
- cache_size_kibibyte = 12,
- obsolete_max_files = 13,
- obsolete_max_size = 14,
- unsupported_source_language = 15,
- bad_output_file = 16,
- no_input_file = 17,
- multiple_source_files = 18,
- autoconf_test = 19,
- unsupported_compiler_option = 20,
- output_to_stdout = 21,
- direct_cache_hit = 22,
- compiler_produced_no_output = 23,
- compiler_produced_empty_output = 24,
- error_hashing_extra_file = 25,
- compiler_check_failed = 26,
- could_not_use_precompiled_header = 27,
- called_for_preprocessing = 28,
- cleanups_performed = 29,
- unsupported_code_directive = 30,
- stats_zeroed_timestamp = 31,
- could_not_use_modules = 32,
-
- END
-};
+++ /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 "Statistics.hpp"
-
-#include "AtomicFile.hpp"
-#include "Config.hpp"
-#include "Lockfile.hpp"
-#include "Logging.hpp"
-#include "Util.hpp"
-#include "exceptions.hpp"
-#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 nonstd::nullopt;
-using nonstd::optional;
-
-// Returns a formatted version of a statistics value, or the empty string if the
-// statistics line shouldn't be printed.
-using FormatFunction = std::string (*)(uint64_t value);
-
-static std::string
-format_size(uint64_t size)
-{
- return FMT("{:>11}", Util::format_human_readable_size(size));
-}
-
-static std::string
-format_size_times_1024(uint64_t size)
-{
- return format_size(size * 1024);
-}
-
-static std::string
-format_timestamp(uint64_t timestamp)
-{
- if (timestamp > 0) {
- const auto tm = Util::localtime(timestamp);
- char buffer[100] = "?";
- if (tm) {
- strftime(buffer, sizeof(buffer), "%c", &*tm);
- }
- return std::string(" ") + buffer;
- } else {
- return {};
- }
-}
-
-static double
-hit_rate(const Counters& counters)
-{
- const uint64_t direct = counters.get(Statistic::direct_cache_hit);
- const uint64_t preprocessed = counters.get(Statistic::preprocessed_cache_hit);
- const uint64_t hit = direct + preprocessed;
- const uint64_t miss = counters.get(Statistic::cache_miss);
- const uint64_t total = hit + miss;
- return total > 0 ? (100.0 * hit) / total : 0.0;
-}
-
-static void
-for_each_level_1_and_2_stats_file(
- const std::string& cache_dir,
- const std::function<void(const std::string& path)> function)
-{
- for (size_t level_1 = 0; level_1 <= 0xF; ++level_1) {
- function(FMT("{}/{:x}/stats", cache_dir, level_1));
- for (size_t level_2 = 0; level_2 <= 0xF; ++level_2) {
- function(FMT("{}/{:x}/{:x}/stats", cache_dir, level_1, level_2));
- }
- }
-}
-
-static std::pair<Counters, time_t>
-collect_counters(const Config& config)
-{
- Counters counters;
- uint64_t zero_timestamp = 0;
- time_t last_updated = 0;
-
- // Add up the stats in each directory.
- for_each_level_1_and_2_stats_file(
- config.cache_dir(), [&](const std::string& path) {
- counters.set(Statistic::stats_zeroed_timestamp, 0); // Don't add
- counters.increment(Statistics::read(path));
- zero_timestamp = std::max(counters.get(Statistic::stats_zeroed_timestamp),
- zero_timestamp);
- last_updated = std::max(last_updated, Stat::stat(path).mtime());
- });
-
- counters.set(Statistic::stats_zeroed_timestamp, zero_timestamp);
- return std::make_pair(counters, last_updated);
-}
-
-namespace {
-
-struct StatisticsField
-{
- StatisticsField(Statistic statistic_,
- const char* id_,
- const char* message_,
- unsigned flags_ = 0,
- FormatFunction format_ = nullptr)
- : statistic(statistic_),
- id(id_),
- message(message_),
- flags(flags_),
- format(format_)
- {
- }
-
- const Statistic statistic;
- const char* const id; // for --print-stats
- const char* const message; // for --show-stats
- const unsigned flags; // bitmask of FLAG_* values
- const FormatFunction format; // nullptr -> use plain integer format
-};
-
-} // namespace
-
-#define STATISTICS_FIELD(id, ...) \
- { \
- Statistic::id, #id, __VA_ARGS__ \
- }
-
-// Statistics fields in display order.
-const StatisticsField k_statistics_fields[] = {
- STATISTICS_FIELD(
- stats_zeroed_timestamp, "stats zeroed", FLAG_ALWAYS, format_timestamp),
- STATISTICS_FIELD(direct_cache_hit, "cache hit (direct)", FLAG_ALWAYS),
- STATISTICS_FIELD(
- preprocessed_cache_hit, "cache hit (preprocessed)", FLAG_ALWAYS),
- STATISTICS_FIELD(cache_miss, "cache miss", FLAG_ALWAYS),
- STATISTICS_FIELD(called_for_link, "called for link"),
- STATISTICS_FIELD(called_for_preprocessing, "called for preprocessing"),
- STATISTICS_FIELD(multiple_source_files, "multiple source files"),
- STATISTICS_FIELD(compiler_produced_stdout, "compiler produced stdout"),
- STATISTICS_FIELD(compiler_produced_no_output, "compiler produced no output"),
- STATISTICS_FIELD(compiler_produced_empty_output,
- "compiler produced empty output"),
- STATISTICS_FIELD(compile_failed, "compile failed"),
- STATISTICS_FIELD(internal_error, "ccache internal error"),
- STATISTICS_FIELD(preprocessor_error, "preprocessor error"),
- STATISTICS_FIELD(could_not_use_precompiled_header,
- "can't use precompiled header"),
- STATISTICS_FIELD(could_not_use_modules, "can't use modules"),
- STATISTICS_FIELD(could_not_find_compiler, "couldn't find the compiler"),
- STATISTICS_FIELD(missing_cache_file, "cache file missing"),
- STATISTICS_FIELD(bad_compiler_arguments, "bad compiler arguments"),
- STATISTICS_FIELD(unsupported_source_language, "unsupported source language"),
- STATISTICS_FIELD(compiler_check_failed, "compiler check failed"),
- STATISTICS_FIELD(autoconf_test, "autoconf compile/link"),
- STATISTICS_FIELD(unsupported_compiler_option, "unsupported compiler option"),
- STATISTICS_FIELD(unsupported_code_directive, "unsupported code directive"),
- STATISTICS_FIELD(output_to_stdout, "output to stdout"),
- STATISTICS_FIELD(bad_output_file, "could not write to output file"),
- STATISTICS_FIELD(no_input_file, "no input file"),
- STATISTICS_FIELD(error_hashing_extra_file, "error hashing extra file"),
- STATISTICS_FIELD(cleanups_performed, "cleanups performed", FLAG_ALWAYS),
- STATISTICS_FIELD(files_in_cache, "files in cache", FLAG_NOZERO | FLAG_ALWAYS),
- STATISTICS_FIELD(cache_size_kibibyte,
- "cache size",
- FLAG_NOZERO | FLAG_ALWAYS,
- format_size_times_1024),
- STATISTICS_FIELD(obsolete_max_files, "OBSOLETE", FLAG_NOZERO | FLAG_NEVER),
- STATISTICS_FIELD(obsolete_max_size, "OBSOLETE", FLAG_NOZERO | FLAG_NEVER),
- STATISTICS_FIELD(none, nullptr),
-};
-
-namespace Statistics {
-
-Counters
-read(const std::string& path)
-{
- Counters counters;
-
- std::string data;
- try {
- data = Util::read_file(path);
- } catch (const Error&) {
- // Ignore.
- return counters;
- }
-
- size_t i = 0;
- const char* str = data.c_str();
- while (true) {
- char* end;
- const uint64_t value = std::strtoull(str, &end, 10);
- if (end == str) {
- break;
- }
- counters.set_raw(i, value);
- ++i;
- str = end;
- }
-
- return counters;
-}
-
-optional<Counters>
-update(const std::string& path,
- std::function<void(Counters& counters)> function)
-{
- Lockfile lock(path);
- if (!lock.acquired()) {
- LOG("Failed to acquire lock for {}", path);
- return nullopt;
- }
-
- auto counters = Statistics::read(path);
- function(counters);
-
- AtomicFile file(path, AtomicFile::Mode::text);
- for (size_t i = 0; i < counters.size(); ++i) {
- file.write(FMT("{}\n", counters.get_raw(i)));
- }
- try {
- file.commit();
- } catch (const Error& e) {
- // 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());
- }
-
- return counters;
-}
-
-optional<std::string>
-get_result(const Counters& counters)
-{
- for (const auto& field : k_statistics_fields) {
- if (counters.get(field.statistic) != 0 && !(field.flags & FLAG_NOZERO)) {
- return field.message;
- }
- }
- return nullopt;
-}
-
-void
-zero_all_counters(const Config& config)
-{
- const time_t timestamp = time(nullptr);
-
- for_each_level_1_and_2_stats_file(
- config.cache_dir(), [=](const std::string& path) {
- Statistics::update(path, [=](Counters& cs) {
- for (size_t i = 0; k_statistics_fields[i].message; ++i) {
- if (!(k_statistics_fields[i].flags & FLAG_NOZERO)) {
- cs.set(k_statistics_fields[i].statistic, 0);
- }
- }
- cs.set(Statistic::stats_zeroed_timestamp, timestamp);
- });
- });
-}
-
-std::string
-format_human_readable(const Config& config)
-{
- Counters counters;
- time_t last_updated;
- std::tie(counters, last_updated) = collect_counters(config);
- std::string result;
-
- 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);
- char timestamp[100] = "?";
- if (tm) {
- strftime(timestamp, sizeof(timestamp), "%c", &*tm);
- }
- result += FMT("{:36}{}\n", "stats updated", timestamp);
- }
-
- // ...and display them.
- for (size_t i = 0; k_statistics_fields[i].message; i++) {
- const Statistic statistic = k_statistics_fields[i].statistic;
-
- if (k_statistics_fields[i].flags & FLAG_NEVER) {
- continue;
- }
- if (counters.get(statistic) == 0
- && !(k_statistics_fields[i].flags & FLAG_ALWAYS)) {
- continue;
- }
-
- const std::string value =
- k_statistics_fields[i].format
- ? k_statistics_fields[i].format(counters.get(statistic))
- : FMT("{:8}", counters.get(statistic));
- if (!value.empty()) {
- result += FMT("{:32}{}\n", k_statistics_fields[i].message, value);
- }
-
- if (statistic == Statistic::cache_miss) {
- double percent = hit_rate(counters);
- result += FMT("{:34}{:6.2f} %\n", "cache hit rate", percent);
- }
- }
-
- if (config.max_files() != 0) {
- result += FMT("{:32}{:8}\n", "max files", config.max_files());
- }
- if (config.max_size() != 0) {
- result +=
- FMT("{:32}{}\n", "max cache size", format_size(config.max_size()));
- }
-
- return result;
-}
-
-std::string
-format_machine_readable(const Config& config)
-{
- Counters counters;
- time_t last_updated;
- std::tie(counters, last_updated) = collect_counters(config);
- std::string result;
-
- 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("{}\t{}\n",
- k_statistics_fields[i].id,
- counters.get(k_statistics_fields[i].statistic));
- }
- }
-
- return result;
-}
-
-} // namespace Statistics
+++ /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
-
-#pragma once
-
-#include "system.hpp"
-
-#include "Counters.hpp"
-#include "Statistic.hpp" // Any reasonable use of Statistics requires the Statistic enum.
-
-#include "third_party/nonstd/optional.hpp"
-
-#include <functional>
-#include <string>
-
-class Config;
-
-namespace Statistics {
-
-// Read counters from `path`. No lock is acquired.
-Counters read(const std::string& path);
-
-// Acquire a lock, read counters from `path`, call `function` with the counters,
-// write the counters to `path` and release the lock. Returns the resulting
-// counters or nullopt on error (e.g. if the lock could not be acquired).
-nonstd::optional<Counters> update(const std::string& path,
- std::function<void(Counters& counters)>);
-
-// Return a human-readable string representing the final ccache result, or
-// nullopt if there was no result.
-nonstd::optional<std::string> get_result(const Counters& counters);
-
-// Zero all statistics counters except those tracking cache size and number of
-// files in the cache.
-void zero_all_counters(const Config& config);
-
-// Format cache statistics in human-readable format.
-std::string format_human_readable(const Config& config);
-
-// Format cache statistics in machine-readable format.
-std::string format_machine_readable(const Config& config);
-
-} // namespace Statistics
+++ /dev/null
-// Copyright (C) 2019 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
-
-namespace std {
-
-#if __cplusplus < 201402L
-template<typename T, typename... TArgs>
-inline unique_ptr<T>
-make_unique(TArgs&&... args)
-{
- return unique_ptr<T>(new T(std::forward<TArgs>(args)...));
-}
-#endif
-
-} // namespace std
-// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Util.hpp"
-#ifdef _WIN32
-# include "third_party/win32/mktemp.h"
-#endif
-
-using nonstd::string_view;
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
-namespace {
+#include <cstdlib>
-#ifndef _WIN32
-mode_t
-get_umask()
-{
- static bool mask_retrieved = false;
- static mode_t mask;
- if (!mask_retrieved) {
- mask = umask(0);
- umask(mask);
- mask_retrieved = true;
- }
- return mask;
-}
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
#endif
-} // namespace
+#ifdef _WIN32
+# include "third_party/win32/mktemp.h"
+#endif
-TemporaryFile::TemporaryFile(string_view path_prefix)
- : path(std::string(path_prefix) + ".XXXXXX")
+TemporaryFile::TemporaryFile(std::string_view path_prefix,
+ std::string_view suffix)
+ : path(FMT("{}{}XXXXXX{}", path_prefix, tmp_file_infix, suffix))
{
Util::ensure_dir_exists(Util::dir_name(path));
#ifdef _WIN32
- // MSVC lacks mkstemp() and Mingw-w64's implementation[1] is problematic, as
+ // MSVC lacks mkstemps() and Mingw-w64's implementation[1] is problematic, as
// it can reuse the names of recently-deleted files unless the caller
// remembers to call srand().
// [1]: <https://github.com/Alexpux/mingw-w64/blob/
// d0d7f784833bbb0b2d279310ddc6afb52fe47a46/mingw-w64-crt/misc/mkstemp.c>
- fd = Fd(bsd_mkstemp(&path[0]));
+ fd = Fd(bsd_mkstemps(&path[0], suffix.length()));
#else
- fd = Fd(mkstemp(&path[0]));
+ fd = Fd(mkstemps(&path[0], suffix.length()));
#endif
if (!fd) {
- throw Fatal(
- "Failed to create temporary file for {}: {}", path, strerror(errno));
+ throw core::Fatal(
+ FMT("Failed to create temporary file for {}: {}", path, strerror(errno)));
}
Util::set_cloexec_flag(*fd);
#ifndef _WIN32
- fchmod(*fd, 0666 & ~get_umask());
+ fchmod(*fd, 0666 & ~Util::get_umask());
#endif
}
+
+bool
+TemporaryFile::is_tmp_file(std::string_view path)
+{
+ return Util::base_name(path).find(tmp_file_infix) != std::string::npos;
+}
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Fd.hpp"
-#include "third_party/nonstd/string_view.hpp"
-
#include <string>
+#include <string_view>
// This class represents a unique temporary file created by mkstemp. The file is
// not deleted by the destructor.
class TemporaryFile
{
public:
+ static constexpr char tmp_file_infix[] = ".tmp.";
+
// `path_prefix` is the base path. The resulting filename will be this path
- // plus a unique suffix. If `path_prefix` refers to a nonexistent directory
- // the directory will be created if possible.`
- TemporaryFile(nonstd::string_view path_prefix);
+ // plus a unique string plus `suffix`. If `path_prefix` refers to a
+ // nonexistent directory the directory will be created if possible.
+ TemporaryFile(std::string_view path_prefix, std::string_view suffix = ".tmp");
TemporaryFile(TemporaryFile&& other) noexcept = default;
- // Note: Should be declared noexcept, but since GCC 4.8 trips on it, don't do
- // that for now.
- TemporaryFile& operator=(TemporaryFile&& other) = default;
+ TemporaryFile& operator=(TemporaryFile&& other) noexcept = default;
+
+ static bool is_tmp_file(std::string_view path);
// The resulting open file descriptor in read/write mode. Unset on error.
Fd fd;
{
{
std::unique_lock<std::mutex> lock(m_mutex);
+ if (m_shutting_down) {
+ // Already called shut_down.
+ return;
+ }
m_shutting_down = true;
}
m_task_enqueued_or_shutting_down_condition.notify_all();
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include <condition_variable>
#include <functional>
#include <limits>
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
+#include <sys/stat.h>
+#include <sys/types.h>
-#include "third_party/nonstd/optional.hpp"
+#include <optional>
// This class sets a new (process-global) umask and restores the previous umask
// when destructed.
class UmaskScope
{
public:
- UmaskScope(nonstd::optional<mode_t> new_umask);
+ UmaskScope(std::optional<mode_t> new_umask);
~UmaskScope();
+ void release();
+
private:
- nonstd::optional<mode_t> m_saved_umask;
+ std::optional<mode_t> m_saved_umask = std::nullopt;
};
-UmaskScope::UmaskScope(nonstd::optional<mode_t> new_umask)
+inline UmaskScope::UmaskScope(std::optional<mode_t> new_umask)
{
#ifndef _WIN32
if (new_umask) {
- m_saved_umask = umask(*new_umask);
+ m_saved_umask = Util::set_umask(*new_umask);
}
#else
(void)new_umask;
#endif
}
-UmaskScope::~UmaskScope()
+inline UmaskScope::~UmaskScope()
+{
+ release();
+}
+
+inline void
+UmaskScope::release()
{
#ifndef _WIN32
if (m_saved_umask) {
- umask(*m_saved_umask);
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80635
+# if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+# endif
+ Util::set_umask(*m_saved_umask);
+# if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic pop
+# endif
+ m_saved_umask = std::nullopt;
}
#endif
}
-// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Config.hpp"
#include "Context.hpp"
#include "Fd.hpp"
-#include "FormatNonstdStringView.hpp"
#include "Logging.hpp"
#include "TemporaryFile.hpp"
-#include "fmtmacros.hpp"
+#include "Win32Util.hpp"
+
+#include <Config.hpp>
+#include <Finalizer.hpp>
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+#include <util/TimePoint.hpp>
+#include <util/file.hpp>
+#include <util/path.hpp>
+#include <util/string.hpp>
+
+#include <limits.h> // NOLINT: PATH_MAX is defined in limits.h
extern "C" {
#include "third_party/base32hex.h"
}
-#include <algorithm>
-#include <fstream>
-
-#ifndef HAVE_DIRENT_H
-# include <filesystem>
-#endif
-
-#ifdef HAVE_PWD_H
-# include <pwd.h>
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>
#endif
-#ifdef HAVE_SYS_TIME_H
-# include <sys/time.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
#endif
-#ifdef HAVE_LINUX_FS_H
-# include <linux/magic.h>
-# include <sys/statfs.h>
-#elif defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
-# include <sys/mount.h>
-# include <sys/param.h>
-#endif
+#include <fcntl.h>
-#ifdef _WIN32
-# include "Win32Util.hpp"
+#ifdef HAVE_PWD_H
+# include <pwd.h>
#endif
#ifdef __linux__
# endif
#endif
-using nonstd::nullopt;
-using nonstd::optional;
-using nonstd::string_view;
+using IncludeDelimiter = util::Tokenizer::IncludeDelimiter;
namespace {
+// Process umask, read and written by get_umask and set_umask.
+mode_t g_umask = [] {
+ const mode_t mask = umask(0);
+ umask(mask);
+ return mask;
+}();
+
// Search for the first match of the following regular expression:
//
// \x1b\[[\x30-\x3f]*[\x20-\x2f]*[Km]
// The primary reason for not using std::regex is that it's not available for
// GCC 4.8. It's also a bit bloated. The reason for not using POSIX regex
// functionality is that it's are not available in MinGW.
-string_view
-find_first_ansi_csi_seq(string_view string)
+std::string_view
+find_first_ansi_csi_seq(std::string_view string)
{
size_t pos = 0;
while (pos < string.length() && string[pos] != 0x1b) {
template<typename T>
std::vector<T>
-split_at(string_view input, const char* separators)
-{
- ASSERT(separators != nullptr && separators[0] != '\0');
+split_into(std::string_view string,
+ const char* separators,
+ util::Tokenizer::Mode mode,
+ IncludeDelimiter include_delimiter)
+{
std::vector<T> result;
-
- size_t start = 0;
- while (start < input.size()) {
- size_t end = input.find_first_of(separators, start);
-
- if (end == string_view::npos) {
- result.emplace_back(input.data() + start, input.size() - start);
- break;
- } else if (start != end) {
- result.emplace_back(input.data() + start, end - start);
- }
-
- start = end + 1;
+ for (const auto token :
+ util::Tokenizer(string, separators, mode, include_delimiter)) {
+ result.emplace_back(token);
}
-
return result;
}
std::string
-rewrite_stderr_to_absolute_paths(string_view text)
+rewrite_stderr_to_absolute_paths(std::string_view text)
{
static const std::string in_file_included_from = "In file included from ";
std::string result;
- for (auto line : Util::split_into_views(text, "\n")) {
+ using util::Tokenizer;
+ for (auto line : Tokenizer(text,
+ "\n",
+ Tokenizer::Mode::include_empty,
+ Tokenizer::IncludeDelimiter::yes)) {
// Rewrite <path> to <absolute path> in the following two cases, where X may
// be optional ANSI CSI sequences:
//
// In file included from X<path>X:1:
// X<path>X:1:2: ...
- if (Util::starts_with(line, in_file_included_from)) {
+ if (util::starts_with(line, in_file_included_from)) {
result += in_file_included_from;
line = line.substr(in_file_included_from.length());
}
line = line.substr(csi_seq.length());
}
size_t path_end = line.find(':');
- if (path_end == string_view::npos) {
+ if (path_end == std::string_view::npos) {
result.append(line.data(), line.length());
} else {
std::string path(line.substr(0, path_end));
result.append(line.data(), line.length());
}
}
- result += '\n';
}
return result;
}
namespace Util {
-string_view
-base_name(string_view path)
+std::string_view
+base_name(std::string_view path)
{
#ifdef _WIN32
const char delim[] = "/\\";
}
std::string
-change_extension(string_view path, string_view new_ext)
+change_extension(std::string_view path, std::string_view new_ext)
{
- string_view without_ext = Util::remove_extension(path);
+ std::string_view without_ext = Util::remove_extension(path);
return std::string(without_ext).append(new_ext.data(), new_ext.length());
}
# if defined(__linux__)
Fd src_fd(open(src.c_str(), O_RDONLY));
if (!src_fd) {
- throw Error("{}: {}", src, strerror(errno));
+ throw core::Error(FMT("{}: {}", src, strerror(errno)));
}
Fd dest_fd;
dest_fd =
Fd(open(dest.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666));
if (!dest_fd) {
- throw Error("{}: {}", src, strerror(errno));
+ throw core::Error(FMT("{}: {}", src, strerror(errno)));
}
}
if (ioctl(*dest_fd, FICLONE, *src_fd) != 0) {
- throw Error(strerror(errno));
+ throw core::Error(strerror(errno));
}
dest_fd.close();
# elif defined(__APPLE__)
(void)via_tmp_file;
if (clonefile(src.c_str(), dest.c_str(), CLONE_NOOWNERCOPY) != 0) {
- throw Error(strerror(errno));
+ throw core::Error(strerror(errno));
}
# else
(void)src;
(void)dest;
(void)via_tmp_file;
- throw Error(strerror(EOPNOTSUPP));
+ throw core::Error(strerror(EOPNOTSUPP));
# endif
}
#endif // FILE_CLONING_SUPPORTED
void
-clone_hard_link_or_copy_file(const Context& ctx,
+clone_hard_link_or_copy_file(const Config& config,
const std::string& source,
const std::string& dest,
bool via_tmp_file)
{
- if (ctx.config.file_clone()) {
+ if (config.file_clone()) {
#ifdef FILE_CLONING_SUPPORTED
LOG("Cloning {} to {}", source, dest);
try {
clone_file(source, dest, via_tmp_file);
return;
- } catch (Error& e) {
+ } catch (core::Error& e) {
LOG("Failed to clone: {}", e.what());
}
#else
LOG("Not cloning {} to {} since it's unsupported", source, dest);
#endif
}
- if (ctx.config.hard_link()) {
+ if (config.hard_link()) {
LOG("Hard linking {} to {}", source, dest);
try {
Util::hard_link(source, dest);
- if (chmod(dest.c_str(), 0444) != 0) {
- LOG("Failed to chmod: {}", strerror(errno));
+#ifndef _WIN32
+ if (chmod(dest.c_str(), 0444 & ~Util::get_umask()) != 0) {
+ LOG("Failed to chmod {}: {}", dest.c_str(), strerror(errno));
}
+#endif
return;
- } catch (const Error& e) {
- LOG_RAW(e.what());
+ } catch (const core::Error& e) {
+ LOG("Failed to hard link {} to {}: {}", source, dest, e.what());
// Fall back to copying.
}
}
}
size_t
-common_dir_prefix_length(string_view dir, string_view path)
+common_dir_prefix_length(std::string_view dir, std::string_view path)
{
if (dir.empty() || path.empty() || dir == "/" || path == "/") {
return 0;
void
copy_fd(int fd_in, int fd_out)
{
- read_fd(fd_in,
- [=](const void* data, size_t size) { write_fd(fd_out, data, size); });
+ util::read_fd(fd_in, [=](const void* data, size_t size) {
+ util::write_fd(fd_out, data, size);
+ });
}
void
copy_file(const std::string& src, const std::string& dest, bool via_tmp_file)
{
- Fd src_fd(open(src.c_str(), O_RDONLY));
+ Fd src_fd(open(src.c_str(), O_RDONLY | O_BINARY));
if (!src_fd) {
- throw Error("{}: {}", src, strerror(errno));
+ throw core::Error(
+ FMT("Failed to open {} for reading: {}", src, strerror(errno)));
}
+ unlink(dest.c_str());
+
Fd dest_fd;
std::string tmp_file;
if (via_tmp_file) {
dest_fd =
Fd(open(dest.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666));
if (!dest_fd) {
- throw Error("{}: {}", dest, strerror(errno));
+ throw core::Error(
+ FMT("Failed to open {} for writing: {}", dest, strerror(errno)));
}
}
}
bool
-create_dir(string_view dir)
+create_dir(std::string_view dir)
{
std::string dir_str(dir);
auto st = Stat::stat(dir_str);
}
}
-string_view
-dir_name(string_view path)
+std::string_view
+dir_name(std::string_view path)
{
#ifdef _WIN32
const char delim[] = "/\\";
{
std::string result;
const char* left = str.c_str();
- for (const char* right = left; *right; ++right) {
+ const char* right = left;
+ while (*right) {
if (*right == '$') {
result.append(left, right - left);
++right;
}
if (curly && *right != '}') {
- throw Error("syntax error: missing '}}' after \"{}\"", left);
+ throw core::Error(FMT("syntax error: missing '}}' after \"{}\"", left));
}
if (right == left) {
// Special case: don't consider a single $ the left of a variable.
std::string name(left, right - left);
const char* value = getenv(name.c_str());
if (!value) {
- throw Error("environment variable \"{}\" not set", name);
+ throw core::Error(FMT("environment variable \"{}\" not set", name));
}
result += value;
if (!curly) {
left = right + 1;
}
}
+ ++right;
}
result += left;
return result;
fallocate(int fd, long new_size)
{
#ifdef HAVE_POSIX_FALLOCATE
- return posix_fallocate(fd, 0, new_size);
-#else
+ const int posix_fallocate_err = posix_fallocate(fd, 0, new_size);
+ if (posix_fallocate_err == 0 || posix_fallocate_err != EINVAL) {
+ return posix_fallocate_err;
+ }
+ // the underlying filesystem does not support the operation so fallback to
+ // lseeks
+#endif
off_t saved_pos = lseek(fd, 0, SEEK_END);
off_t old_size = lseek(fd, 0, SEEK_END);
if (old_size == -1) {
}
int err = 0;
try {
- write_fd(fd, buf, bytes_to_write);
- } catch (Error&) {
+ util::write_fd(fd, buf, bytes_to_write);
+ } catch (core::Error&) {
err = errno;
}
lseek(fd, saved_pos, SEEK_SET);
free(buf);
return err;
-#endif
-}
-
-void
-for_each_level_1_subdir(const std::string& cache_dir,
- const SubdirVisitor& visitor,
- const ProgressReceiver& progress_receiver)
-{
- for (int i = 0; i <= 0xF; i++) {
- double progress = 1.0 * i / 16;
- progress_receiver(progress);
- std::string subdir_path = FMT("{}/{:x}", cache_dir, i);
- visitor(subdir_path, [&](double inner_progress) {
- progress_receiver(progress + inner_progress / 16);
- });
- }
- progress_receiver(1.0);
}
std::string
}
void
-ensure_dir_exists(nonstd::string_view dir)
+ensure_dir_exists(std::string_view dir)
{
if (!create_dir(dir)) {
- throw Fatal("Failed to create directory {}: {}", dir, strerror(errno));
+ throw core::Fatal(
+ FMT("Failed to create directory {}: {}", dir, strerror(errno)));
}
}
return actual_cwd;
#else
auto pwd = getenv("PWD");
- if (!pwd) {
+ if (!pwd || !util::is_absolute_path(pwd)) {
return actual_cwd;
}
auto pwd_stat = Stat::stat(pwd);
auto cwd_stat = Stat::stat(actual_cwd);
- if (!pwd_stat || !cwd_stat || !pwd_stat.same_inode_as(cwd_stat)) {
- return actual_cwd;
- }
- std::string normalized_pwd = normalize_absolute_path(pwd);
- return normalized_pwd == pwd
- || Stat::stat(normalized_pwd).same_inode_as(pwd_stat)
- ? normalized_pwd
- : pwd;
+ return !pwd_stat || !cwd_stat || !pwd_stat.same_inode_as(cwd_stat)
+ ? actual_cwd
+ : normalize_concrete_absolute_path(pwd);
#endif
}
-string_view
-get_extension(string_view path)
+std::string_view
+get_extension(std::string_view path)
{
#ifndef _WIN32
const char stop_at_chars[] = "./";
const char stop_at_chars[] = "./\\";
#endif
size_t pos = path.find_last_of(stop_at_chars);
- if (pos == string_view::npos || path.at(pos) == '/') {
+ if (pos == std::string_view::npos || path.at(pos) == '/') {
return {};
#ifdef _WIN32
} else if (path.at(pos) == '\\') {
}
}
-std::vector<CacheFile>
-get_level_1_files(const std::string& dir,
- const ProgressReceiver& progress_receiver)
-{
- std::vector<CacheFile> files;
-
- if (!Stat::stat(dir)) {
- return files;
- }
-
- size_t level_2_directories = 0;
-
- Util::traverse(dir, [&](const std::string& path, bool is_dir) {
- auto name = Util::base_name(path);
- if (name == "CACHEDIR.TAG" || name == "stats" || name.starts_with(".nfs")) {
- return;
- }
-
- if (!is_dir) {
- files.emplace_back(path);
- } else if (path != dir
- && path.find('/', dir.size() + 1) == std::string::npos) {
- ++level_2_directories;
- progress_receiver(level_2_directories / 16.0);
- }
- });
-
- progress_receiver(1.0);
- return files;
-}
-
std::string
get_home_directory()
{
- const char* p = getenv("HOME");
- if (p) {
+#ifdef _WIN32
+ if (const char* p = getenv("USERPROFILE")) {
return p;
}
-#ifdef _WIN32
- p = getenv("APPDATA");
- if (p) {
+ throw core::Fatal(
+ "The USERPROFILE environment variable must be set to your user profile "
+ "folder");
+#else
+ if (const char* p = getenv("HOME")) {
return p;
}
-#endif
-#ifdef HAVE_GETPWUID
+# ifdef HAVE_GETPWUID
{
struct passwd* pwd = getpwuid(getuid());
if (pwd) {
return pwd->pw_dir;
}
}
+# endif
+ throw core::Fatal(
+ "Could not determine home directory from $HOME or getpwuid(3)");
#endif
- throw Fatal("Could not determine home directory from $HOME or getpwuid(3)");
}
const char*
}
std::string
-get_relative_path(string_view dir, string_view path)
+get_relative_path(std::string_view dir, std::string_view path)
{
- ASSERT(Util::is_absolute_path(dir));
- ASSERT(Util::is_absolute_path(path));
+ ASSERT(util::is_absolute_path(dir));
+ ASSERT(util::is_absolute_path(path));
#ifdef _WIN32
// Paths can be escaped by a slash for use with e.g. -isystem.
return result.empty() ? "." : result;
}
-std::string
-get_path_in_cache(string_view cache_dir, uint8_t level, string_view name)
+mode_t
+get_umask()
{
- ASSERT(level >= 1 && level <= 8);
- ASSERT(name.length() >= level);
-
- std::string path(cache_dir);
- path.reserve(path.size() + level * 2 + 1 + name.length() - level);
-
- for (uint8_t i = 0; i < level; ++i) {
- path.push_back('/');
- path.push_back(name.at(i));
- }
-
- path.push_back('/');
- string_view name_remaining = name.substr(level);
- path.append(name_remaining.data(), name_remaining.length());
-
- return path;
+ return g_umask;
}
void
#ifndef _WIN32
if (link(oldpath.c_str(), newpath.c_str()) != 0) {
- throw Error(
- "failed to link {} to {}: {}", oldpath, newpath, strerror(errno));
+ throw core::Error(strerror(errno));
}
#else
if (!CreateHardLink(newpath.c_str(), oldpath.c_str(), nullptr)) {
- DWORD error = GetLastError();
- throw Error("failed to link {} to {}: {}",
- oldpath,
- newpath,
- Win32Util::error_message(error));
+ throw core::Error(Win32Util::error_message(GetLastError()));
}
#endif
}
-bool
-is_absolute_path(string_view path)
+std::optional<size_t>
+is_absolute_path_with_prefix(std::string_view path)
{
#ifdef _WIN32
- if (path.length() >= 2 && path[1] == ':'
- && (path[2] == '/' || path[2] == '\\')) {
- return true;
- }
+ const char delim[] = "/\\";
+#else
+ const char delim[] = "/";
#endif
- return !path.empty() && path[0] == '/';
-}
-
-#if defined(HAVE_LINUX_FS_H) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
-int
-is_nfs_fd(int fd, bool* is_nfs)
-{
- struct statfs buf;
- if (fstatfs(fd, &buf) != 0) {
- return errno;
+ auto split_pos = path.find_first_of(delim);
+ if (split_pos != std::string::npos) {
+#ifdef _WIN32
+ // -I/C:/foo and -I/c/foo will already be handled by delim_pos correctly
+ // resulting in -I and /C:/foo or /c/foo respectively. -IC:/foo will not as
+ // we would get -IC: and /foo.
+ if (split_pos > 0 && path[split_pos - 1] == ':') {
+ split_pos = split_pos - 2;
+ }
+#endif
+ // This is not redundant on some platforms, so nothing to simplify.
+ // NOLINTNEXTLINE(readability-simplify-boolean-expr)
+ return split_pos;
}
-# ifdef HAVE_LINUX_FS_H
- *is_nfs = buf.f_type == NFS_SUPER_MAGIC;
-# else // Mac OS X and some other BSD flavors
- *is_nfs = strcmp(buf.f_fstypename, "nfs") == 0;
-# endif
- return 0;
+ return std::nullopt;
}
-#else
-int
-is_nfs_fd(int /*fd*/, bool* /*is_nfs*/)
+
+bool
+is_ccache_executable(const std::string_view path)
{
- return -1;
-}
+ std::string name(Util::base_name(path));
+#ifdef _WIN32
+ name = Util::to_lowercase(name);
#endif
+ return util::starts_with(name, "ccache");
+}
bool
-is_precompiled_header(string_view path)
+is_precompiled_header(std::string_view path)
{
- string_view ext = get_extension(path);
+ std::string_view ext = get_extension(path);
return ext == ".gch" || ext == ".pch" || ext == ".pth"
|| get_extension(dir_name(path)) == ".gch";
}
-optional<tm>
-localtime(optional<time_t> time)
+std::optional<tm>
+localtime(std::optional<util::TimePoint> time)
{
- time_t timestamp = time ? *time : ::time(nullptr);
+ time_t timestamp = time ? time->sec() : util::TimePoint::now().sec();
tm result;
if (localtime_r(×tamp, &result)) {
return result;
} else {
- return nullopt;
+ return std::nullopt;
}
}
make_relative_path(const std::string& base_dir,
const std::string& actual_cwd,
const std::string& apparent_cwd,
- nonstd::string_view path)
+ std::string_view path)
{
- if (base_dir.empty() || !Util::starts_with(path, base_dir)) {
+ if (base_dir.empty() || !util::path_starts_with(path, base_dir)) {
return std::string(path);
}
const auto path_suffix = std::string(original_path.substr(path.length()));
const auto real_path = Util::real_path(std::string(path));
- const auto add_relpath_candidates = [&](nonstd::string_view path) {
- const std::string normalized_path = Util::normalize_absolute_path(path);
+ const auto add_relpath_candidates = [&](auto path) {
+ const std::string normalized_path =
+ Util::normalize_abstract_absolute_path(path);
relpath_candidates.push_back(
Util::get_relative_path(actual_cwd, normalized_path));
if (apparent_cwd != actual_cwd) {
// Find best (i.e. shortest existing) match:
std::sort(relpath_candidates.begin(),
relpath_candidates.end(),
- [](const std::string& path1, const std::string& path2) {
+ [](const auto& path1, const auto& path2) {
return path1.length() < path2.length();
});
for (const auto& relpath : relpath_candidates) {
}
std::string
-make_relative_path(const Context& ctx, string_view path)
+make_relative_path(const Context& ctx, std::string_view path)
{
return make_relative_path(
ctx.config.base_dir(), ctx.actual_cwd, ctx.apparent_cwd, path);
}
bool
-matches_dir_prefix_or_file(string_view dir_prefix_or_file, string_view path)
+matches_dir_prefix_or_file(std::string_view dir_prefix_or_file,
+ std::string_view path)
{
return !dir_prefix_or_file.empty() && !path.empty()
&& dir_prefix_or_file.length() <= path.length()
- && path.starts_with(dir_prefix_or_file)
+ && util::starts_with(path, dir_prefix_or_file)
&& (dir_prefix_or_file.length() == path.length()
|| is_dir_separator(path[dir_prefix_or_file.length()])
|| is_dir_separator(dir_prefix_or_file.back()));
}
std::string
-normalize_absolute_path(string_view path)
+normalize_abstract_absolute_path(std::string_view path)
{
- if (!is_absolute_path(path)) {
+ if (!util::is_absolute_path(path)) {
return std::string(path);
}
#ifdef _WIN32
- if (path.find("\\") != string_view::npos) {
+ if (path.find("\\") != std::string_view::npos) {
std::string new_path(path);
std::replace(new_path.begin(), new_path.end(), '\\', '/');
- return normalize_absolute_path(new_path);
+ return normalize_abstract_absolute_path(new_path);
}
std::string drive(path.substr(0, 2));
#endif
std::string result = "/";
- const size_t npos = string_view::npos;
+ const size_t npos = std::string_view::npos;
size_t left = 1;
while (true) {
break;
}
const auto right = path.find('/', left);
- string_view part = path.substr(left, right == npos ? npos : right - left);
+ std::string_view part =
+ path.substr(left, right == npos ? npos : right - left);
if (part == "..") {
if (result.length() > 1) {
// "/x/../part" -> "/part"
#endif
}
+std::string
+normalize_concrete_absolute_path(const std::string& path)
+{
+ const auto normalized_path = normalize_abstract_absolute_path(path);
+ return Stat::stat(normalized_path).same_inode_as(Stat::stat(path))
+ ? normalized_path
+ : path;
+}
+
uint64_t
-parse_duration(const std::string& duration)
+parse_duration(std::string_view duration)
{
uint64_t factor = 0;
char last_ch = duration.empty() ? '\0' : duration[duration.length() - 1];
factor = 1;
break;
default:
- throw Error("invalid suffix (supported: d (day) and s (second)): \"{}\"",
- duration);
+ throw core::Error(FMT(
+ "invalid suffix (supported: d (day) and s (second)): \"{}\"", duration));
}
- return factor * parse_unsigned(duration.substr(0, duration.length() - 1));
-}
-
-int64_t
-parse_signed(const std::string& value,
- optional<int64_t> min_value,
- optional<int64_t> max_value,
- string_view description)
-{
- std::string stripped_value = strip_whitespace(value);
-
- size_t end = 0;
- long long result = 0;
- bool failed = false;
- try {
- // Note: sizeof(long long) is guaranteed to be >= sizeof(int64_t)
- result = std::stoll(stripped_value, &end, 10);
- } catch (std::exception&) {
- failed = true;
- }
- if (failed || end != stripped_value.size()) {
- throw Error("invalid integer: \"{}\"", stripped_value);
- }
-
- int64_t min = min_value ? *min_value : INT64_MIN;
- int64_t max = max_value ? *max_value : INT64_MAX;
- if (result < min || result > max) {
- throw Error("{} must be between {} and {}", description, min, max);
+ const auto value =
+ util::parse_unsigned(duration.substr(0, duration.length() - 1));
+ if (value) {
+ return factor * *value;
+ } else {
+ throw core::Error(value.error());
}
- return result;
}
uint64_t
char* p;
double result = strtod(value.c_str(), &p);
if (errno != 0 || result < 0 || p == value.c_str() || value.empty()) {
- throw Error("invalid size: \"{}\"", value);
+ throw core::Error(FMT("invalid size: \"{}\"", value));
}
while (isspace(*p)) {
switch (*p) {
case 'T':
result *= multiplier;
- // Fallthrough.
+ [[fallthrough]];
case 'G':
result *= multiplier;
- // Fallthrough.
+ [[fallthrough]];
case 'M':
result *= multiplier;
- // Fallthrough.
+ [[fallthrough]];
case 'K':
case 'k':
result *= multiplier;
break;
default:
- throw Error("invalid size: \"{}\"", value);
+ throw core::Error(FMT("invalid size: \"{}\"", value));
}
} else {
// Default suffix: G.
return static_cast<uint64_t>(result);
}
-uint64_t
-parse_unsigned(const std::string& value,
- optional<uint64_t> min_value,
- optional<uint64_t> max_value,
- string_view description)
-{
- std::string stripped_value = strip_whitespace(value);
-
- size_t end = 0;
- unsigned long long result = 0;
- bool failed = false;
- if (Util::starts_with(stripped_value, "-")) {
- failed = true;
- } else {
- try {
- // Note: sizeof(unsigned long long) is guaranteed to be >=
- // sizeof(uint64_t)
- result = std::stoull(stripped_value, &end, 10);
- } catch (std::exception&) {
- failed = true;
- }
- }
- if (failed || end != stripped_value.size()) {
- throw Error("invalid unsigned integer: \"{}\"", stripped_value);
- }
-
- uint64_t min = min_value ? *min_value : 0;
- uint64_t max = max_value ? *max_value : UINT64_MAX;
- if (result < min || result > max) {
- throw Error("{} must be between {} and {}", description, min, max);
- }
- return result;
-}
-
-bool
-read_fd(int fd, DataReceiver data_receiver)
-{
- ssize_t n;
- char buffer[READ_BUFFER_SIZE];
- while ((n = read(fd, buffer, sizeof(buffer))) != 0) {
- if (n == -1 && errno != EINTR) {
- break;
- }
- if (n > 0) {
- data_receiver(buffer, n);
- }
- }
- return n >= 0;
-}
-
-std::string
-read_file(const std::string& path, size_t size_hint)
-{
- if (size_hint == 0) {
- auto stat = Stat::stat(path);
- if (!stat) {
- throw Error(strerror(errno));
- }
- size_hint = stat.size();
- }
-
- // +1 to be able to detect EOF in the first read call
- size_hint = (size_hint < 1024) ? 1024 : size_hint + 1;
-
- Fd fd(open(path.c_str(), O_RDONLY | O_BINARY));
- if (!fd) {
- throw Error(strerror(errno));
- }
-
- ssize_t ret = 0;
- size_t pos = 0;
- std::string result;
- result.resize(size_hint);
-
- while (true) {
- if (pos == result.size()) {
- result.resize(2 * result.size());
- }
- const size_t max_read = result.size() - pos;
- ret = read(*fd, &result[pos], max_read);
- if (ret == 0 || (ret == -1 && errno != EINTR)) {
- break;
- }
- if (ret > 0) {
- pos += ret;
- if (static_cast<size_t>(ret) < max_read) {
- break;
- }
- }
- }
-
- if (ret == -1) {
- LOG("Failed reading {}", path);
- throw Error(strerror(errno));
- }
-
- result.resize(pos);
- return result;
-}
-
#ifndef _WIN32
std::string
read_link(const std::string& path)
{
size_t buffer_size = path_max(path);
std::unique_ptr<char[]> buffer(new char[buffer_size]);
- ssize_t len = readlink(path.c_str(), buffer.get(), buffer_size - 1);
+ const auto len = readlink(path.c_str(), buffer.get(), buffer_size - 1);
if (len == -1) {
return "";
}
return resolved ? resolved : (return_empty_on_error ? "" : path);
}
-string_view
-remove_extension(string_view path)
+std::string_view
+remove_extension(std::string_view path)
{
return path.substr(0, path.length() - get_extension(path).length());
}
{
#ifndef _WIN32
if (::rename(oldpath.c_str(), newpath.c_str()) != 0) {
- throw Error(
- "failed to rename {} to {}: {}", oldpath, newpath, strerror(errno));
+ throw core::Error(
+ FMT("failed to rename {} to {}: {}", oldpath, newpath, strerror(errno)));
}
#else
// Windows' rename() won't overwrite an existing file, so need to use
if (!MoveFileExA(
oldpath.c_str(), newpath.c_str(), MOVEFILE_REPLACE_EXISTING)) {
DWORD error = GetLastError();
- throw Error("failed to rename {} to {}: {}",
- oldpath,
- newpath,
- Win32Util::error_message(error));
+ throw core::Error(FMT("failed to rename {} to {}: {}",
+ oldpath,
+ newpath,
+ Win32Util::error_message(error)));
}
#endif
}
-bool
-same_program_name(nonstd::string_view program_name,
- nonstd::string_view canonical_program_name)
-{
-#ifdef _WIN32
- std::string lowercase_program_name = Util::to_lowercase(program_name);
- return lowercase_program_name == canonical_program_name
- || lowercase_program_name == FMT("{}.exe", canonical_program_name);
-#else
- return program_name == canonical_program_name;
-#endif
-}
-
void
-send_to_stderr(const Context& ctx, const std::string& text)
+send_to_fd(const Context& ctx, std::string_view text, int fd)
{
- const std::string* text_to_send = &text;
+ std::string_view text_to_send = text;
std::string modified_text;
+#ifdef _WIN32
+ // stdout/stderr are normally opened in text mode, which would convert
+ // newlines a second time since we treat output as binary data. Make sure to
+ // switch to binary mode.
+ int oldmode = _setmode(fd, _O_BINARY);
+ Finalizer binary_mode_restorer([=] { _setmode(fd, oldmode); });
+#endif
+
if (ctx.args_info.strip_diagnostics_colors) {
try {
modified_text = strip_ansi_csi_seqs(text);
- text_to_send = &modified_text;
- } catch (const Error&) {
- // Fall through
+ text_to_send = modified_text;
+ } catch (const core::Error&) {
+ // Ignore.
}
}
if (ctx.config.absolute_paths_in_stderr()) {
- modified_text = rewrite_stderr_to_absolute_paths(*text_to_send);
- text_to_send = &modified_text;
+ modified_text = rewrite_stderr_to_absolute_paths(text_to_send);
+ text_to_send = modified_text;
}
- try {
- write_fd(STDERR_FILENO, text_to_send->data(), text_to_send->length());
- } catch (Error& e) {
- throw Error("Failed to write to stderr: {}", e.what());
+ const auto result =
+ util::write_fd(fd, text_to_send.data(), text_to_send.length());
+ if (!result) {
+ throw core::Error(FMT("Failed to write to {}: {}", fd, result.error()));
}
}
#endif
}
+mode_t
+set_umask(mode_t mask)
+{
+ g_umask = mask;
+ return umask(mask);
+}
+
void
setenv(const std::string& name, const std::string& value)
{
#endif
}
-std::vector<string_view>
-split_into_views(string_view input, const char* separators)
+std::vector<std::string_view>
+split_into_views(std::string_view string,
+ const char* separators,
+ util::Tokenizer::Mode mode,
+ IncludeDelimiter include_delimiter)
{
- return split_at<string_view>(input, separators);
+ return split_into<std::string_view>(
+ string, separators, mode, include_delimiter);
}
std::vector<std::string>
-split_into_strings(string_view input, const char* separators)
+split_into_strings(std::string_view string,
+ const char* separators,
+ util::Tokenizer::Mode mode,
+ IncludeDelimiter include_delimiter)
{
- return split_at<std::string>(input, separators);
+ return split_into<std::string>(string, separators, mode, include_delimiter);
}
std::string
-strip_ansi_csi_seqs(string_view string)
+strip_ansi_csi_seqs(std::string_view string)
{
size_t pos = 0;
std::string result;
}
std::string
-strip_whitespace(string_view string)
-{
- auto is_space = [](int ch) { return std::isspace(ch); };
- auto start = std::find_if_not(string.begin(), string.end(), is_space);
- auto end = std::find_if_not(string.rbegin(), string.rend(), is_space).base();
- return start < end ? std::string(start, end) : std::string();
-}
-
-std::string
-to_lowercase(string_view string)
+to_lowercase(std::string_view string)
{
std::string result;
result.resize(string.length());
if (stat.error_number() == ENOENT || stat.error_number() == ESTALE) {
continue;
}
- throw Error("failed to lstat {}: {}",
- entry_path,
- strerror(stat.error_number()));
+ throw core::Error(FMT("failed to lstat {}: {}",
+ entry_path,
+ strerror(stat.error_number())));
}
is_dir = stat.is_directory();
}
} else if (errno == ENOTDIR) {
visitor(path, false);
} else {
- throw Error("failed to open directory {}: {}", path, strerror(errno));
+ throw core::Error(
+ FMT("failed to open directory {}: {}", path, strerror(errno)));
}
}
} else if (std::filesystem::exists(path)) {
visitor(path, false);
} else {
- throw Error("failed to open directory {}: {}", path, strerror(errno));
+ throw core::Error(
+ FMT("failed to open directory {}: {}", path, strerror(errno)));
}
}
// If path is on an NFS share, unlink isn't atomic, so we rename to a temp
// file. We don't care if the temp file is trashed, so it's always safe to
// unlink it first.
- std::string tmp_name = path + ".ccache.rm.tmp";
+ const std::string tmp_name =
+ FMT("{}.ccache{}unlink", path, TemporaryFile::tmp_file_infix);
bool success = true;
try {
Util::rename(path, tmp_name);
- } catch (Error&) {
+ } catch (core::Error&) {
success = false;
saved_errno = errno;
}
{
#ifdef HAVE_UNSETENV
::unsetenv(name.c_str());
+#elif defined(_WIN32)
+ SetEnvironmentVariable(name.c_str(), NULL);
#else
putenv(strdup(name.c_str())); // Leak to environment.
#endif
}
-void
-update_mtime(const std::string& path)
-{
-#ifdef HAVE_UTIMES
- utimes(path.c_str(), nullptr);
-#else
- utime(path.c_str(), nullptr);
-#endif
-}
-
void
wipe_path(const std::string& path)
{
traverse(path, [](const std::string& p, bool is_dir) {
if (is_dir) {
if (rmdir(p.c_str()) != 0 && errno != ENOENT && errno != ESTALE) {
- throw Error("failed to rmdir {}: {}", p, strerror(errno));
+ throw core::Error(FMT("failed to rmdir {}: {}", p, strerror(errno)));
}
} else if (unlink(p.c_str()) != 0 && errno != ENOENT && errno != ESTALE) {
- throw Error("failed to unlink {}: {}", p, strerror(errno));
+ throw core::Error(FMT("failed to unlink {}: {}", p, strerror(errno)));
}
});
}
-void
-write_fd(int fd, const void* data, size_t size)
-{
- ssize_t written = 0;
- do {
- ssize_t count =
- write(fd, static_cast<const uint8_t*>(data) + written, size - written);
- if (count == -1) {
- if (errno != EAGAIN && errno != EINTR) {
- throw Error(strerror(errno));
- }
- } else {
- written += count;
- }
- } while (static_cast<size_t>(written) < size);
-}
-
-void
-write_file(const std::string& path,
- const std::string& data,
- std::ios_base::openmode open_mode)
-{
- if (path.empty()) {
- throw Error("No such file or directory");
- }
-
- open_mode |= std::ios::out;
- std::ofstream file(path, open_mode);
- if (!file) {
- throw Error(strerror(errno));
- }
- file << data;
-}
-
} // namespace Util
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
+#include <Stat.hpp>
+#include <util/TimePoint.hpp>
+#include <util/Tokenizer.hpp>
-#include "CacheFile.hpp"
-
-#include "third_party/nonstd/optional.hpp"
-#include "third_party/nonstd/string_view.hpp"
-
-#include <algorithm>
+#include <cstdint>
+#include <filesystem>
#include <functional>
-#include <ios>
#include <memory>
+#include <optional>
#include <string>
+#include <string_view>
#include <utility>
#include <vector>
+class Config;
class Context;
namespace Util {
-using DataReceiver = std::function<void(const void* data, size_t size)>;
-using ProgressReceiver = std::function<void(double progress)>;
-using SubdirVisitor = std::function<void(
- const std::string& dir_path, const ProgressReceiver& progress_receiver)>;
using TraverseVisitor =
std::function<void(const std::string& path, bool is_dir)>;
enum class UnlinkLog { log_failure, ignore_failure };
// Get base name of path.
-nonstd::string_view base_name(nonstd::string_view path);
+std::string_view base_name(std::string_view path);
// Get an integer value from bytes in big endian order.
//
// Remove the extension via `remove_extension()`, then add `new_ext`. `new_ext`
// should start with a dot, no extra dot is inserted.
-std::string change_extension(nonstd::string_view path,
- nonstd::string_view new_ext);
-
-// Return `value` adjusted to not be less than `min` and not more than `max`.
-template<typename T>
-T
-clamp(T value, T min, T max)
-{
- return std::min(max, std::max(min, value));
-}
+std::string change_extension(std::string_view path, std::string_view new_ext);
// Clone a file from `src` to `dest`. If `via_tmp_file` is true, `src` is cloned
-// to a temporary file and then renamed to `dest`. Throws `Error` on error.
+// to a temporary file and then renamed to `dest`. Throws `core::Error` on
+// error.
void clone_file(const std::string& src,
const std::string& dest,
bool via_tmp_file = false);
// Clone, hard link or copy a file from `source` to `dest` depending on settings
// in `ctx`. If cloning or hard linking cannot and should not be done the file
-// will be copied instead. Throws `Error` on error.
-void clone_hard_link_or_copy_file(const Context& ctx,
+// will be copied instead. Throws `core::Error` on error.
+void clone_hard_link_or_copy_file(const Config& config,
const std::string& source,
const std::string& dest,
bool via_tmp_file = false);
// Compute the length of the longest directory path that is common to paths
// `dir` (a directory) and `path` (any path).
-size_t common_dir_prefix_length(nonstd::string_view dir,
- nonstd::string_view path);
+size_t common_dir_prefix_length(std::string_view dir, std::string_view path);
-// Copy all data from `fd_in` to `fd_out`. Throws `Error` on error.
+// Copy all data from `fd_in` to `fd_out`. Throws `core::Error` on error.
void copy_fd(int fd_in, int fd_out);
// Copy a file from `src` to `dest`. If via_tmp_file is true, `src` is copied to
-// a temporary file and then renamed to dest. Throws `Error` on error.
+// a temporary file and then renamed to dest. Throws `core::Error` on error.
void copy_file(const std::string& src,
const std::string& dest,
bool via_tmp_file = false);
// Create a directory if needed, including its parents if needed.
//
// Returns true if the directory exists or could be created, otherwise false.
-bool create_dir(nonstd::string_view dir);
+bool create_dir(std::string_view dir);
// Get directory name of path.
-nonstd::string_view dir_name(nonstd::string_view path);
-
-// Return true if `suffix` is a suffix of `string`.
-inline bool
-ends_with(nonstd::string_view string, nonstd::string_view suffix)
-{
- return string.ends_with(suffix);
-}
+std::string_view dir_name(std::string_view path);
// Like create_dir but throws Fatal on error.
-void ensure_dir_exists(nonstd::string_view dir);
+void ensure_dir_exists(std::string_view dir);
// Expand all instances of $VAR or ${VAR}, where VAR is an environment variable,
-// in `str`. Throws `Error` if one of the environment variables.
+// in `str`. Throws `core::Error` if one of the environment variables.
[[nodiscard]] std::string expand_environment_variables(const std::string& str);
// Extends file size to at least new_size by calling posix_fallocate() if
// Returns 0 on success, an error number otherwise.
int fallocate(int fd, long new_size);
-// Call a function for each subdir (0-9a-f) in the cache.
-//
-// Parameters:
-// - cache_dir: Path to the cache directory.
-// - visitor: Function to call with directory path and progress_receiver as
-// arguments.
-// - progress_receiver: Function that will be called for progress updates.
-void for_each_level_1_subdir(const std::string& cache_dir,
- const SubdirVisitor& visitor,
- const ProgressReceiver& progress_receiver);
-
// Format `argv` as a simple string for logging purposes. That is, the result is
// not intended to be machine parsable. `argv` must be terminated by a nullptr.
std::string format_argv_for_logging(const char* const* argv);
// Return the file extension (including the dot) as a view into `path`. If
// `path` has no file extension, an empty string_view is returned.
-nonstd::string_view get_extension(nonstd::string_view path);
-
-// Get a list of files in a level 1 subdirectory of the cache.
-//
-// The function works under the assumption that directory entries with one
-// character names (except ".") are subdirectories and that there are no other
-// subdirectories.
-//
-// Files ignored:
-// - CACHEDIR.TAG
-// - stats
-// - .nfs* (temporary NFS files that may be left for open but deleted files).
-//
-// Parameters:
-// - dir: The directory to traverse recursively.
-// - progress_receiver: Function that will be called for progress updates.
-std::vector<CacheFile>
-get_level_1_files(const std::string& dir,
- const ProgressReceiver& progress_receiver);
+std::string_view get_extension(std::string_view path);
// Return the current user's home directory, or throw `Fatal` if it can't
// be determined.
// `path` (an absolute path). Assumes that both `dir` and `path` are normalized.
// The algorithm does *not* follow symlinks, so the result may not actually
// resolve to the same file as `path`.
-std::string get_relative_path(nonstd::string_view dir,
- nonstd::string_view path);
+std::string get_relative_path(std::string_view dir, std::string_view path);
-// Join `cache_dir`, a '/' and `name` into a single path and return it.
-// Additionally, `level` single-character, '/'-separated subpaths are split from
-// the beginning of `name` before joining them all.
-std::string get_path_in_cache(nonstd::string_view cache_dir,
- uint8_t level,
- nonstd::string_view name);
+// Get process umask.
+mode_t get_umask();
-// Hard-link `oldpath` to `newpath`. Throws `Error` on error.
+// Hard-link `oldpath` to `newpath`. Throws `core::Error` on error.
void hard_link(const std::string& oldpath, const std::string& newpath);
// Write bytes in big endian order from an integer value.
buffer[0] = value;
}
-// Return whether `path` is absolute.
-bool is_absolute_path(nonstd::string_view path);
+// Determine if `path` is an absolute path with prefix, returning the split
+// point.
+std::optional<size_t> is_absolute_path_with_prefix(std::string_view path);
-// Test if a file is on nfs.
-//
-// Sets is_nfs to the result if fstatfs is available and no error occurred.
-//
-// Returns 0 if is_nfs was set, -1 if fstatfs is not available or errno if an
-// error occurred.
-int is_nfs_fd(int fd, bool* is_nfs);
+// Detmine if `path` refers to a ccache executable.
+bool is_ccache_executable(std::string_view path);
// Return whether `ch` is a directory separator, i.e. '/' on POSIX systems and
// '/' or '\\' on Windows systems.
;
}
-// Return whether `path` is a full path.
-inline bool
-is_full_path(nonstd::string_view path)
-{
-#ifdef _WIN32
- if (path.find('\\') != nonstd::string_view::npos) {
- return true;
- }
-#endif
- return path.find('/') != nonstd::string_view::npos;
-}
-
// Return whether `path` represents a precompiled header (see "Precompiled
// Headers" in GCC docs).
-bool is_precompiled_header(nonstd::string_view path);
+bool is_precompiled_header(std::string_view path);
// Thread-safe version of `localtime(3)`. If `time` is not specified the current
// time of day is used.
-nonstd::optional<tm> localtime(nonstd::optional<time_t> time = {});
+std::optional<tm> localtime(std::optional<util::TimePoint> time = {});
+
+// Construct a normalized native path.
+//
+// Example:
+//
+// std::string path = Util::make_path("usr", "local", "bin");
+template<typename... T>
+std::string
+make_path(const T&... args)
+{
+ return (std::filesystem::path{} / ... / args).lexically_normal().string();
+}
// Make a relative path from current working directory (either `actual_cwd` or
// `apparent_cwd`) to `path` if `path` is under `base_dir`.
std::string make_relative_path(const std::string& base_dir,
const std::string& actual_cwd,
const std::string& apparent_cwd,
- nonstd::string_view path);
+ std::string_view path);
// Like above but with base directory and apparent/actual CWD taken from `ctx`.
-std::string make_relative_path(const Context& ctx, nonstd::string_view path);
+std::string make_relative_path(const Context& ctx, std::string_view path);
// Return whether `path` is equal to `dir_prefix_or_file` or if
// `dir_prefix_or_file` is a directory prefix of `path`.
-bool matches_dir_prefix_or_file(nonstd::string_view dir_prefix_or_file,
- nonstd::string_view path);
+bool matches_dir_prefix_or_file(std::string_view dir_prefix_or_file,
+ std::string_view path);
// Normalize absolute path `path`, not taking symlinks into account.
//
// Normalization here means syntactically removing redundant slashes and
// resolving "." and ".." parts. The algorithm does however *not* follow
-// symlinks, so the result may not actually resolve to `path`.
+// symlinks, so the result may not actually resolve to the same filesystem entry
+// as `path` (nor to any existing file system entry for that matter).
//
// On Windows: Backslashes are replaced with forward slashes.
-std::string normalize_absolute_path(nonstd::string_view path);
+std::string normalize_abstract_absolute_path(std::string_view path);
-// Parse `duration`, an unsigned integer with d (days) or s (seconds) suffix,
-// into seconds. Throws `Error` on error.
-uint64_t parse_duration(const std::string& duration);
+// Like normalize_abstract_absolute_path, but returns `path` unchanged if the
+// normalized result doesn't resolve to the same file system entry as `path`.
+std::string normalize_concrete_absolute_path(const std::string& path);
-// Parse a string into a signed integer.
-//
-// Throws `Error` if `value` cannot be parsed as an int64_t or if the value
-// falls out of the range [`min_value`, `max_value`]. `min_value` and
-// `max_value` default to min and max values of int64_t. `description` is
-// included in the error message for range violations.
-int64_t parse_signed(const std::string& value,
- nonstd::optional<int64_t> min_value = nonstd::nullopt,
- nonstd::optional<int64_t> max_value = nonstd::nullopt,
- nonstd::string_view description = "integer");
+// Parse `duration`, an unsigned integer with d (days) or s (seconds) suffix,
+// into seconds. Throws `core::Error` on error.
+uint64_t parse_duration(std::string_view duration);
// Parse a "size value", i.e. a string that can end in k, M, G, T (10-based
// suffixes) or Ki, Mi, Gi, Ti (2-based suffixes). For backward compatibility, K
-// is also recognized as a synonym of k. Throws `Error` on parse error.
+// is also recognized as a synonym of k. Throws `core::Error` on parse error.
uint64_t parse_size(const std::string& value);
-// Parse a string into an unsigned integer.
-//
-// Throws `Error` if `value` cannot be parsed as an uint64_t or if the value
-// falls out of the range [`min_value`, `max_value`]. `min_value` and
-// `max_value` default to min and max values of uint64_t. `description` is
-// included in the error message for range violations.
-uint64_t parse_unsigned(const std::string& value,
- nonstd::optional<uint64_t> min_value = nonstd::nullopt,
- nonstd::optional<uint64_t> max_value = nonstd::nullopt,
- nonstd::string_view description = "integer");
-
-// Read data from `fd` until end of file and call `data_receiver` with the read
-// data. Returns whether reading was successful, i.e. whether the read(2) call
-// did not return -1.
-bool read_fd(int fd, DataReceiver data_receiver);
-
-// Return `path`'s content as a string. If `size_hint` is not 0 then assume that
-// `path` has this size (this saves system calls).
-//
-// Throws `Error` on error. The description contains the error message without
-// the path.
-std::string read_file(const std::string& path, size_t size_hint = 0);
-
#ifndef _WIN32
// Like readlink(2) but returns the string (or the empty string on failure).
std::string read_link(const std::string& path);
// Return a view into `path` containing the given path without the filename
// extension as determined by `get_extension()`.
-nonstd::string_view remove_extension(nonstd::string_view path);
+std::string_view remove_extension(std::string_view path);
-// Rename `oldpath` to `newpath` (deleting `newpath`). Throws `Error` on error.
+// Rename `oldpath` to `newpath` (deleting `newpath`). Throws `core::Error` on
+// error.
void rename(const std::string& oldpath, const std::string& newpath);
-// Detmine if `program_name` is equal to `canonical_program_name`. On Windows,
-// this means performing a case insensitive equality check with and without a
-// ".exe" suffix. On non-Windows, it is a simple equality check.
-bool same_program_name(nonstd::string_view program_name,
- nonstd::string_view canonical_program_name);
-
-// Send `text` to STDERR_FILENO, optionally stripping ANSI color sequences if
-// `ctx.args_info.strip_diagnostics_colors` is true and rewriting paths to
-// absolute if `ctx.config.absolute_paths_in_stderr` is true. Throws `Error` on
-// error.
-void send_to_stderr(const Context& ctx, const std::string& text);
+// Send `text` to file descriptor `fd`, optionally stripping ANSI color
+// sequences if `ctx.args_info.strip_diagnostics_colors` is true and rewriting
+// paths to absolute if `ctx.config.absolute_paths_in_stderr` is true. Throws
+// `core::Error` on error.
+void send_to_fd(const Context& ctx, std::string_view text, int fd);
// Set the FD_CLOEXEC on file descriptor `fd`. This is a NOP on Windows.
void set_cloexec_flag(int fd);
+// Set process umask. Returns the previous mask.
+mode_t set_umask(mode_t mask);
+
// Set environment variable `name` to `value`.
void setenv(const std::string& name, const std::string& value);
/ 1024;
}
-// Split `input` into words at any of the characters listed in `separators`.
-// These words are a view into `input`; empty words are omitted. `separators`
-// must neither be the empty string nor a nullptr.
-std::vector<nonstd::string_view> split_into_views(nonstd::string_view input,
- const char* separators);
-
-// Same as `split_into_views` but the words are copied from `input`.
-std::vector<std::string> split_into_strings(nonstd::string_view input,
- const char* separators);
-
-// Return true if `prefix` is a prefix of `string`.
-inline bool
-starts_with(const char* string, nonstd::string_view prefix)
-{
- // Optimized version of starts_with(string_view, string_view): avoid computing
- // the length of the string argument.
- return strncmp(string, prefix.data(), prefix.length()) == 0;
-}
-
-// Return true if `prefix` is a prefix of `string`.
-inline bool
-starts_with(nonstd::string_view string, nonstd::string_view prefix)
-{
- return string.starts_with(prefix);
-}
+// Split `string` into tokens at any of the characters in `separators`. These
+// tokens are views into `string`. `separators` must neither be the empty string
+// nor a nullptr.
+std::vector<std::string_view>
+split_into_views(std::string_view string,
+ const char* separators,
+ util::Tokenizer::Mode mode = util::Tokenizer::Mode::skip_empty,
+ util::Tokenizer::IncludeDelimiter include_delimiter =
+ util::Tokenizer::IncludeDelimiter::no);
+
+// Same as `split_into_views` but the tokens are copied from `string`.
+std::vector<std::string> split_into_strings(
+ std::string_view string,
+ const char* separators,
+ util::Tokenizer::Mode mode = util::Tokenizer::Mode::skip_empty,
+ util::Tokenizer::IncludeDelimiter include_delimiter =
+ util::Tokenizer::IncludeDelimiter::no);
// Returns a copy of string with the specified ANSI CSI sequences removed.
-[[nodiscard]] std::string strip_ansi_csi_seqs(nonstd::string_view string);
-
-// Strip whitespace from left and right side of a string.
-[[nodiscard]] std::string strip_whitespace(nonstd::string_view string);
+[[nodiscard]] std::string strip_ansi_csi_seqs(std::string_view string);
// Convert a string to lowercase.
-[[nodiscard]] std::string to_lowercase(nonstd::string_view string);
+[[nodiscard]] std::string to_lowercase(std::string_view string);
// Traverse `path` recursively (postorder, i.e. files are visited before their
// parent directory).
//
-// Throws Error on error.
+// Throws core::Error on error.
void traverse(const std::string& path, const TraverseVisitor& visitor);
// Remove `path` (non-directory), NFS safe. Logs according to `unlink_log`.
//
-// Returns whether removal was successful. A nonexistent `path` is considered
-// successful.
+// Returns whether removal was successful. A nonexistent `path` is considered a
+// failure.
bool unlink_safe(const std::string& path,
UnlinkLog unlink_log = UnlinkLog::log_failure);
// Unset environment variable `name`.
void unsetenv(const std::string& name);
-// Set mtime of `path` to the current timestamp.
-void update_mtime(const std::string& path);
-
// Remove `path` (and its contents if it's a directory). A nonexistent path is
// not considered an error.
//
-// Throws Error on error.
+// Throws core::Error on error.
void wipe_path(const std::string& path);
-// Write `size` bytes from `data` to `fd`. Throws `Error` on error.
-void write_fd(int fd, const void* data, size_t size);
-
-// Write `data` to `path`. The file will be opened according to `open_mode`,
-// which always will include `std::ios::out` even if not specified at the call
-// site.
-//
-// Throws `Error` on error. The description contains the error message without
-// the path.
-void write_file(const std::string& path,
- const std::string& data,
- std::ios_base::openmode open_mode = std::ios::binary);
-
} // namespace Util
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
0,
nullptr);
std::string message(buffer, size);
+ while (!message.empty()
+ && (message.back() == '\n' || message.back() == '\r')) {
+ message.pop_back();
+ }
LocalFree(buffer);
return message;
}
++bs;
break;
}
- // Fallthrough.
+ [[fallthrough]];
+
case '"':
bs = (bs << 1) + 1;
- // Fallthrough.
+ [[fallthrough]];
+
default:
while (bs > 0) {
result += '\\';
} // namespace Win32Util
-// From: https://stackoverflow.com/a/58162122/262458
-#ifdef _MSC_VER
-int
-gettimeofday(struct timeval* tp, struct timezone* /*tzp*/)
-{
- namespace sc = std::chrono;
- sc::system_clock::duration d = sc::system_clock::now().time_since_epoch();
- sc::seconds s = sc::duration_cast<sc::seconds>(d);
- tp->tv_sec = static_cast<long>(s.count());
- tp->tv_usec =
- static_cast<long>(sc::duration_cast<sc::microseconds>(d - s).count());
-
- return 0;
-}
-#endif
-
-void
-usleep(int64_t usec)
-{
- std::this_thread::sleep_for(std::chrono::microseconds(usec));
-}
-
struct tm*
localtime_r(time_t* _clock, struct tm* _result)
{
-// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
+#ifdef _WIN32
-#include <string>
+# include <core/wincompat.hpp>
+
+# include <string>
+
+struct tm* localtime_r(time_t* _clock, struct tm* _result);
+
+# ifdef _MSC_VER
+int asprintf(char** strp, const char* fmt, ...);
+# endif
namespace Win32Util {
NTSTATUS get_last_ntstatus();
} // namespace Win32Util
+
+#endif
+++ /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
-
-#include "ZstdCompressor.hpp"
-
-#include "Logging.hpp"
-#include "assertions.hpp"
-#include "exceptions.hpp"
-
-#include <algorithm>
-
-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);
- }
-
- // 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(
- "Using compression level 1 (minimum level supported by libzstd) instead"
- " of {}",
- compression_level);
- compression_level = 1;
- }
-
- 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 {}",
- m_compression_level,
- compression_level);
- }
-
- size_t ret = ZSTD_initCStream(m_zstd_stream, m_compression_level);
- if (ZSTD_isError(ret)) {
- ZSTD_freeCStream(m_zstd_stream);
- throw Error("error initializing zstd compression stream");
- }
-}
-
-ZstdCompressor::~ZstdCompressor()
-{
- ZSTD_freeCStream(m_zstd_stream);
-}
-
-int8_t
-ZstdCompressor::actual_compression_level() const
-{
- return m_compression_level;
-}
-
-void
-ZstdCompressor::write(const void* data, size_t count)
-{
- m_zstd_in.src = data;
- m_zstd_in.size = count;
- m_zstd_in.pos = 0;
-
- int flush = data ? 0 : 1;
-
- size_t ret;
- while (m_zstd_in.pos < m_zstd_in.size) {
- uint8_t buffer[READ_BUFFER_SIZE];
- m_zstd_out.dst = buffer;
- m_zstd_out.size = sizeof(buffer);
- m_zstd_out.pos = 0;
- ret = ZSTD_compressStream(m_zstd_stream, &m_zstd_out, &m_zstd_in);
- ASSERT(!(ZSTD_isError(ret)));
- size_t compressed_bytes = m_zstd_out.pos;
- if (fwrite(buffer, 1, compressed_bytes, m_stream) != compressed_bytes
- || ferror(m_stream)) {
- throw Error("failed to write to zstd output stream ");
- }
- }
- ret = flush;
- while (ret > 0) {
- uint8_t buffer[READ_BUFFER_SIZE];
- m_zstd_out.dst = buffer;
- m_zstd_out.size = sizeof(buffer);
- m_zstd_out.pos = 0;
- ret = ZSTD_endStream(m_zstd_stream, &m_zstd_out);
- size_t compressed_bytes = m_zstd_out.pos;
- if (fwrite(buffer, 1, compressed_bytes, m_stream) != compressed_bytes
- || ferror(m_stream)) {
- throw Error("failed to write to zstd output stream");
- }
- }
-}
-
-void
-ZstdCompressor::finalize()
-{
- write(nullptr, 0);
-}
+++ /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 "system.hpp"
-
-#include "Compressor.hpp"
-#include "NonCopyable.hpp"
-
-#include <zstd.h>
-
-// A compressor of a Zstandard stream.
-class ZstdCompressor : public Compressor, NonCopyable
-{
-public:
- // Parameters:
- // - stream: The file to write data to.
- // - compression_level: Desired compression level.
- ZstdCompressor(FILE* stream, int8_t compression_level);
-
- ~ZstdCompressor() override;
-
- int8_t actual_compression_level() const override;
- void write(const void* data, size_t count) override;
- void finalize() override;
-
- constexpr static uint8_t default_compression_level = 1;
-
-private:
- FILE* m_stream;
- ZSTD_CStream* m_zstd_stream;
- ZSTD_inBuffer m_zstd_in;
- ZSTD_outBuffer m_zstd_out;
- int8_t m_compression_level;
-};
+++ /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
-
-#include "ZstdDecompressor.hpp"
-
-#include "assertions.hpp"
-#include "exceptions.hpp"
-
-ZstdDecompressor::ZstdDecompressor(FILE* stream)
- : m_stream(stream),
- m_input_size(0),
- m_input_consumed(0),
- m_zstd_stream(ZSTD_createDStream()),
- m_reached_stream_end(false)
-{
- size_t ret = ZSTD_initDStream(m_zstd_stream);
- if (ZSTD_isError(ret)) {
- ZSTD_freeDStream(m_zstd_stream);
- throw Error("failed to initialize zstd decompression stream");
- }
-}
-
-ZstdDecompressor::~ZstdDecompressor()
-{
- ZSTD_freeDStream(m_zstd_stream);
-}
-
-void
-ZstdDecompressor::read(void* data, size_t count)
-{
- size_t bytes_read = 0;
- while (bytes_read < count) {
- ASSERT(m_input_size >= m_input_consumed);
- if (m_input_size == m_input_consumed) {
- m_input_size = fread(m_input_buffer, 1, sizeof(m_input_buffer), m_stream);
- if (m_input_size == 0) {
- throw Error("failed to read from zstd input stream");
- }
- m_input_consumed = 0;
- }
-
- m_zstd_in.src = (m_input_buffer + m_input_consumed);
- m_zstd_in.size = m_input_size - m_input_consumed;
- m_zstd_in.pos = 0;
-
- m_zstd_out.dst = static_cast<uint8_t*>(data) + bytes_read;
- m_zstd_out.size = count - bytes_read;
- m_zstd_out.pos = 0;
- size_t ret = ZSTD_decompressStream(m_zstd_stream, &m_zstd_out, &m_zstd_in);
- if (ZSTD_isError(ret)) {
- throw Error("failed to read from zstd input stream");
- }
- if (ret == 0) {
- m_reached_stream_end = true;
- break;
- }
- bytes_read += m_zstd_out.pos;
- m_input_consumed += m_zstd_in.pos;
- }
-}
-
-void
-ZstdDecompressor::finalize()
-{
- if (!m_reached_stream_end) {
- throw Error("garbage data at end of zstd input stream");
- }
-}
+++ /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 "system.hpp"
-
-#include "Decompressor.hpp"
-
-#include <fstream>
-#include <zstd.h>
-
-// A decompressor of a Zstandard stream.
-class ZstdDecompressor : public Decompressor
-{
-public:
- // Parameters:
- // - stream: The file to read data from.
- explicit ZstdDecompressor(FILE* stream);
-
- ~ZstdDecompressor() override;
-
- void read(void* data, size_t count) override;
- void finalize() override;
-
-private:
- FILE* m_stream;
- char m_input_buffer[READ_BUFFER_SIZE];
- size_t m_input_size;
- size_t m_input_consumed;
- ZSTD_DStream* m_zstd_stream;
- ZSTD_inBuffer m_zstd_in;
- ZSTD_outBuffer m_zstd_out;
- bool m_reached_stream_end;
-};
-// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "argprocessing.hpp"
#include "Context.hpp"
-#include "FormatNonstdStringView.hpp"
#include "Logging.hpp"
#include "assertions.hpp"
#include "compopt.hpp"
#include "fmtmacros.hpp"
#include "language.hpp"
+#include <Depfile.hpp>
+#include <core/wincompat.hpp>
+#include <util/string.hpp>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
#include <cassert>
-using nonstd::nullopt;
-using nonstd::optional;
-using nonstd::string_view;
+using core::Statistic;
namespace {
enum class ColorDiagnostics : int8_t { never, automatic, always };
+// The dependency target in the dependency file is taken from the highest
+// priority source.
+enum class OutputDepOrigin : uint8_t {
+ // Not set
+ none = 0,
+ // From -MF target
+ mf = 1,
+ // From -Wp,-MD,target or -Wp,-MMD,target
+ wp = 2
+};
+
struct ArgumentProcessingState
{
bool found_c_opt = false;
bool found_S_opt = false;
bool found_pch = false;
bool found_fpch_preprocess = false;
+ bool found_Yu = false;
+ bool found_valid_Fp = false;
+ bool found_syntax_only = false;
ColorDiagnostics color_diagnostics = ColorDiagnostics::automatic;
bool found_directives_only = false;
bool found_rewrite_includes = false;
+ std::optional<std::string> found_xarch_arch;
+ bool found_mf_opt = false;
+ bool found_wp_md_or_mmd_opt = false;
+ bool found_md_or_mmd_opt = false;
+ bool found_Wa_a_opt = false;
std::string explicit_language; // As specified with -x.
std::string input_charset_option; // -finput-charset=...
- // Is the dependency makefile name overridden with -MF?
- bool dependency_filename_specified = false;
-
- // Is the dependency target name implicitly specified using
- // DEPENDENCIES_OUTPUT or SUNPRO_DEPENDENCIES?
- bool dependency_implicit_target_specified = false;
+ // Is the dependency file set via -Wp,-M[M]D,target or -MFtarget?
+ OutputDepOrigin output_dep_origin = OutputDepOrigin::none;
// Is the compiler being asked to output debug info on level 3?
bool generating_debuginfo_level_3 = false;
// Whether to include the full command line in the hash.
bool hash_full_command_line = false;
+
+ // Whether to include the actual CWD in the hash.
+ bool hash_actual_cwd = false;
};
bool
color_output_possible()
{
const char* term_env = getenv("TERM");
- return isatty(STDERR_FILENO) && term_env && strcasecmp(term_env, "DUMB") != 0;
+ return isatty(STDERR_FILENO) && term_env
+ && Util::to_lowercase(term_env) != "dumb";
}
bool
-detect_pch(Context& ctx,
- const std::string& option,
+detect_pch(const std::string& option,
const std::string& arg,
+ std::string& included_pch_file,
bool is_cc1_option,
- bool* found_pch)
+ ArgumentProcessingState& state)
{
- ASSERT(found_pch);
-
// Try to be smart about detecting precompiled headers.
// If the option is an option for Clang (is_cc1_option), don't accept
// anything just because it has a corresponding precompiled header,
// because Clang doesn't behave that way either.
std::string pch_file;
- if (option == "-include-pch" || option == "-include-pth") {
+ if (option == "-Yu") {
+ state.found_Yu = true;
+ if (state.found_valid_Fp) { // Use file set by -Fp.
+ LOG("Detected use of precompiled header: {}", included_pch_file);
+ pch_file = included_pch_file;
+ included_pch_file.clear(); // reset pch file set from /Fp
+ } else {
+ std::string file = Util::change_extension(arg, ".pch");
+ if (Stat::stat(file)) {
+ LOG("Detected use of precompiled header: {}", file);
+ pch_file = file;
+ }
+ }
+ } else if (option == "-Fp") {
+ std::string file = arg;
+ if (Util::get_extension(file).empty()) {
+ file += ".pch";
+ }
+ if (Stat::stat(file)) {
+ state.found_valid_Fp = true;
+ if (!state.found_Yu) {
+ LOG("Precompiled header file specified: {}", file);
+ included_pch_file = file; // remember file
+ return true; // -Fp does not turn on PCH
+ }
+ LOG("Detected use of precompiled header: {}", file);
+ pch_file = file;
+ included_pch_file.clear(); // reset pch file set from /Yu
+ // continue and set as if the file was passed to -Yu
+ }
+ } else if (option == "-include-pch" || option == "-include-pth") {
if (Stat::stat(arg)) {
LOG("Detected use of precompiled header: {}", arg);
pch_file = arg;
}
if (!pch_file.empty()) {
- if (!ctx.included_pch_file.empty()) {
+ if (!included_pch_file.empty()) {
LOG("Multiple precompiled headers used: {} and {}",
- ctx.included_pch_file,
+ included_pch_file,
pch_file);
return false;
}
- ctx.included_pch_file = pch_file;
- *found_pch = true;
+ included_pch_file = pch_file;
+ state.found_pch = true;
}
return true;
}
bool
-process_profiling_option(Context& ctx, const std::string& arg)
+process_profiling_option(const Context& ctx,
+ ArgsInfo& args_info,
+ const std::string& arg)
{
static const std::vector<std::string> known_simple_options = {
"-fprofile-correction",
std::string new_profile_path;
bool new_profile_use = false;
- if (Util::starts_with(arg, "-fprofile-dir=")) {
+ if (util::starts_with(arg, "-fprofile-dir=")) {
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.config.compiler_type() == CompilerType::clang) {
+ args_info.profile_generate = true;
+ if (ctx.config.is_compiler_group_clang()) {
new_profile_path = ".";
} else {
// GCC uses $PWD/$(basename $obj).
new_profile_path = ctx.apparent_cwd;
}
- } else if (Util::starts_with(arg, "-fprofile-generate=")
- || Util::starts_with(arg, "-fprofile-instr-generate=")) {
- ctx.args_info.profile_generate = true;
+ } else if (util::starts_with(arg, "-fprofile-generate=")
+ || util::starts_with(arg, "-fprofile-instr-generate=")) {
+ args_info.profile_generate = true;
new_profile_path = arg.substr(arg.find('=') + 1);
} else if (arg == "-fprofile-use" || arg == "-fprofile-instr-use"
|| arg == "-fprofile-sample-use" || arg == "-fbranch-probabilities"
|| arg == "-fauto-profile") {
new_profile_use = true;
- if (ctx.args_info.profile_path.empty()) {
+ if (args_info.profile_path.empty()) {
new_profile_path = ".";
}
- } else if (Util::starts_with(arg, "-fprofile-use=")
- || Util::starts_with(arg, "-fprofile-instr-use=")
- || Util::starts_with(arg, "-fprofile-sample-use=")
- || Util::starts_with(arg, "-fauto-profile=")) {
+ } else if (util::starts_with(arg, "-fprofile-use=")
+ || util::starts_with(arg, "-fprofile-instr-use=")
+ || util::starts_with(arg, "-fprofile-sample-use=")
+ || util::starts_with(arg, "-fauto-profile=")) {
new_profile_use = true;
new_profile_path = arg.substr(arg.find('=') + 1);
} else {
}
if (new_profile_use) {
- if (ctx.args_info.profile_use) {
+ if (args_info.profile_use) {
LOG_RAW("Multiple profiling options not supported");
return false;
}
- ctx.args_info.profile_use = true;
+ 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);
+ args_info.profile_path = new_profile_path;
+ LOG("Set profile directory to {}", args_info.profile_path);
}
- if (ctx.args_info.profile_generate && ctx.args_info.profile_use) {
+ if (args_info.profile_generate && args_info.profile_use) {
// Too hard to figure out what the compiler will do.
LOG_RAW("Both generating and using profile info, giving up");
return false;
return true;
}
-optional<Statistic>
-process_arg(Context& ctx,
- Args& args,
- size_t& args_index,
- ArgumentProcessingState& state)
+// Returns std::nullopt if the option wasn't recognized, otherwise the error
+// code (with Statistic::none for "no error").
+std::optional<Statistic>
+process_option_arg(const Context& ctx,
+ ArgsInfo& args_info,
+ Config& config,
+ Args& args,
+ size_t& args_index,
+ ArgumentProcessingState& state)
{
- ArgsInfo& args_info = ctx.args_info;
- Config& config = ctx.config;
-
size_t& i = args_index;
-
// The user knows best: just swallow the next arg.
if (args[i] == "--ccache-skip") {
i++;
return Statistic::bad_compiler_arguments;
}
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
+ }
+
+ bool changed_from_slash = false;
+ if (ctx.config.is_compiler_group_msvc() && util::starts_with(args[i], "/")) {
+ // MSVC understands both /option and -option, so convert all /option to
+ // -option to simplify our handling.
+ args[i][0] = '-';
+ changed_from_slash = true;
}
// Ignore clang -ivfsoverlay <arg> to not detect multiple input files.
if (args[i] == "-ivfsoverlay"
- && !(config.sloppiness() & SLOPPY_IVFSOVERLAY)) {
+ && !(config.sloppiness().is_enabled(core::Sloppy::ivfsoverlay))) {
LOG_RAW(
"You have to specify \"ivfsoverlay\" sloppiness when using"
" -ivfsoverlay to get hits");
if (args[i] == "-E") {
return Statistic::called_for_preprocessing;
}
+ // MSVC -P is -E with output to a file.
+ if (args[i] == "-P" && ctx.config.is_compiler_group_msvc()) {
+ return Statistic::called_for_preprocessing;
+ }
// Handle "@file" argument.
- if (Util::starts_with(args[i], "@") || Util::starts_with(args[i], "-@")) {
+ if (util::starts_with(args[i], "@") || util::starts_with(args[i], "-@")) {
const char* argpath = args[i].c_str() + 1;
if (argpath[-1] == '-') {
++argpath;
}
- auto file_args = Args::from_gcc_atfile(argpath);
+ auto file_args = Args::from_atfile(argpath,
+ config.is_compiler_group_msvc()
+ ? Args::AtFileFormat::msvc
+ : Args::AtFileFormat::gcc);
if (!file_args) {
LOG("Couldn't read arg file {}", argpath);
return Statistic::bad_compiler_arguments;
args.replace(i, *file_args);
i--;
- return nullopt;
+ return Statistic::none;
}
// Handle cuda "-optf" and "--options-file" argument.
// Argument is a comma-separated list of files.
auto paths = Util::split_into_strings(args[i], ",");
for (auto it = paths.rbegin(); it != paths.rend(); ++it) {
- auto file_args = Args::from_gcc_atfile(*it);
+ auto file_args = Args::from_atfile(*it);
if (!file_args) {
LOG("Couldn't read CUDA options file {}", *it);
return Statistic::bad_compiler_arguments;
args.insert(i + 1, *file_args);
}
- return nullopt;
+ return Statistic::none;
}
// These are always too hard.
- if (compopt_too_hard(args[i]) || Util::starts_with(args[i], "-fdump-")
- || Util::starts_with(args[i], "-MJ")) {
+ if (compopt_too_hard(args[i]) || util::starts_with(args[i], "-fdump-")
+ || util::starts_with(args[i], "-MJ")
+ || util::starts_with(args[i], "-Yc")) {
LOG("Compiler option {} is unsupported", args[i]);
return Statistic::unsupported_compiler_option;
}
}
// -Xarch_* options are too hard.
- if (Util::starts_with(args[i], "-Xarch_")) {
- LOG("Unsupported compiler option: {}", args[i]);
- return Statistic::unsupported_compiler_option;
+ if (util::starts_with(args[i], "-Xarch_")) {
+ if (i == args.size() - 1) {
+ LOG("Missing argument to {}", args[i]);
+ return Statistic::bad_compiler_arguments;
+ }
+ const auto arch = args[i].substr(7);
+ if (!state.found_xarch_arch) {
+ state.found_xarch_arch = arch;
+ } else if (*state.found_xarch_arch != arch) {
+ LOG_RAW("Multiple different -Xarch_* options not supported");
+ return Statistic::unsupported_compiler_option;
+ }
+ state.common_args.push_back(args[i]);
+ return Statistic::none;
}
// Handle -arch options.
if (args_info.arch_args.size() == 2) {
config.set_run_second_cpp(true);
}
- return nullopt;
+ return Statistic::none;
}
// Some arguments that clang passes directly to cc1 (related to precompiled
// headers) need the usual ccache handling. In those cases, the -Xclang
// prefix is skipped and the cc1 argument is handled instead.
- if (args[i] == "-Xclang" && i < args.size() - 1
+ if (args[i] == "-Xclang" && i + 1 < args.size()
&& (args[i + 1] == "-emit-pch" || args[i + 1] == "-emit-pth"
|| args[i + 1] == "-include-pch" || args[i + 1] == "-include-pth"
|| args[i + 1] == "-fno-pch-timestamp")) {
++i;
}
+ if (util::starts_with(args[i], "-Wa,")) {
+ for (const auto part : util::Tokenizer(&args[i][4], ",")) {
+ if (util::starts_with(part, "-a")) {
+ if (state.found_Wa_a_opt) {
+ LOG_RAW(
+ "Multiple assembler listing options (-Wa,-a) are not supported");
+ return Statistic::unsupported_compiler_option;
+ }
+ state.found_Wa_a_opt = true;
+
+ const auto eq_pos = part.find('=');
+ if (eq_pos != std::string_view::npos) {
+ args_info.output_al = part.substr(eq_pos + 1);
+ }
+ }
+ }
+ }
+
// Handle options that should not be passed to the preprocessor.
if (compopt_affects_compiler_output(args[i])) {
state.compiler_only_args.push_back(args[i]);
state.compiler_only_args.push_back(args[i + 1]);
++i;
}
- return nullopt;
+ return Statistic::none;
}
if (compopt_prefix_affects_compiler_output(args[i])) {
state.compiler_only_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
// Modules are handled on demand as necessary in the background, so there is
LOG("Compiler option {} is unsupported without direct depend mode",
args[i]);
return Statistic::could_not_use_modules;
- } else if (!(config.sloppiness() & SLOPPY_MODULES)) {
+ } else if (!(config.sloppiness().is_enabled(core::Sloppy::modules))) {
LOG_RAW(
"You have to specify \"modules\" sloppiness when using"
" -fmodules to get hits");
// We must have -c.
if (args[i] == "-c") {
state.found_c_opt = true;
- return nullopt;
+ return Statistic::none;
+ }
+
+ // MSVC -Fo with no space.
+ if (util::starts_with(args[i], "-Fo") && config.is_compiler_group_msvc()) {
+ args_info.output_obj = args[i].substr(3);
+ return Statistic::none;
}
// when using nvcc with separable compilation, -dc implies -c
if ((args[i] == "-dc" || args[i] == "--device-c")
&& config.compiler_type() == CompilerType::nvcc) {
state.found_dc_opt = true;
- return nullopt;
+ return Statistic::none;
}
// -S changes the default extension.
if (args[i] == "-S") {
state.common_args.push_back(args[i]);
state.found_S_opt = true;
- return nullopt;
+ return Statistic::none;
}
- if (Util::starts_with(args[i], "-x")) {
+ if (util::starts_with(args[i], "-x")) {
if (args[i].length() >= 3 && !islower(args[i][2])) {
// -xCODE (where CODE can be e.g. Host or CORE-AVX2, always starting with
// an uppercase letter) is an ordinary Intel compiler option, not a
// language specification. (GCC's "-x" language argument is always
// lowercase.)
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
// Special handling for -x: remember the last specified language before the
state.explicit_language = args[i + 1];
}
i++;
- return nullopt;
+ return Statistic::none;
}
DEBUG_ASSERT(args[i].length() >= 3);
if (args_info.input_file.empty()) {
state.explicit_language = args[i].substr(2);
}
- return nullopt;
+ return Statistic::none;
}
// We need to work out where the output was meant to go.
LOG("Missing argument to {}", args[i]);
return Statistic::bad_compiler_arguments;
}
- args_info.output_obj = Util::make_relative_path(ctx, args[i + 1]);
+ args_info.output_obj = args[i + 1];
i++;
- return nullopt;
+ return Statistic::none;
}
// Alternate form of -o with no space. Nvcc does not support this.
- if (Util::starts_with(args[i], "-o")
- && config.compiler_type() != CompilerType::nvcc) {
- args_info.output_obj =
- Util::make_relative_path(ctx, string_view(args[i]).substr(2));
- return nullopt;
- }
-
- if (Util::starts_with(args[i], "-fdebug-prefix-map=")
- || Util::starts_with(args[i], "-ffile-prefix-map=")) {
+ // Cl does support it as deprecated, but also has -openmp or -link -out
+ // which can confuse this and cause incorrect output_obj (and thus
+ // ccache debug file location), so better ignore it.
+ if (util::starts_with(args[i], "-o")
+ && config.compiler_type() != CompilerType::nvcc
+ && config.compiler_type() != CompilerType::msvc) {
+ args_info.output_obj = args[i].substr(2);
+ return Statistic::none;
+ }
+
+ if (util::starts_with(args[i], "-fdebug-prefix-map=")
+ || util::starts_with(args[i], "-ffile-prefix-map=")) {
std::string map = args[i].substr(args[i].find('=') + 1);
args_info.debug_prefix_maps.push_back(map);
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
// Debugging is handled specially, so that we know if we can strip line
// number info.
- if (Util::starts_with(args[i], "-g")) {
+ if (util::starts_with(args[i], "-g")) {
state.common_args.push_back(args[i]);
- if (Util::starts_with(args[i], "-gdwarf")) {
+ if (util::starts_with(args[i], "-gdwarf")) {
// Selection of DWARF format (-gdwarf or -gdwarf-<version>) enables
// debug info on level 2.
args_info.generating_debuginfo = true;
- return nullopt;
+ return Statistic::none;
}
- if (Util::starts_with(args[i], "-gz")) {
+ if (util::starts_with(args[i], "-gz")) {
// -gz[=type] neither disables nor enables debug info.
- return nullopt;
+ return Statistic::none;
}
char last_char = args[i].back();
args_info.seen_split_dwarf = true;
}
}
- return nullopt;
+ return Statistic::none;
}
// These options require special handling, because they behave differently
// with gcc -E, when the output file is not specified.
- if (args[i] == "-MD" || args[i] == "-MMD") {
+ if ((args[i] == "-MD" || args[i] == "-MMD")
+ && !config.is_compiler_group_msvc()) {
+ state.found_md_or_mmd_opt = true;
args_info.generating_dependencies = true;
- args_info.seen_MD_MMD = true;
state.dep_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
- if (Util::starts_with(args[i], "-MF")) {
- state.dependency_filename_specified = true;
+ if (util::starts_with(args[i], "-MF")) {
+ state.found_mf_opt = true;
std::string dep_file;
bool separate_argument = (args[i].size() == 3);
// -MFarg or -MF=arg (EDG-based compilers)
dep_file = args[i].substr(args[i][3] == '=' ? 4 : 3);
}
- args_info.output_dep = Util::make_relative_path(ctx, dep_file);
+
+ if (state.output_dep_origin <= OutputDepOrigin::mf) {
+ state.output_dep_origin = OutputDepOrigin::mf;
+ args_info.output_dep = Util::make_relative_path(ctx, dep_file);
+ }
// Keep the format of the args the same.
if (separate_argument) {
state.dep_args.push_back("-MF");
} else {
state.dep_args.push_back("-MF" + args_info.output_dep);
}
- return nullopt;
+ return Statistic::none;
}
- if (Util::starts_with(args[i], "-MQ") || Util::starts_with(args[i], "-MT")) {
- ctx.args_info.dependency_target_specified = true;
+ if ((util::starts_with(args[i], "-MQ") || util::starts_with(args[i], "-MT"))
+ && !config.is_compiler_group_msvc()) {
+ const bool is_mq = args[i][2] == 'Q';
+ std::string_view dep_target;
if (args[i].size() == 3) {
// -MQ arg or -MT arg
if (i == args.size() - 1) {
return Statistic::bad_compiler_arguments;
}
state.dep_args.push_back(args[i]);
- std::string relpath = Util::make_relative_path(ctx, args[i + 1]);
- state.dep_args.push_back(relpath);
+ state.dep_args.push_back(args[i + 1]);
+ dep_target = args[i + 1];
i++;
} else {
- 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("{}{}", arg_opt, relpath));
+ // -MQarg or -MTarg
+ const std::string_view arg_view(args[i]);
+ const auto arg_opt = arg_view.substr(0, 3);
+ dep_target = arg_view.substr(3);
+ state.dep_args.push_back(FMT("{}{}", arg_opt, dep_target));
+ }
+
+ if (args_info.dependency_target) {
+ args_info.dependency_target->push_back(' ');
+ } else {
+ args_info.dependency_target = "";
}
- return nullopt;
+ *args_info.dependency_target +=
+ is_mq ? Depfile::escape_filename(dep_target) : dep_target;
+
+ return Statistic::none;
+ }
+
+ // MSVC -MD[d], -MT[d] and -LT[d] options are something different than GCC's
+ // -MD etc.
+ if (config.is_compiler_group_msvc()
+ && (util::starts_with(args[i], "-MD") || util::starts_with(args[i], "-MT")
+ || util::starts_with(args[i], "-LD"))) {
+ // These affect compiler but also #define some things.
+ state.cpp_args.push_back(args[i]);
+ state.common_args.push_back(args[i]);
+ return Statistic::none;
+ }
+
+ if (args[i] == "-showIncludes") {
+ args_info.generating_includes = true;
+ state.dep_args.push_back(args[i]);
+ return Statistic::none;
}
if (args[i] == "-fprofile-arcs") {
args_info.profile_arcs = true;
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
if (args[i] == "-ftest-coverage") {
args_info.generating_coverage = true;
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
if (args[i] == "-fstack-usage") {
args_info.generating_stackusage = true;
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
- if (args[i] == "-fsyntax-only") {
+ // -Zs is MSVC's -fsyntax-only equivalent
+ if (args[i] == "-fsyntax-only" || args[i] == "-Zs") {
args_info.expect_output_obj = false;
state.compiler_only_args.push_back(args[i]);
- return nullopt;
+ state.found_syntax_only = true;
+ return Statistic::none;
}
if (args[i] == "--coverage" // = -fprofile-arcs -ftest-coverage
args_info.profile_arcs = true;
args_info.generating_coverage = true;
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
+ }
+
+ if (args[i] == "-fprofile-abs-path") {
+ if (!config.sloppiness().is_enabled(core::Sloppy::gcno_cwd)) {
+ // -fprofile-abs-path makes the compiler include absolute paths based on
+ // the actual CWD in the .gcno file.
+ state.hash_actual_cwd = true;
+ }
+ return Statistic::none;
}
- if (Util::starts_with(args[i], "-fprofile-")
- || Util::starts_with(args[i], "-fauto-profile")
+ if (util::starts_with(args[i], "-fprofile-")
+ || util::starts_with(args[i], "-fauto-profile")
|| args[i] == "-fbranch-probabilities") {
- if (!process_profiling_option(ctx, args[i])) {
+ if (!process_profiling_option(ctx, args_info, args[i])) {
// The failure is logged by process_profiling_option.
return Statistic::unsupported_compiler_option;
}
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
- if (Util::starts_with(args[i], "-fsanitize-blacklist=")) {
+ if (util::starts_with(args[i], "-fsanitize-blacklist=")) {
args_info.sanitize_blacklists.emplace_back(args[i].substr(21));
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
- if (Util::starts_with(args[i], "--sysroot=")) {
- auto path = string_view(args[i]).substr(10);
+ if (util::starts_with(args[i], "--sysroot=")) {
+ auto path = std::string_view(args[i]).substr(10);
auto relpath = Util::make_relative_path(ctx, path);
state.common_args.push_back("--sysroot=" + relpath);
- return nullopt;
+ return Statistic::none;
}
// Alternate form of specifying sysroot without =
auto relpath = Util::make_relative_path(ctx, args[i + 1]);
state.common_args.push_back(relpath);
i++;
- return nullopt;
+ return Statistic::none;
}
// Alternate form of specifying target without =
state.common_args.push_back(args[i]);
state.common_args.push_back(args[i + 1]);
i++;
- return nullopt;
+ return Statistic::none;
}
if (args[i] == "-P" || args[i] == "-Wp,-P") {
state.compiler_only_args.push_back(args[i]);
LOG("{} used; not compiling preprocessed code", args[i]);
config.set_run_second_cpp(true);
- return nullopt;
+ return Statistic::none;
}
- if (Util::starts_with(args[i], "-Wp,")) {
+ if (util::starts_with(args[i], "-Wp,")) {
if (args[i].find(",-P,") != std::string::npos
- || Util::ends_with(args[i], ",-P")) {
- // -P together with other preprocessor options is just too hard.
+ || util::ends_with(args[i], ",-P")) {
+ LOG("-P together with other preprocessor options is too hard: {}",
+ args[i]);
return Statistic::unsupported_compiler_option;
- } else if (Util::starts_with(args[i], "-Wp,-MD,")
+ } else if (util::starts_with(args[i], "-Wp,-MD,")
&& args[i].find(',', 8) == std::string::npos) {
+ state.found_wp_md_or_mmd_opt = true;
args_info.generating_dependencies = true;
- state.dependency_filename_specified = true;
- args_info.output_dep =
- Util::make_relative_path(ctx, string_view(args[i]).substr(8));
+ if (state.output_dep_origin <= OutputDepOrigin::wp) {
+ state.output_dep_origin = OutputDepOrigin::wp;
+ args_info.output_dep = args[i].substr(8);
+ }
state.dep_args.push_back(args[i]);
- return nullopt;
- } else if (Util::starts_with(args[i], "-Wp,-MMD,")
+ return Statistic::none;
+ } else if (util::starts_with(args[i], "-Wp,-MMD,")
&& args[i].find(',', 9) == std::string::npos) {
+ state.found_wp_md_or_mmd_opt = true;
args_info.generating_dependencies = true;
- state.dependency_filename_specified = true;
- args_info.output_dep =
- Util::make_relative_path(ctx, string_view(args[i]).substr(9));
+ if (state.output_dep_origin <= OutputDepOrigin::wp) {
+ state.output_dep_origin = OutputDepOrigin::wp;
+ args_info.output_dep = args[i].substr(9);
+ }
state.dep_args.push_back(args[i]);
- return nullopt;
- } else if (Util::starts_with(args[i], "-Wp,-D")
+ return Statistic::none;
+ } else if (util::starts_with(args[i], "-Wp,-D")
&& args[i].find(',', 6) == std::string::npos) {
// Treat it like -D.
state.cpp_args.push_back(args[i].substr(4));
- return nullopt;
+ return Statistic::none;
} else if (args[i] == "-Wp,-MP"
- || (args[i].size() > 8 && Util::starts_with(args[i], "-Wp,-M")
+ || (args[i].size() > 8 && util::starts_with(args[i], "-Wp,-M")
&& args[i][7] == ','
&& (args[i][6] == 'F' || args[i][6] == 'Q'
|| args[i][6] == 'T')
&& args[i].find(',', 8) == std::string::npos)) {
- // TODO: Make argument to MF/MQ/MT relative.
state.dep_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
} else if (config.direct_mode()) {
// -Wp, can be used to pass too hard options to the preprocessor.
// Hence, disable direct mode.
// Any other -Wp,* arguments are only relevant for the preprocessor.
state.cpp_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
if (args[i] == "-MP") {
state.dep_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
// Input charset needs to be handled specially.
- if (Util::starts_with(args[i], "-finput-charset=")) {
+ if (util::starts_with(args[i], "-finput-charset=")) {
state.input_charset_option = args[i];
- return nullopt;
+ return Statistic::none;
}
if (args[i] == "--serialize-diagnostics") {
args_info.generating_diagnostics = true;
args_info.output_dia = Util::make_relative_path(ctx, args[i + 1]);
i++;
- return nullopt;
- }
-
- if (config.compiler_type() == CompilerType::gcc
- && (args[i] == "-fcolor-diagnostics"
- || args[i] == "-fno-color-diagnostics")) {
- // Special case: If a GCC compiler gets -f(no-)color-diagnostics we'll bail
- // out and just execute the compiler. The reason is that we don't include
- // -f(no-)color-diagnostics in the hash so there can be a false cache hit in
- // the following scenario:
- //
- // 1. ccache gcc -c example.c # adds a cache entry
- // 2. ccache gcc -c example.c -fcolor-diagnostics # unexpectedly succeeds
- return Statistic::unsupported_compiler_option;
- }
-
- // In the "-Xclang -fcolor-diagnostics" form, -Xclang is skipped and the
- // -fcolor-diagnostics argument which is passed to cc1 is handled below.
- if (args[i] == "-Xclang" && i < args.size() - 1
- && args[i + 1] == "-fcolor-diagnostics") {
- state.compiler_only_args_no_hash.push_back(args[i]);
- ++i;
- }
-
- 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;
+ return Statistic::none;
+ }
+
+ if (config.compiler_type() == CompilerType::gcc) {
+ if (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 Statistic::none;
+ } else if (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 Statistic::none;
+ } else if (args[i] == "-fdiagnostics-color=auto") {
+ state.color_diagnostics = ColorDiagnostics::automatic;
+ state.compiler_only_args_no_hash.push_back(args[i]);
+ return Statistic::none;
+ }
+ } else if (config.is_compiler_group_clang()) {
+ // In the "-Xclang -fcolor-diagnostics" form, -Xclang is skipped and the
+ // -fcolor-diagnostics argument which is passed to cc1 is handled below.
+ if (args[i] == "-Xclang" && i + 1 < args.size()
+ && args[i + 1] == "-fcolor-diagnostics") {
+ state.compiler_only_args_no_hash.push_back(args[i]);
+ ++i;
+ }
+ if (args[i] == "-fcolor-diagnostics") {
+ state.color_diagnostics = ColorDiagnostics::always;
+ state.compiler_only_args_no_hash.push_back(args[i]);
+ return Statistic::none;
+ } else if (args[i] == "-fno-color-diagnostics") {
+ state.color_diagnostics = ColorDiagnostics::never;
+ state.compiler_only_args_no_hash.push_back(args[i]);
+ return Statistic::none;
+ }
}
// GCC
if (args[i] == "-fdirectives-only") {
state.found_directives_only = true;
- return nullopt;
+ return Statistic::none;
}
// Clang
if (args[i] == "-frewrite-includes") {
state.found_rewrite_includes = true;
- return nullopt;
+ return Statistic::none;
}
if (args[i] == "-fno-pch-timestamp") {
args_info.fno_pch_timestamp = true;
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
if (args[i] == "-fpch-preprocess") {
state.found_fpch_preprocess = true;
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
- if (config.sloppiness() & SLOPPY_CLANG_INDEX_STORE
+ if (config.sloppiness().is_enabled(core::Sloppy::clang_index_store)
&& args[i] == "-index-store-path") {
// Xcode 9 or later calls Clang with this option. The given path includes a
// UUID that might lead to cache misses, especially when cache is shared
if (i <= args.size() - 1) {
LOG("Skipping argument -index-store-path {}", args[i]);
}
- return nullopt;
+ return Statistic::none;
}
if (args[i] == "-frecord-gcc-switches") {
state.hash_full_command_line = true;
}
- // Options taking an argument that we may want to rewrite to relative paths to
- // get better hit rate. A secondary effect is that paths in the standard error
- // output produced by the compiler will be normalized.
+ // MSVC -u is something else than GCC -u, handle it specially.
+ if (args[i] == "-u" && ctx.config.is_compiler_group_msvc()) {
+ state.cpp_args.push_back(args[i]);
+ return Statistic::none;
+ }
+
if (compopt_takes_path(args[i])) {
if (i == args.size() - 1) {
LOG("Missing argument to {}", args[i]);
// In the -Xclang -include-(pch/pth) -Xclang <path> case, the path is one
// index further behind.
- int next = 1;
- if (args[i + 1] == "-Xclang" && i + 2 < args.size()) {
- next = 2;
- }
+ const size_t next = args[i + 1] == "-Xclang" && i + 2 < args.size() ? 2 : 1;
- if (!detect_pch(
- ctx, args[i], args[i + next], next == 2, &state.found_pch)) {
+ if (!detect_pch(args[i],
+ args[i + next],
+ args_info.included_pch_file,
+ next == 2,
+ state)) {
return Statistic::bad_compiler_arguments;
}
+ // Potentially rewrite path argument to relative path to get better hit
+ // rate. A secondary effect is that paths in the standard error output
+ // produced by the compiler will be normalized.
std::string relpath = Util::make_relative_path(ctx, args[i + next]);
auto& dest_args =
compopt_affects_cpp_output(args[i]) ? state.cpp_args : state.common_args;
dest_args.push_back(relpath);
i += next;
- return nullopt;
+ return Statistic::none;
+ }
+
+ // Detect PCH for options with concatenated path (relative or absolute).
+ if (util::starts_with(args[i], "-include")
+ || util::starts_with(args[i], "-Fp")
+ || util::starts_with(args[i], "-Yu")) {
+ const size_t path_pos = util::starts_with(args[i], "-include") ? 8 : 3;
+ if (!detect_pch(args[i].substr(0, path_pos),
+ args[i].substr(path_pos),
+ args_info.included_pch_file,
+ false,
+ state)) {
+ return Statistic::bad_compiler_arguments;
+ }
+
+ // Fall through to the next section, so intentionally not returning here.
}
- // Same as above but options with concatenated argument beginning with a
- // slash.
+ // Potentially rewrite concatenated absolute path argument to relative.
if (args[i][0] == '-') {
- size_t slash_pos = args[i].find('/');
- if (slash_pos != std::string::npos) {
- std::string option = args[i].substr(0, slash_pos);
+ const auto path_pos = Util::is_absolute_path_with_prefix(args[i]);
+ if (path_pos) {
+ const std::string option = args[i].substr(0, *path_pos);
if (compopt_takes_concat_arg(option) && compopt_takes_path(option)) {
- auto relpath =
- Util::make_relative_path(ctx, string_view(args[i]).substr(slash_pos));
+ const auto relpath = Util::make_relative_path(
+ ctx, std::string_view(args[i]).substr(*path_pos));
std::string new_option = option + relpath;
if (compopt_affects_cpp_output(option)) {
state.cpp_args.push_back(new_option);
} else {
state.common_args.push_back(new_option);
}
- return nullopt;
+ return Statistic::none;
}
}
}
}
i++;
- return nullopt;
+ return Statistic::none;
+ }
+
+ if (args[i] == "--") {
+ args_info.seen_double_dash = true;
+ return Statistic::none;
}
// Other options.
} else {
state.common_args.push_back(args[i]);
}
- return nullopt;
+ return Statistic::none;
+ }
+
+ // It was not a known option.
+ if (changed_from_slash) {
+ args[i][0] = '/';
+ }
+
+ return std::nullopt;
+}
+
+Statistic
+process_arg(const Context& ctx,
+ ArgsInfo& args_info,
+ Config& config,
+ Args& args,
+ size_t& args_index,
+ ArgumentProcessingState& state)
+{
+ const auto processed =
+ process_option_arg(ctx, args_info, config, args, args_index, state);
+ if (processed) {
+ const auto& error = *processed;
+ return error;
}
+ size_t& i = args_index;
+
// If an argument isn't a plain file then assume its an option, not an input
// file. This allows us to cope better with unusual compiler options.
//
if (!st || !st.is_regular()) {
LOG("{} is not a regular file, not considering as input file", args[i]);
state.common_args.push_back(args[i]);
- return nullopt;
+ return Statistic::none;
}
}
}
}
- // The source code file path gets put into the notes.
- if (args_info.generating_coverage) {
- args_info.input_file = args[i];
- return nullopt;
- }
-
// Rewrite to relative to increase hit rate.
+ args_info.orig_input_file = args[i];
args_info.input_file = Util::make_relative_path(ctx, args[i]);
+ args_info.normalized_input_file =
+ Util::normalize_concrete_absolute_path(args_info.input_file);
- return nullopt;
+ return Statistic::none;
}
-void
-handle_dependency_environment_variables(Context& ctx,
- ArgumentProcessingState& state)
+const char*
+get_default_object_file_extension(const Config& config)
{
- ArgsInfo& args_info = ctx.args_info;
-
- // See <http://gcc.gnu.org/onlinedocs/cpp/Environment-Variables.html>.
- // Contrary to what the documentation seems to imply the compiler still
- // creates object files with these defined (confirmed with GCC 8.2.1), i.e.
- // they work as -MMD/-MD, not -MM/-M. These environment variables do nothing
- // on Clang.
- const char* dependencies_env = getenv("DEPENDENCIES_OUTPUT");
- bool using_sunpro_dependencies = false;
- if (!dependencies_env) {
- dependencies_env = getenv("SUNPRO_DEPENDENCIES");
- using_sunpro_dependencies = true;
- }
- if (!dependencies_env) {
- return;
- }
-
- args_info.generating_dependencies = true;
- state.dependency_filename_specified = true;
-
- auto dependencies = Util::split_into_views(dependencies_env, " ");
-
- if (!dependencies.empty()) {
- auto abspath_file = dependencies[0];
- args_info.output_dep = Util::make_relative_path(ctx, abspath_file);
- }
-
- // Specifying target object is optional.
- if (dependencies.size() > 1) {
- // It's the "file target" form.
- ctx.args_info.dependency_target_specified = true;
- 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("{} {}", args_info.output_dep, relpath_obj);
- if (using_sunpro_dependencies) {
- Util::setenv("SUNPRO_DEPENDENCIES", relpath_both);
- } else {
- Util::setenv("DEPENDENCIES_OUTPUT", relpath_both);
- }
- } else {
- // It's the "file" form.
- state.dependency_implicit_target_specified = true;
- // Ensure that the compiler gets a relative path.
- if (using_sunpro_dependencies) {
- Util::setenv("SUNPRO_DEPENDENCIES", args_info.output_dep);
- } else {
- Util::setenv("DEPENDENCIES_OUTPUT", args_info.output_dep);
- }
- }
+ return config.is_compiler_group_msvc() ? ".obj" : ".o";
}
} // namespace
state.common_args.push_back(args[0]); // Compiler
- optional<Statistic> argument_error;
+ std::optional<Statistic> argument_error;
for (size_t i = 1; i < args.size(); i++) {
- const auto error = process_arg(ctx, args, i, state);
- if (error && !argument_error) {
+ const auto error = process_arg(ctx, args_info, ctx.config, args, i, state);
+ if (error != Statistic::none && !argument_error) {
argument_error = error;
}
}
+ // Bail out on too hard combinations of options.
+ if (state.found_mf_opt && state.found_wp_md_or_mmd_opt) {
+ // GCC and Clang behave differently when "-Wp,-M[M]D,wp.d" and "-MF mf.d"
+ // are used: GCC writes to wp.d but Clang writes to mf.d. We could
+ // potentially support this by behaving differently depending on the
+ // compiler type, but let's just bail out for now.
+ LOG_RAW("-Wp,-M[M]D in combination with -MF is not supported");
+ return Statistic::unsupported_compiler_option;
+ }
+
// Don't try to second guess the compiler's heuristics for stdout handling.
if (args_info.output_obj == "-") {
LOG_RAW("Output file is -");
}
// Determine output object file.
- const bool implicit_output_obj = args_info.output_obj.empty();
- if (implicit_output_obj && !args_info.input_file.empty()) {
- string_view extension = state.found_S_opt ? ".s" : ".o";
- args_info.output_obj =
+ bool output_obj_by_source = args_info.output_obj.empty();
+ if (!output_obj_by_source && ctx.config.is_compiler_group_msvc()) {
+ if (*args_info.output_obj.rbegin() == '\\') {
+ output_obj_by_source = true;
+ } else {
+ auto st = Stat::stat(args_info.output_obj);
+ if (st && st.is_directory()) {
+ args_info.output_obj.append("\\");
+ output_obj_by_source = true;
+ }
+ }
+ }
+
+ if (output_obj_by_source && !args_info.input_file.empty()) {
+ std::string_view extension =
+ state.found_S_opt ? ".s" : get_default_object_file_extension(ctx.config);
+ args_info.output_obj +=
Util::change_extension(Util::base_name(args_info.input_file), extension);
}
+ args_info.orig_output_obj = args_info.output_obj;
+ args_info.output_obj = Util::make_relative_path(ctx, args_info.output_obj);
+
+ // Determine output dependency file.
+
// On argument processing error, return now since we have determined
// args_info.output_obj which is needed to determine the log filename in
// CCACHE_DEBUG mode.
}
#endif
- handle_dependency_environment_variables(ctx, state);
-
if (args_info.input_file.empty()) {
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)) {
+ if (!(config.sloppiness().is_enabled(core::Sloppy::time_macros))) {
LOG_RAW(
"You have to specify \"time_macros\" sloppiness when using"
" precompiled headers to get direct hits");
args_info.actual_language.find("-header") != std::string::npos
|| Util::is_precompiled_header(args_info.output_obj);
- if (args_info.output_is_precompiled_header && implicit_output_obj) {
- args_info.output_obj = args_info.input_file + ".gch";
+ if (args_info.output_is_precompiled_header && output_obj_by_source) {
+ args_info.orig_output_obj = args_info.orig_input_file + ".gch";
+ args_info.output_obj =
+ Util::make_relative_path(ctx, args_info.orig_output_obj);
}
if (args_info.output_is_precompiled_header
- && !(config.sloppiness() & SLOPPY_PCH_DEFINES)) {
+ && !(config.sloppiness().is_enabled(core::Sloppy::pch_defines))) {
LOG_RAW(
"You have to specify \"pch_defines,time_macros\" sloppiness when"
" creating precompiled headers");
return Statistic::could_not_use_precompiled_header;
}
- if (!state.found_c_opt && !state.found_dc_opt && !state.found_S_opt) {
+ // -fsyntax-only/-Zs does not need -c
+ if (!state.found_c_opt && !state.found_dc_opt && !state.found_S_opt
+ && !state.found_syntax_only) {
if (args_info.output_is_precompiled_header) {
state.common_args.push_back("-c");
} else {
return Statistic::unsupported_source_language;
}
+ if (args_info.actual_language == "assembler") {
+ // -MD/-MMD for assembler file does not produce a dependency file.
+ args_info.generating_dependencies = false;
+ }
+
if (!config.run_second_cpp()
&& (args_info.actual_language == "cu"
|| args_info.actual_language == "cuda")) {
// Some options shouldn't be passed to the real compiler when it compiles
// preprocessed code:
//
- // -finput-charset=XXX (otherwise conversion happens twice)
- // -x XXX (otherwise the wrong language is selected)
+ // -finput-charset=CHARSET (otherwise conversion happens twice)
+ // -x CHARSET (otherwise the wrong language is selected)
if (!state.input_charset_option.empty()) {
state.cpp_args.push_back(state.input_charset_option);
}
- if (state.found_pch) {
+ if (state.found_pch && ctx.config.compiler_type() != CompilerType::msvc) {
state.cpp_args.push_back("-fpch-preprocess");
}
if (!state.explicit_language.empty()) {
// Since output is redirected, compilers will not color their output by
// default, so force it explicitly.
- nonstd::optional<std::string> diagnostics_color_arg;
- if (config.compiler_type() == CompilerType::clang) {
+ std::optional<std::string> diagnostics_color_arg;
+ if (config.is_compiler_group_clang()) {
// Don't pass -fcolor-diagnostics when compiling assembler to avoid an
// "argument unused during compilation" warning.
if (args_info.actual_language != "assembler") {
}
if (args_info.generating_dependencies) {
- if (!state.dependency_filename_specified) {
- auto default_depfile_name =
- Util::change_extension(args_info.output_obj, ".d");
- args_info.output_dep =
- Util::make_relative_path(ctx, default_depfile_name);
+ if (state.output_dep_origin == OutputDepOrigin::none) {
+ args_info.output_dep = Util::change_extension(args_info.output_obj, ".d");
if (!config.run_second_cpp()) {
// If we're compiling preprocessed code we're sending dep_args to the
// preprocessor so we need to use -MF to write to the correct .d file
// location since the preprocessor doesn't know the final object path.
state.dep_args.push_back("-MF");
- state.dep_args.push_back(default_depfile_name);
+ state.dep_args.push_back(args_info.output_dep);
}
}
- if (!ctx.args_info.dependency_target_specified
- && !state.dependency_implicit_target_specified
- && !config.run_second_cpp()) {
+ if (!args_info.dependency_target && !config.run_second_cpp()) {
// If we're compiling preprocessed code we're sending dep_args to the
// preprocessor so we need to use -MQ to get the correct target object
// file in the .d file.
state.dep_args.push_back("-MQ");
state.dep_args.push_back(args_info.output_obj);
}
+
+ if (!args_info.dependency_target) {
+ std::string dep_target = args_info.orig_output_obj;
+
+ // GCC and Clang behave differently when "-Wp,-M[M]D,wp.d" is used with
+ // "-o" but with neither "-MMD" nor "-MT"/"-MQ": GCC uses a dependency
+ // target based on the source filename but Clang bases it on the output
+ // filename.
+ if (state.found_wp_md_or_mmd_opt && !args_info.output_obj.empty()
+ && !state.found_md_or_mmd_opt) {
+ if (config.compiler_type() == CompilerType::clang) {
+ // Clang does the sane thing: the dependency target is the output file
+ // so that the dependency file actually makes sense.
+ } else if (config.compiler_type() == CompilerType::gcc) {
+ // GCC strangely uses the base name of the source file but with a .o
+ // extension.
+ dep_target = Util::change_extension(
+ Util::base_name(args_info.orig_input_file),
+ get_default_object_file_extension(ctx.config));
+ } else {
+ // How other compilers behave is currently unknown, so bail out.
+ LOG_RAW(
+ "-Wp,-M[M]D with -o without -MMD, -MQ or -MT is only supported for"
+ " GCC or Clang");
+ return Statistic::unsupported_compiler_option;
+ }
+ }
+
+ args_info.dependency_target = Depfile::escape_filename(dep_target);
+ }
}
if (args_info.generating_stackusage) {
compiler_args.push_back("-dc");
}
+ if (state.found_xarch_arch && !args_info.arch_args.empty()) {
+ if (args_info.arch_args.size() > 1) {
+ LOG_RAW(
+ "Multiple -arch options in combination with -Xarch_* not supported");
+ return Statistic::unsupported_compiler_option;
+ } else if (args_info.arch_args[0] != *state.found_xarch_arch) {
+ LOG_RAW("-arch option not matching -Xarch_* option not supported");
+ return Statistic::unsupported_compiler_option;
+ }
+ }
+
for (const auto& arch : args_info.arch_args) {
compiler_args.push_back("-arch");
compiler_args.push_back(arch);
}
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);
+ args_info.depend_extra_args.push_back(*diagnostics_color_arg);
}
}
- return {preprocessor_args, extra_args_to_hash, compiler_args};
+ if (ctx.config.depend_mode() && !args_info.generating_includes
+ && ctx.config.compiler_type() == CompilerType::msvc) {
+ ctx.auto_depend_mode = true;
+ args_info.generating_includes = true;
+ args_info.depend_extra_args.push_back("/showIncludes");
+ }
+
+ return {
+ preprocessor_args,
+ extra_args_to_hash,
+ compiler_args,
+ state.hash_actual_cwd,
+ };
}
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
#include "Args.hpp"
-#include "Statistic.hpp"
-#include "third_party/nonstd/optional.hpp"
+#include <core/Statistic.hpp>
+
+#include <optional>
class Context;
struct ProcessArgsResult
{
- ProcessArgsResult(Statistic error_);
+ ProcessArgsResult(core::Statistic error_);
ProcessArgsResult(const Args& preprocessor_args_,
const Args& extra_args_to_hash_,
- const Args& compiler_args_);
+ const Args& compiler_args_,
+ bool hash_actual_cwd_);
// nullopt on success, otherwise the statistics counter that should be
// incremented.
- nonstd::optional<Statistic> error;
+ std::optional<core::Statistic> error;
// Arguments (except -E) to send to the preprocessor.
Args preprocessor_args;
// Arguments to send to the real compiler.
Args compiler_args;
+
+ // Whether to include the actual CWD in the hash.
+ bool hash_actual_cwd;
};
-inline ProcessArgsResult::ProcessArgsResult(Statistic error_) : error(error_)
+inline ProcessArgsResult::ProcessArgsResult(core::Statistic error_)
+ : error(error_)
{
}
inline ProcessArgsResult::ProcessArgsResult(const Args& preprocessor_args_,
const Args& extra_args_to_hash_,
- const Args& compiler_args_)
+ const Args& compiler_args_,
+ bool hash_actual_cwd_)
: preprocessor_args(preprocessor_args_),
extra_args_to_hash(extra_args_to_hash_),
- compiler_args(compiler_args_)
+ compiler_args(compiler_args_),
+ hash_actual_cwd(hash_actual_cwd_)
{
}
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
+#include <cstddef>
#ifdef _MSC_VER
# define CCACHE_FUNCTION __func__
// Copyright (C) 2002-2007 Andrew Tridgell
-// Copyright (C) 2009-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Args.hpp"
#include "ArgsInfo.hpp"
-#include "Checksum.hpp"
-#include "Compression.hpp"
#include "Context.hpp"
#include "Depfile.hpp"
#include "Fd.hpp"
#include "File.hpp"
#include "Finalizer.hpp"
-#include "FormatNonstdStringView.hpp"
#include "Hash.hpp"
-#include "Lockfile.hpp"
#include "Logging.hpp"
-#include "Manifest.hpp"
#include "MiniTrace.hpp"
-#include "ProgressBar.hpp"
-#include "Result.hpp"
-#include "ResultDumper.hpp"
-#include "ResultExtractor.hpp"
-#include "ResultRetriever.hpp"
#include "SignalHandler.hpp"
-#include "Statistics.hpp"
-#include "StdMakeUnique.hpp"
#include "TemporaryFile.hpp"
#include "UmaskScope.hpp"
#include "Util.hpp"
+#include "Win32Util.hpp"
#include "argprocessing.hpp"
-#include "cleanup.hpp"
#include "compopt.hpp"
-#include "compress.hpp"
-#include "exceptions.hpp"
#include "execute.hpp"
#include "fmtmacros.hpp"
#include "hashutil.hpp"
#include "language.hpp"
+#include <AtomicFile.hpp>
+#include <core/CacheEntry.hpp>
+#include <core/Manifest.hpp>
+#include <core/MsvcShowIncludesOutput.hpp>
+#include <core/Result.hpp>
+#include <core/ResultRetriever.hpp>
+#include <core/Statistics.hpp>
+#include <core/StatsLog.hpp>
+#include <core/exceptions.hpp>
+#include <core/mainoptions.hpp>
+#include <core/types.hpp>
+#include <core/wincompat.hpp>
+#include <storage/Storage.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+#include <util/path.hpp>
+#include <util/string.hpp>
+
#include "third_party/fmt/core.h"
-#include "third_party/nonstd/optional.hpp"
-#include "third_party/nonstd/string_view.hpp"
-#ifdef HAVE_GETOPT_LONG
-# include <getopt.h>
-#elif defined(_WIN32)
-# include "third_party/win32/getopt.h"
-#else
-extern "C" {
-# include "third_party/getopt_long.h"
-}
-#endif
+#include <fcntl.h>
-#ifdef _WIN32
-# include "Win32Util.hpp"
+#include <optional>
+#include <string_view>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
#endif
#include <algorithm>
#include <cmath>
#include <limits>
+#include <memory>
-#ifndef MYNAME
-# define MYNAME "ccache"
-#endif
-const char CCACHE_NAME[] = MYNAME;
-
-using nonstd::nullopt;
-using nonstd::optional;
-using nonstd::string_view;
-
-constexpr const char VERSION_TEXT[] =
- R"({} version {}
-
-Copyright (C) 2002-2007 Andrew Tridgell
-Copyright (C) 2009-2021 Joel Rosdahl and other contributors
-
-See <https://ccache.dev/credits.html> 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.
-)";
-
-constexpr const char USAGE_TEXT[] =
- R"(Usage:
- {} [options]
- {} compiler [compiler options]
- compiler [compiler options] (via symbolic link)
-
-Common options:
- -c, --cleanup delete old files and recalculate size counters
- (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
- with a d (days) or s (seconds) suffix)
- -F, --max-files NUM set maximum number of files in cache to NUM (use
- 0 for no limit)
- -M, --max-size SIZE set maximum size of cache to SIZE (use 0 for no
- limit); available suffixes: k, M, G, T (decimal)
- and Ki, Mi, Gi, Ti (binary); default suffix: G
- -X, --recompress LEVEL recompress the cache to level LEVEL (integer or
- "uncompressed") using the Zstandard algorithm;
- see "Cache compression" in the manual for details
- -o, --set-config KEY=VAL set configuration item KEY to value VAL
- -x, --show-compression show compression statistics
- -p, --show-config show current configuration options in
- human-readable format
- -s, --show-stats show summary of configuration and statistics
- counters in human-readable format
- -z, --zero-stats zero statistics counters
-
- -h, --help print this help text
- -V, --version print version and copyright information
-
-Options for scripting or debugging:
- --checksum-file PATH print the checksum (64 bit XXH3) of the file at
- PATH
- --dump-manifest PATH dump manifest file at PATH in text format
- --dump-result PATH dump result file at PATH in text format
- --extract-result PATH extract data stored in result file at PATH to the
- current working directory
- -k, --get-config KEY print the value of configuration key KEY
- --hash-file PATH print the hash (160 bit BLAKE3) of the file at
- PATH
- --print-stats print statistics counter IDs and corresponding
- values in machine-parsable format
-
-See also the manual on <https://ccache.dev/documentation.html>.
-)";
-
-// How often (in seconds) to scan $CCACHE_DIR/tmp for left-over temporary
-// files.
-const int k_tempdir_cleanup_interval = 2 * 24 * 60 * 60; // 2 days
-
-// Maximum files per cache directory. This constant is somewhat arbitrarily
-// chosen to be large enough to avoid unnecessary cache levels but small enough
-// not to make esoteric file systems (with bad performance for large
-// directories) too slow. It could be made configurable, but hopefully there
-// will be no need to do that.
-const uint64_t k_max_cache_files_per_directory = 2000;
-
-// Minimum number of cache levels ($CCACHE_DIR/1/2/stored_file).
-const uint8_t k_min_cache_levels = 2;
-
-// Maximum number of cache levels ($CCACHE_DIR/1/2/3/stored_file).
-//
-// On a cache miss, (k_max_cache_levels - k_min_cache_levels + 1) cache lookups
-// (i.e. stat system calls) will be performed for a cache entry.
-//
-// An assumption made here is that if a cache is so large that it holds more
-// than 16^4 * k_max_cache_files_per_directory files then we can assume that the
-// file system is sane enough to handle more than
-// k_max_cache_files_per_directory.
-const uint8_t k_max_cache_levels = 4;
+using core::Statistic;
// This is a string that identifies the current "version" of the hash sum
// computed by ccache. If, for any reason, we want to force the hash sum to be
// different for the same input in a new ccache version, we can just change
// this string. A typical example would be if the format of one of the files
// stored in the cache changes in a backwards-incompatible way.
-const char HASH_PREFIX[] = "3";
+const char HASH_PREFIX[] = "4";
namespace {
-// Throw a Failure if ccache did not succeed in getting or putting a result in
-// the cache. If `exit_code` is set, just exit with that code directly,
-// otherwise execute the real compiler and exit with its exit code. Also updates
-// statistics counter `statistic` if it's not `Statistic::none`.
-class Failure : public std::exception
+// Return nonstd::make_unexpected<Failure> if ccache did not succeed in getting
+// or putting a result in the cache. If `exit_code` is set, ccache will just
+// exit with that code directly, otherwise execute the real compiler and exit
+// with its exit code. Statistics counters will also be incremented.
+class Failure
{
public:
- Failure(Statistic statistic,
- nonstd::optional<int> exit_code = nonstd::nullopt);
+ Failure(Statistic statistic);
+ Failure(std::initializer_list<Statistic> statistics);
- nonstd::optional<int> exit_code() const;
- Statistic statistic() const;
+ const core::StatisticsCounters& counters() const;
+ std::optional<int> exit_code() const;
+ void set_exit_code(int exit_code);
private:
- Statistic m_statistic;
- nonstd::optional<int> m_exit_code;
+ core::StatisticsCounters m_counters;
+ std::optional<int> m_exit_code;
};
-inline Failure::Failure(Statistic statistic, nonstd::optional<int> exit_code)
- : m_statistic(statistic),
- m_exit_code(exit_code)
+inline Failure::Failure(const Statistic statistic) : m_counters({statistic})
+{
+}
+
+inline Failure::Failure(const std::initializer_list<Statistic> statistics)
+ : m_counters(statistics)
+{
+}
+
+inline const core::StatisticsCounters&
+Failure::counters() const
{
+ return m_counters;
}
-inline nonstd::optional<int>
+inline std::optional<int>
Failure::exit_code() const
{
return m_exit_code;
}
-inline Statistic
-Failure::statistic() const
+inline void
+Failure::set_exit_code(const int exit_code)
{
- return m_statistic;
+ m_exit_code = exit_code;
}
} // namespace
Args prefix;
for (const auto& word : Util::split_into_strings(prefix_command, " ")) {
- std::string path = find_executable(ctx, word, CCACHE_NAME);
+ std::string path = find_executable(ctx, word, ctx.orig_args[0]);
if (path.empty()) {
- throw Fatal("{}: {}", word, strerror(errno));
+ throw core::Fatal(FMT("{}: {}", word, strerror(errno)));
}
prefix.push_back(path);
}
}
-static void
-clean_up_internal_tempdir(const Config& config)
-{
- time_t now = time(nullptr);
- auto dir_st = Stat::stat(config.cache_dir(), Stat::OnError::log);
- if (!dir_st || dir_st.mtime() + k_tempdir_cleanup_interval >= now) {
- // No cleanup needed.
- return;
- }
-
- Util::update_mtime(config.cache_dir());
-
- const std::string& temp_dir = config.temporary_dir();
- if (!Stat::lstat(temp_dir)) {
- return;
- }
-
- Util::traverse(temp_dir, [now](const std::string& path, bool is_dir) {
- if (is_dir) {
- return;
- }
- auto st = Stat::lstat(path, Stat::OnError::log);
- if (st && st.mtime() + k_tempdir_cleanup_interval < now) {
- Util::unlink_tmp(path);
- }
- });
-}
-
static std::string
prepare_debug_path(const std::string& debug_dir,
+ const util::TimePoint& time_of_invocation,
const std::string& output_obj,
- string_view suffix)
+ std::string_view suffix)
{
- const std::string prefix =
- debug_dir.empty() ? output_obj : debug_dir + Util::real_path(output_obj);
- try {
- Util::ensure_dir_exists(Util::dir_name(prefix));
- } catch (Error&) {
- // Ignore since we can't handle an error in another way in this context. The
- // caller takes care of logging when trying to open the path for writing.
- }
- return FMT("{}.ccache-{}", prefix, suffix);
+ auto prefix = debug_dir.empty()
+ ? output_obj
+ : debug_dir + util::to_absolute_path_no_drive(output_obj);
+
+ // Ignore any error from create_dir since we can't handle an error in another
+ // way in this context. The caller takes care of logging when trying to open
+ // the path for writing.
+ Util::create_dir(Util::dir_name(prefix));
+
+ char timestamp[100];
+ const auto tm = Util::localtime(time_of_invocation);
+ if (tm) {
+ strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &*tm);
+ } else {
+ snprintf(timestamp,
+ sizeof(timestamp),
+ "%llu",
+ static_cast<long long unsigned int>(time_of_invocation.sec()));
+ }
+ return FMT("{}.{}_{:06}.ccache-{}",
+ prefix,
+ timestamp,
+ time_of_invocation.nsec_decimal_part() / 1000,
+ suffix);
}
static void
init_hash_debug(Context& ctx,
Hash& hash,
char type,
- string_view section_name,
+ std::string_view section_name,
FILE* debug_text_file)
{
if (!ctx.config.debug()) {
return;
}
- const auto path = prepare_debug_path(
- ctx.config.debug_dir(), ctx.args_info.output_obj, FMT("input-{}", type));
+ const auto path = prepare_debug_path(ctx.config.debug_dir(),
+ ctx.time_of_invocation,
+ ctx.args_info.output_obj,
+ FMT("input-{}", type));
File debug_binary_file(path, "wb");
if (debug_binary_file) {
hash.enable_debug(section_name, debug_binary_file.get(), debug_text_file);
}
CompilerType
-guess_compiler(string_view path)
+guess_compiler(std::string_view path)
{
std::string compiler_path(path);
if (symlink_value.empty()) {
break;
}
- if (Util::is_absolute_path(symlink_value)) {
+ if (util::is_absolute_path(symlink_value)) {
compiler_path = symlink_value;
} else {
compiler_path =
}
#endif
- const string_view name = Util::base_name(compiler_path);
- if (name.find("clang") != nonstd::string_view::npos) {
+ const auto name =
+ Util::to_lowercase(Util::remove_extension(Util::base_name(compiler_path)));
+ if (name.find("clang-cl") != std::string_view::npos) {
+ return CompilerType::clang_cl;
+ } else if (name.find("clang") != std::string_view::npos) {
return CompilerType::clang;
- } else if (name.find("gcc") != nonstd::string_view::npos
- || name.find("g++") != nonstd::string_view::npos) {
+ } else if (name.find("gcc") != std::string_view::npos
+ || name.find("g++") != std::string_view::npos) {
return CompilerType::gcc;
- } else if (name.find("nvcc") != nonstd::string_view::npos) {
+ } else if (name.find("nvcc") != std::string_view::npos) {
return CompilerType::nvcc;
- } else if (name == "pump" || name == "distcc-pump") {
- return CompilerType::pump;
+ } else if (name == "icl") {
+ return CompilerType::icl;
+ } else if (name == "cl") {
+ return CompilerType::msvc;
} else {
return CompilerType::other;
}
// The comparison using >= is intentional, due to a possible race between
// starting compilation and writing the include file. See also the notes under
// "Performance" in doc/MANUAL.adoc.
- if (!(ctx.config.sloppiness() & SLOPPY_INCLUDE_FILE_MTIME)
+ if (!(ctx.config.sloppiness().is_enabled(core::Sloppy::include_file_mtime))
&& path_stat.mtime() >= ctx.time_of_compilation) {
LOG("Include file {} too new", path);
return true;
}
// The same >= logic as above applies to the change time of the file.
- if (!(ctx.config.sloppiness() & SLOPPY_INCLUDE_FILE_CTIME)
+ if (!(ctx.config.sloppiness().is_enabled(core::Sloppy::include_file_ctime))
&& path_stat.ctime() >= ctx.time_of_compilation) {
LOG("Include file {} ctime too new", path);
return true;
return true;
}
- if (path == ctx.args_info.input_file) {
+ if (path == ctx.args_info.normalized_input_file) {
// Don't remember the input file.
return true;
}
- if (system && (ctx.config.sloppiness() & SLOPPY_SYSTEM_HEADERS)) {
+ if (system
+ && (ctx.config.sloppiness().is_enabled(core::Sloppy::system_headers))) {
// Don't remember this system header.
return true;
}
// Canonicalize path for comparison; Clang uses ./header.h.
- if (Util::starts_with(path, "./")) {
+ if (util::starts_with(path, "./")) {
path.erase(0, 2);
}
}
// Let's hash the include file content.
- Hash fhash;
+ Digest file_digest;
if (is_pch) {
- if (ctx.included_pch_file.empty()) {
+ if (ctx.args_info.included_pch_file.empty()) {
LOG("Detected use of precompiled header: {}", path);
}
bool using_pch_sum = false;
}
}
- if (!hash_binary_file(ctx, fhash, path)) {
+ if (!hash_binary_file(ctx, file_digest, path)) {
return false;
}
cpp_hash.hash_delimiter(using_pch_sum ? "pch_sum_hash" : "pch_hash");
- cpp_hash.hash(fhash.digest().to_string());
+ cpp_hash.hash(file_digest.to_string());
}
if (ctx.config.direct_mode()) {
if (!is_pch) { // else: the file has already been hashed.
- int result = hash_source_code_file(ctx, fhash, path);
+ int result = hash_source_code_file(ctx, file_digest, path);
if (result & HASH_SOURCE_CODE_ERROR
|| result & HASH_SOURCE_CODE_FOUND_TIME) {
return false;
}
}
- Digest d = fhash.digest();
- ctx.included_files.emplace(path, d);
+ ctx.included_files.emplace(path, file_digest);
if (depend_mode_hash) {
depend_mode_hash->hash_delimiter("include");
- depend_mode_hash->hash(d.to_string());
+ depend_mode_hash->hash(file_digest.to_string());
}
}
static void
print_included_files(const Context& ctx, FILE* fp)
{
- for (const auto& item : ctx.included_files) {
- PRINT(fp, "{}\n", item.first);
+ for (const auto& [path, digest] : ctx.included_files) {
+ PRINT(fp, "{}\n", path);
}
}
// - Makes include file paths for which the base directory is a prefix relative
// when computing the hash sum.
// - Stores the paths and hashes of included files in ctx.included_files.
-//
-// Returns Statistic::none on success, otherwise a statistics counter to be
-// incremented.
-static Statistic
-process_preprocessed_file(Context& ctx,
- Hash& hash,
- const std::string& path,
- bool pump)
+static nonstd::expected<void, Failure>
+process_preprocessed_file(Context& ctx, Hash& hash, const std::string& path)
{
- std::string data;
- try {
- data = Util::read_file(path);
- } catch (Error&) {
- return Statistic::internal_error;
+ auto data = util::read_file<std::string>(path);
+ if (!data) {
+ LOG("Failed to read {}: {}", path, data.error());
+ return nonstd::make_unexpected(Statistic::internal_error);
}
// Bytes between p and q are pending to be hashed.
- const char* p = &data[0];
- char* q = &data[0];
- const char* end = p + data.length();
+ char* q = &(*data)[0];
+ const char* p = q;
+ const char* end = p + data->length();
// There must be at least 7 characters (# 1 "x") left to potentially find an
// include file path.
while (q < end - 7) {
- static const string_view pragma_gcc_pch_preprocess =
+ static const std::string_view pragma_gcc_pch_preprocess =
"pragma GCC pch_preprocess ";
- static const string_view hash_31_command_line_newline =
+ static const std::string_view hash_31_command_line_newline =
"# 31 \"<command-line>\"\n";
- static const string_view hash_32_command_line_2_newline =
+ static const std::string_view hash_32_command_line_2_newline =
"# 32 \"<command-line>\" 2\n";
+ // Note: Intentionally not using the string form to avoid false positive
+ // match by ccache itself.
+ static const char incbin_directive[] = {'.', 'i', 'n', 'c', 'b', 'i', 'n'};
// Check if we look at a line containing the file name of an included file.
// At least the following formats exist (where N is a positive integer):
// GCC:
&& ((q[1] == ' ' && q[2] >= '0' && q[2] <= '9')
// GCC precompiled header:
- || Util::starts_with(&q[1], pragma_gcc_pch_preprocess)
+ || util::starts_with(&q[1], pragma_gcc_pch_preprocess)
// HP/AIX:
|| (q[1] == 'l' && q[2] == 'i' && q[3] == 'n' && q[4] == 'e'
&& q[5] == ' '))
- && (q == data.data() || q[-1] == '\n')) {
+ && (q == data->data() || q[-1] == '\n')) {
// Workarounds for preprocessor linemarker bugs in GCC version 6.
if (q[2] == '3') {
- if (Util::starts_with(q, hash_31_command_line_newline)) {
+ if (util::starts_with(q, hash_31_command_line_newline)) {
// Bogus extra line with #31, after the regular #1: Ignore the whole
// line, and continue parsing.
hash.hash(p, q - p);
q++;
p = q;
continue;
- } else if (Util::starts_with(q, hash_32_command_line_2_newline)) {
+ } else if (util::starts_with(q, hash_32_command_line_2_newline)) {
// Bogus wrong line with #32, instead of regular #1: Replace the line
// number with the usual one.
hash.hash(p, q - p);
q++;
if (q >= end) {
LOG_RAW("Failed to parse included file path");
- return Statistic::internal_error;
+ return nonstd::make_unexpected(Statistic::internal_error);
}
// q points to the beginning of an include file path
hash.hash(p, q - p);
while (q < end && *q != '"') {
q++;
}
+ if (p == q) {
+ // Skip empty file name.
+ continue;
+ }
// Look for preprocessor flags, after the "filename".
bool system = false;
const char* r = q + 1;
}
// p and q span the include file path.
std::string inc_path(p, q - p);
- if (!ctx.has_absolute_include_headers) {
- ctx.has_absolute_include_headers = Util::is_absolute_path(inc_path);
- }
+ inc_path = Util::normalize_concrete_absolute_path(inc_path);
inc_path = Util::make_relative_path(ctx, inc_path);
- bool should_hash_inc_path = true;
- if (!ctx.config.hash_dir()) {
- if (Util::starts_with(inc_path, ctx.apparent_cwd)
- && Util::ends_with(inc_path, "//")) {
- // When compiling with -g or similar, GCC adds the absolute path to
- // CWD like this:
- //
- // # 1 "CWD//"
- //
- // If the user has opted out of including the CWD in the hash, don't
- // hash it. See also how debug_prefix_map is handled.
- should_hash_inc_path = false;
- }
- }
- if (should_hash_inc_path) {
+ if ((inc_path != ctx.apparent_cwd) || ctx.config.hash_dir()) {
hash.hash(inc_path);
}
if (remember_include_file(ctx, inc_path, hash, system, nullptr)
== RememberIncludeFileResult::cannot_use_pch) {
- return Statistic::could_not_use_precompiled_header;
+ return nonstd::make_unexpected(
+ Statistic::could_not_use_precompiled_header);
}
p = q; // Everything of interest between p and q has been hashed now.
- } else if (q[0] == '.' && q[1] == 'i' && q[2] == 'n' && q[3] == 'c'
- && q[4] == 'b' && q[5] == 'i' && q[6] == 'n') {
+ } else if (strncmp(q, incbin_directive, sizeof(incbin_directive)) == 0
+ && ((q[7] == ' '
+ && (q[8] == '"' || (q[8] == '\\' && q[9] == '"')))
+ || q[7] == '"')) {
// An assembler .inc bin (without the space) statement, which could be
// 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_RAW(
- "Found unsupported .inc"
+ "Found potential unsupported .inc"
"bin directive in source code");
- throw Failure(Statistic::unsupported_code_directive);
- } else if (pump && strncmp(q, "_________", 9) == 0) {
+ return nonstd::make_unexpected(
+ Failure(Statistic::unsupported_code_directive));
+ } else if (strncmp(q, "___________", 10) == 0
+ && (q == data->data() || q[-1] == '\n')) {
// Unfortunately the distcc-pump wrapper outputs standard output lines:
// __________Using distcc-pump from /usr/bin
// __________Using # distcc servers in pump mode
// __________Shutting down distcc-pump include server
+ hash.hash(p, q - p);
while (q < end && *q != '\n') {
q++;
}
// Explicitly check the .gch/.pch/.pth file as Clang does not include any
// mention of it in the preprocessed output.
- if (!ctx.included_pch_file.empty()) {
- std::string pch_path = Util::make_relative_path(ctx, ctx.included_pch_file);
+ if (!ctx.args_info.included_pch_file.empty()) {
+ std::string pch_path =
+ Util::make_relative_path(ctx, ctx.args_info.included_pch_file);
hash.hash(pch_path);
remember_include_file(ctx, pch_path, hash, false, nullptr);
}
print_included_files(ctx, stdout);
}
- return Statistic::none;
+ return {};
}
// Extract the used includes from the dependency file. Note that we cannot
// distinguish system headers from other includes here.
-static optional<Digest>
-result_name_from_depfile(Context& ctx, Hash& hash)
+static std::optional<Digest>
+result_key_from_depfile(Context& ctx, Hash& hash)
{
- std::string file_content;
- try {
- file_content = Util::read_file(ctx.args_info.output_dep);
- } catch (const Error& e) {
- LOG(
- "Cannot open dependency file {}: {}", ctx.args_info.output_dep, e.what());
- return nullopt;
+ const auto file_content =
+ util::read_file<std::string>(ctx.args_info.output_dep);
+ if (!file_content) {
+ LOG("Failed to read dependency file {}: {}",
+ ctx.args_info.output_dep,
+ file_content.error());
+ return std::nullopt;
}
- for (string_view token : Depfile::tokenize(file_content)) {
- if (token.ends_with(":")) {
+ for (std::string_view token : Depfile::tokenize(*file_content)) {
+ if (util::ends_with(token, ":")) {
continue;
}
- if (!ctx.has_absolute_include_headers) {
- ctx.has_absolute_include_headers = Util::is_absolute_path(token);
- }
std::string path = Util::make_relative_path(ctx, token);
remember_include_file(ctx, path, hash, false, &hash);
}
// Explicitly check the .gch/.pch/.pth file as it may not be mentioned in the
// dependencies output.
- if (!ctx.included_pch_file.empty()) {
- std::string pch_path = Util::make_relative_path(ctx, ctx.included_pch_file);
+ if (!ctx.args_info.included_pch_file.empty()) {
+ std::string pch_path =
+ Util::make_relative_path(ctx, ctx.args_info.included_pch_file);
hash.hash(pch_path);
remember_include_file(ctx, pch_path, hash, false, nullptr);
}
return hash.digest();
}
+
+struct GetTmpFdResult
+{
+ Fd fd;
+ std::string path;
+};
+
+static GetTmpFdResult
+get_tmp_fd(Context& ctx,
+ const std::string_view description,
+ const bool capture_output)
+{
+ if (capture_output) {
+ TemporaryFile tmp_stdout(
+ FMT("{}/{}", ctx.config.temporary_dir(), description));
+ ctx.register_pending_tmp_file(tmp_stdout.path);
+ return {std::move(tmp_stdout.fd), std::move(tmp_stdout.path)};
+ } else {
+ const auto dev_null_path = util::get_dev_null_path();
+ return {Fd(open(dev_null_path, O_WRONLY | O_BINARY)), dev_null_path};
+ }
+}
+
+struct DoExecuteResult
+{
+ int exit_status;
+ util::Bytes stdout_data;
+ util::Bytes stderr_data;
+};
+
+// Extract the used includes from /showIncludes output in stdout. Note that we
+// cannot distinguish system headers from other includes here.
+static std::optional<Digest>
+result_key_from_includes(Context& ctx, Hash& hash, std::string_view stdout_data)
+{
+ for (std::string_view token : core::MsvcShowIncludesOutput::get_includes(
+ stdout_data, ctx.config.msvc_dep_prefix())) {
+ const std::string path = Util::make_relative_path(ctx, token);
+ remember_include_file(ctx, path, hash, false, &hash);
+ }
+
+ // Explicitly check the .pch file as it is not mentioned in the
+ // includes output.
+ if (!ctx.args_info.included_pch_file.empty()) {
+ std::string pch_path =
+ Util::make_relative_path(ctx, ctx.args_info.included_pch_file);
+ hash.hash(pch_path);
+ remember_include_file(ctx, pch_path, hash, false, nullptr);
+ }
+
+ const bool debug_included = getenv("CCACHE_DEBUG_INCLUDED");
+ if (debug_included) {
+ print_included_files(ctx, stdout);
+ }
+
+ return hash.digest();
+}
+
// Execute the compiler/preprocessor, with logic to retry without requesting
// colored diagnostics messages if that fails.
-static int
-do_execute(Context& ctx,
- Args& args,
- TemporaryFile&& tmp_stdout,
- TemporaryFile&& tmp_stderr)
+static nonstd::expected<DoExecuteResult, Failure>
+do_execute(Context& ctx, Args& args, const bool capture_stdout = true)
{
UmaskScope umask_scope(ctx.original_umask);
DEBUG_ASSERT(ctx.config.compiler_type() == CompilerType::gcc);
args.erase_last("-fdiagnostics-color");
}
+
+ auto tmp_stdout = get_tmp_fd(ctx, "stdout", capture_stdout);
+ auto tmp_stderr = get_tmp_fd(ctx, "stderr", true);
+
int status = execute(ctx,
args.to_argv().data(),
std::move(tmp_stdout.fd),
std::move(tmp_stderr.fd));
if (status != 0 && !ctx.diagnostics_color_failed
&& ctx.config.compiler_type() == CompilerType::gcc) {
- auto errors = Util::read_file(tmp_stderr.path);
- if (errors.find("fdiagnostics-color") != std::string::npos) {
+ const auto errors = util::read_file<std::string>(tmp_stderr.path);
+ if (errors && 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
// 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));
- 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));
- throw Failure(Statistic::internal_error);
- }
-
ctx.diagnostics_color_failed = true;
- return do_execute(
- ctx, args, std::move(tmp_stdout), std::move(tmp_stderr));
+ return do_execute(ctx, args, capture_stdout);
}
}
- return status;
-}
-
-struct LookUpCacheFileResult
-{
- std::string path;
- Stat stat;
- uint8_t level;
-};
-
-static LookUpCacheFileResult
-look_up_cache_file(const std::string& cache_dir,
- const Digest& name,
- nonstd::string_view suffix)
-{
- const auto name_string = FMT("{}{}", name.to_string(), suffix);
- for (uint8_t level = k_min_cache_levels; level <= k_max_cache_levels;
- ++level) {
- const auto path = Util::get_path_in_cache(cache_dir, level, name_string);
- const auto stat = Stat::stat(path);
- if (stat) {
- return {path, stat, level};
+ util::Bytes stdout_data;
+ if (capture_stdout) {
+ auto stdout_data_result = util::read_file<util::Bytes>(tmp_stdout.path);
+ if (!stdout_data_result) {
+ LOG("Failed to read {} (cleanup in progress?): {}",
+ tmp_stdout.path,
+ stdout_data_result.error());
+ return nonstd::make_unexpected(Statistic::missing_cache_file);
}
+ stdout_data = *stdout_data_result;
+ }
+
+ auto stderr_data_result = util::read_file<util::Bytes>(tmp_stderr.path);
+ if (!stderr_data_result) {
+ LOG("Failed to read {} (cleanup in progress?): {}",
+ tmp_stderr.path,
+ stderr_data_result.error());
+ return nonstd::make_unexpected(Statistic::missing_cache_file);
}
- const auto shallowest_path =
- Util::get_path_in_cache(cache_dir, k_min_cache_levels, name_string);
- return {shallowest_path, Stat(), k_min_cache_levels};
+ return DoExecuteResult{status, stdout_data, *stderr_data_result};
}
-// Create or update the manifest file.
static void
-update_manifest_file(Context& ctx)
+read_manifest(Context& ctx, nonstd::span<const uint8_t> cache_entry_data)
{
- if (!ctx.config.direct_mode() || ctx.config.read_only()
- || ctx.config.read_only_direct()) {
- return;
+ try {
+ core::CacheEntry cache_entry(cache_entry_data);
+ cache_entry.verify_checksum();
+ ctx.manifest.read(cache_entry.payload());
+ } catch (const core::Error& e) {
+ LOG("Error reading manifest: {}", e.what());
}
+}
- ASSERT(ctx.manifest_path());
- ASSERT(ctx.result_path());
+static void
+update_manifest(Context& ctx,
+ const Digest& manifest_key,
+ const Digest& result_key)
+{
+ if (ctx.config.read_only() || ctx.config.read_only_direct()) {
+ return;
+ }
- MTR_BEGIN("manifest", "manifest_put");
+ ASSERT(ctx.config.direct_mode());
- const auto old_stat = Stat::stat(*ctx.manifest_path());
+ MTR_SCOPE("manifest", "manifest_put");
- // See comment in get_file_hash_index for why saving of timestamps is forced
- // for precompiled headers.
+ // ctime() may be 0, so we have to check time_of_compilation against
+ // MAX(mtime, ctime).
+ //
+ // ccache only reads mtime/ctime if file_stat_matches sloppiness is enabled,
+ // so mtimes/ctimes are stored as a dummy value (-1) if not enabled. This
+ // reduces the number of file_info entries for the common case.
const bool save_timestamp =
- (ctx.config.sloppiness() & SLOPPY_FILE_STAT_MATCHES)
+ (ctx.config.sloppiness().is_enabled(core::Sloppy::file_stat_matches))
|| ctx.args_info.output_is_precompiled_header;
- 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());
+ const bool added = ctx.manifest.add_result(
+ result_key, ctx.included_files, [&](const std::string& path) {
+ auto stat = Stat::stat(path, Stat::OnError::log);
+ bool cache_time =
+ save_timestamp
+ && ctx.time_of_compilation > std::max(stat.mtime(), stat.ctime());
+ return core::Manifest::FileStats{
+ stat.size(),
+ stat && cache_time ? stat.mtime() : util::TimePoint(),
+ stat && cache_time ? stat.ctime() : util::TimePoint(),
+ };
+ });
+ if (added) {
+ LOG("Added result key to manifest {}", manifest_key.to_string());
+ core::CacheEntry::Header header(ctx.config, core::CacheEntryType::manifest);
+ ctx.storage.put(manifest_key,
+ core::CacheEntryType::manifest,
+ core::CacheEntry::serialize(header, ctx.manifest));
} else {
- const auto new_stat = Stat::stat(*ctx.manifest_path(), Stat::OnError::log);
- ctx.manifest_counter_updates.increment(
- Statistic::cache_size_kibibyte,
- Util::size_change_kibibyte(old_stat, new_stat));
- ctx.manifest_counter_updates.increment(Statistic::files_in_cache,
- !old_stat && new_stat ? 1 : 0);
- }
- MTR_END("manifest", "manifest_put");
-}
-
-static void
-create_cachedir_tag(const Context& ctx)
-{
- constexpr char cachedir_tag[] =
- "Signature: 8a477f597d28d172789f06886806bc55\n"
- "# This file is a cache directory tag created by ccache.\n"
- "# For information about cache directory tags, see:\n"
- "#\thttp://www.brynosaurus.com/cachedir/\n";
-
- 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("Did not add result key to manifest {}", manifest_key.to_string());
}
}
// (in CWD) if -fprofile-dir=DIR is present (regardless of DIR) instead of the
// traditional /dir/to/example.gcno.
- std::string mangled_form = Result::gcno_file_in_mangled_form(ctx);
- std::string unmangled_form = Result::gcno_file_in_unmangled_form(ctx);
+ std::string mangled_form = core::Result::gcno_file_in_mangled_form(ctx);
+ std::string unmangled_form = core::Result::gcno_file_in_unmangled_form(ctx);
std::string found_file;
if (Stat::stat(mangled_form)) {
LOG("Found coverage file {}", mangled_form);
return {true, found_file, found_file == mangled_form};
}
-// Run the real compiler and put the result in cache.
-static void
+[[nodiscard]] static bool
+write_result(Context& ctx,
+ const Digest& result_key,
+ const Stat& obj_stat,
+ const util::Bytes& stdout_data,
+ const util::Bytes& stderr_data)
+{
+ core::Result::Serializer serializer(ctx.config);
+
+ if (!stderr_data.empty()) {
+ serializer.add_data(core::Result::FileType::stderr_output, stderr_data);
+ }
+ // Write stdout only after stderr (better with MSVC), as ResultRetriever
+ // will later print process them in the order they are read.
+ if (!stdout_data.empty()) {
+ serializer.add_data(core::Result::FileType::stdout_output, stdout_data);
+ }
+ if (obj_stat
+ && !serializer.add_file(core::Result::FileType::object,
+ ctx.args_info.output_obj)) {
+ LOG("Object file {} missing", ctx.args_info.output_obj);
+ return false;
+ }
+ if (ctx.args_info.generating_dependencies
+ && !serializer.add_file(core::Result::FileType::dependency,
+ ctx.args_info.output_dep)) {
+ LOG("Dependency file {} missing", ctx.args_info.output_dep);
+ return false;
+ }
+ if (ctx.args_info.generating_coverage) {
+ const auto coverage_file = find_coverage_file(ctx);
+ if (!coverage_file.found) {
+ LOG_RAW("Coverage file not found");
+ return false;
+ }
+ if (!serializer.add_file(coverage_file.mangled
+ ? core::Result::FileType::coverage_mangled
+ : core::Result::FileType::coverage_unmangled,
+ coverage_file.path)) {
+ LOG("Coverage file {} missing", coverage_file.path);
+ return false;
+ }
+ }
+ if (ctx.args_info.generating_stackusage
+ && !serializer.add_file(core::Result::FileType::stackusage,
+ ctx.args_info.output_su)) {
+ LOG("Stack usage file {} missing", ctx.args_info.output_su);
+ return false;
+ }
+ if (ctx.args_info.generating_diagnostics
+ && !serializer.add_file(core::Result::FileType::diagnostic,
+ ctx.args_info.output_dia)) {
+ LOG("Diagnostics file {} missing", ctx.args_info.output_dia);
+ return false;
+ }
+ if (ctx.args_info.seen_split_dwarf
+ // Only store .dwo file if it was created by the compiler (GCC and Clang
+ // behave differently e.g. for "-gsplit-dwarf -g1").
+ && Stat::stat(ctx.args_info.output_dwo)
+ && !serializer.add_file(core::Result::FileType::dwarf_object,
+ ctx.args_info.output_dwo)) {
+ LOG("Split dwarf file {} missing", ctx.args_info.output_dwo);
+ return false;
+ }
+ if (!ctx.args_info.output_al.empty()
+ && !serializer.add_file(core::Result::FileType::assembler_listing,
+ ctx.args_info.output_al)) {
+ LOG("Assembler listing file {} missing", ctx.args_info.output_al);
+ return false;
+ }
+
+ core::CacheEntry::Header header(ctx.config, core::CacheEntryType::result);
+ const auto cache_entry_data = core::CacheEntry::serialize(header, serializer);
+
+ if (!ctx.config.remote_only()) {
+ const auto& raw_files = serializer.get_raw_files();
+ if (!raw_files.empty()) {
+ ctx.storage.local.put_raw_files(result_key, raw_files);
+ }
+ }
+
+ ctx.storage.put(result_key, core::CacheEntryType::result, cache_entry_data);
+
+ return true;
+}
+
+static util::Bytes
+rewrite_stdout_from_compiler(const Context& ctx, util::Bytes&& stdout_data)
+{
+ using util::Tokenizer;
+ using Mode = Tokenizer::Mode;
+ using IncludeDelimiter = Tokenizer::IncludeDelimiter;
+ if (!stdout_data.empty()) {
+ util::Bytes new_stdout_data;
+ for (const auto line : Tokenizer(util::to_string_view(stdout_data),
+ "\n",
+ Mode::include_empty,
+ IncludeDelimiter::yes)) {
+ if (util::starts_with(line, "__________")) {
+ Util::send_to_fd(ctx, line, STDOUT_FILENO);
+ }
+ // Ninja uses the lines with 'Note: including file: ' to determine the
+ // used headers. Headers within basedir need to be changed into relative
+ // paths because otherwise Ninja will use the abs path to original header
+ // to check if a file needs to be recompiled.
+ else if (ctx.config.compiler_type() == CompilerType::msvc
+ && !ctx.config.base_dir().empty()
+ && util::starts_with(line, ctx.config.msvc_dep_prefix())) {
+ std::string orig_line(line.data(), line.length());
+ std::string abs_inc_path =
+ util::replace_first(orig_line, ctx.config.msvc_dep_prefix(), "");
+ abs_inc_path = util::strip_whitespace(abs_inc_path);
+ std::string rel_inc_path = Util::make_relative_path(
+ ctx, Util::normalize_concrete_absolute_path(abs_inc_path));
+ std::string line_with_rel_inc =
+ util::replace_first(orig_line, abs_inc_path, rel_inc_path);
+ new_stdout_data.insert(new_stdout_data.begin(),
+ line_with_rel_inc.data(),
+ line_with_rel_inc.size());
+ } else {
+ new_stdout_data.insert(new_stdout_data.end(), line.data(), line.size());
+ }
+ }
+ return new_stdout_data;
+ } else {
+ return std::move(stdout_data);
+ }
+}
+
+// Run the real compiler and put the result in cache. Returns the result key.
+static nonstd::expected<Digest, Failure>
to_cache(Context& ctx,
Args& args,
+ std::optional<Digest> result_key,
const Args& depend_extra_args,
Hash* depend_mode_hash)
{
- args.push_back("-o");
- args.push_back(ctx.args_info.output_obj);
+ if (ctx.config.is_compiler_group_msvc()) {
+ args.push_back(fmt::format("-Fo{}", ctx.args_info.output_obj));
+ } else {
+ args.push_back("-o");
+ args.push_back(ctx.args_info.output_obj);
+ }
if (ctx.config.hard_link() && ctx.args_info.output_obj != "/dev/null") {
// Workaround for Clang bug where it overwrites an existing object file
args.push_back(ctx.args_info.output_dia);
}
- // Turn off DEPENDENCIES_OUTPUT when running cc1, because otherwise it will
- // emit a line like this:
- //
- // tmp.stdout.vexed.732.o: /home/mbp/.ccache/tmp.stdout.vexed.732.i
- Util::unsetenv("DEPENDENCIES_OUTPUT");
- Util::unsetenv("SUNPRO_DEPENDENCIES");
+ if (ctx.args_info.seen_double_dash) {
+ args.push_back("--");
+ }
if (ctx.config.run_second_cpp()) {
args.push_back(ctx.args_info.input_file);
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));
- throw Failure(Statistic::bad_output_file);
+ return nonstd::make_unexpected(Statistic::bad_output_file);
}
}
LOG_RAW("Running real compiler");
MTR_BEGIN("execute", "compiler");
- 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("{}/tmp.stderr", ctx.config.temporary_dir()));
- ctx.register_pending_tmp_file(tmp_stderr.path);
- std::string tmp_stderr_path = tmp_stderr.path;
-
- int status;
+ nonstd::expected<DoExecuteResult, Failure> result;
if (!ctx.config.depend_mode()) {
- status =
- do_execute(ctx, args, std::move(tmp_stdout), std::move(tmp_stderr));
+ result = do_execute(ctx, args);
args.pop_back(3);
} else {
// Use the original arguments (including dependency options) in depend
depend_mode_args.push_back(depend_extra_args);
add_prefix(ctx, depend_mode_args, ctx.config.prefix_command());
- ctx.time_of_compilation = time(nullptr);
- status = do_execute(
- ctx, depend_mode_args, std::move(tmp_stdout), std::move(tmp_stderr));
+ ctx.time_of_compilation = util::TimePoint::now();
+ result = do_execute(ctx, depend_mode_args);
}
MTR_END("execute", "compiler");
- auto st = Stat::stat(tmp_stdout_path, Stat::OnError::log);
- if (!st) {
- // The stdout file was removed - cleanup in progress? Better bail out.
- throw Failure(Statistic::missing_cache_file);
- }
-
- // distcc-pump outputs lines like this:
- // __________Using # distcc servers in pump mode
- if (st.size() != 0 && ctx.config.compiler_type() != CompilerType::pump) {
- LOG_RAW("Compiler produced stdout");
- throw Failure(Statistic::compiler_produced_stdout);
+ if (!result) {
+ return nonstd::make_unexpected(result.error());
}
// Merge stderr from the preprocessor (if any) and stderr from the real
- // compiler into tmp_stderr.
- if (!ctx.cpp_stderr.empty()) {
- std::string combined_stderr =
- Util::read_file(ctx.cpp_stderr) + Util::read_file(tmp_stderr_path);
- Util::write_file(tmp_stderr_path, combined_stderr);
+ // compiler.
+ if (!ctx.cpp_stderr_data.empty()) {
+ result->stderr_data.insert(result->stderr_data.begin(),
+ ctx.cpp_stderr_data.begin(),
+ ctx.cpp_stderr_data.end());
}
- if (status != 0) {
- LOG("Compiler gave exit status {}", status);
+ result->stdout_data =
+ rewrite_stdout_from_compiler(ctx, std::move(result->stdout_data));
+
+ if (result->exit_status != 0) {
+ LOG("Compiler gave exit status {}", result->exit_status);
// We can output stderr immediately instead of rerunning the compiler.
- Util::send_to_stderr(ctx, Util::read_file(tmp_stderr_path));
+ Util::send_to_fd(
+ ctx, util::to_string_view(result->stderr_data), STDERR_FILENO);
+ Util::send_to_fd(
+ ctx,
+ util::to_string_view(core::MsvcShowIncludesOutput::strip_includes(
+ ctx, std::move(result->stdout_data))),
+ STDOUT_FILENO);
- throw Failure(Statistic::compile_failed, status);
+ auto failure = Failure(Statistic::compile_failed);
+ failure.set_exit_code(result->exit_status);
+ return nonstd::make_unexpected(failure);
}
if (ctx.config.depend_mode()) {
ASSERT(depend_mode_hash);
- auto result_name = result_name_from_depfile(ctx, *depend_mode_hash);
- if (!result_name) {
- throw Failure(Statistic::internal_error);
+ if (ctx.args_info.generating_dependencies) {
+ result_key = result_key_from_depfile(ctx, *depend_mode_hash);
+ } else if (ctx.args_info.generating_includes) {
+ result_key = result_key_from_includes(
+ ctx, *depend_mode_hash, util::to_string_view(result->stdout_data));
+ } else {
+ ASSERT(false);
+ }
+ if (!result_key) {
+ return nonstd::make_unexpected(Statistic::internal_error);
}
- ctx.set_result_name(*result_name);
+ LOG_RAW("Got result key from dependency file");
+ LOG("Result key: {}", result_key->to_string());
}
+ ASSERT(result_key);
+
bool produce_dep_file = ctx.args_info.generating_dependencies
&& ctx.args_info.output_dep != "/dev/null";
Depfile::make_paths_relative_in_output_dep(ctx);
}
- const auto obj_stat = Stat::stat(ctx.args_info.output_obj);
- if (!obj_stat) {
- 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);
- }
-
- const auto stderr_stat = Stat::stat(tmp_stderr_path, Stat::OnError::log);
- if (!stderr_stat) {
- throw Failure(Statistic::internal_error);
- }
-
- const auto result_file = look_up_cache_file(
- ctx.config.cache_dir(), *ctx.result_name(), Result::k_file_suffix);
- ctx.set_result_path(result_file.path);
- Result::Writer result_writer(ctx, result_file.path);
-
- if (stderr_stat.size() > 0) {
- result_writer.write(Result::FileType::stderr_output, tmp_stderr_path);
- }
- 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);
- }
- if (ctx.args_info.generating_coverage) {
- const auto coverage_file = find_coverage_file(ctx);
- if (!coverage_file.found) {
- throw Failure(Statistic::internal_error);
- }
- result_writer.write(coverage_file.mangled
- ? Result::FileType::coverage_mangled
- : Result::FileType::coverage_unmangled,
- coverage_file.path);
- }
- if (ctx.args_info.generating_stackusage) {
- result_writer.write(Result::FileType::stackusage, ctx.args_info.output_su);
- }
- if (ctx.args_info.generating_diagnostics) {
- result_writer.write(Result::FileType::diagnostic, ctx.args_info.output_dia);
- }
- if (ctx.args_info.seen_split_dwarf && Stat::stat(ctx.args_info.output_dwo)) {
- // Only store .dwo file if it was created by the compiler (GCC and Clang
- // behave differently e.g. for "-gsplit-dwarf -g1").
- result_writer.write(Result::FileType::dwarf_object,
- ctx.args_info.output_dwo);
- }
-
- auto error = result_writer.finalize();
- if (error) {
- LOG("Error: {}", *error);
+ Stat obj_stat;
+ if (!ctx.args_info.expect_output_obj) {
+ // Don't probe for object file when we don't expect one since we otherwise
+ // will be fooled by an already existing object file.
+ LOG_RAW("Compiler not expected to produce an object file");
} else {
- LOG("Stored in cache: {}", result_file.path);
+ obj_stat = Stat::stat(ctx.args_info.output_obj);
+ if (!obj_stat) {
+ LOG_RAW("Compiler didn't produce an object file");
+ return nonstd::make_unexpected(Statistic::compiler_produced_no_output);
+ } else if (obj_stat.size() == 0) {
+ LOG_RAW("Compiler produced an empty object file");
+ return nonstd::make_unexpected(Statistic::compiler_produced_empty_output);
+ }
}
- auto new_result_stat = Stat::stat(result_file.path, Stat::OnError::log);
- if (!new_result_stat) {
- throw Failure(Statistic::internal_error);
+ MTR_BEGIN("result", "result_put");
+ if (!write_result(
+ ctx, *result_key, obj_stat, result->stdout_data, result->stderr_data)) {
+ return nonstd::make_unexpected(Statistic::compiler_produced_no_output);
}
- ctx.counter_updates.increment(
- Statistic::cache_size_kibibyte,
- Util::size_change_kibibyte(result_file.stat, new_result_stat));
- ctx.counter_updates.increment(Statistic::files_in_cache,
- result_file.stat ? 0 : 1);
-
- MTR_END("file", "file_put");
-
- // Make sure we have a CACHEDIR.TAG in the cache part of cache_dir. This can
- // be done almost anywhere, but we might as well do it near the end as we save
- // the stat call if we exit early.
- create_cachedir_tag(ctx);
+ MTR_END("result", "result_put");
// Everything OK.
- Util::send_to_stderr(ctx, Util::read_file(tmp_stderr_path));
+ Util::send_to_fd(
+ ctx, util::to_string_view(result->stderr_data), STDERR_FILENO);
+ // Send stdout after stderr, it makes the output clearer with MSVC.
+ Util::send_to_fd(
+ ctx,
+ util::to_string_view(core::MsvcShowIncludesOutput::strip_includes(
+ ctx, std::move(result->stdout_data))),
+ STDOUT_FILENO);
+
+ return *result_key;
}
-// Find the result name by running the compiler in preprocessor mode and
+// Find the result key by running the compiler in preprocessor mode and
// hashing the result.
-static Digest
-get_result_name_from_cpp(Context& ctx, Args& args, Hash& hash)
+static nonstd::expected<Digest, Failure>
+get_result_key_from_cpp(Context& ctx, Args& args, Hash& hash)
{
- ctx.time_of_compilation = time(nullptr);
+ ctx.time_of_compilation = util::TimePoint::now();
+
+ std::string preprocessed_path;
+ util::Bytes cpp_stderr_data;
- std::string stderr_path;
- std::string stdout_path;
- int status;
if (ctx.args_info.direct_i_file) {
// We are compiling a .i or .ii file - that means we can skip the cpp stage
// and directly form the correct i_tmpfile.
- stdout_path = ctx.args_info.input_file;
- status = 0;
+ preprocessed_path = ctx.args_info.input_file;
} else {
// Run cpp on the input file to obtain the .i.
- TemporaryFile tmp_stdout(
- FMT("{}/tmp.cpp_stdout", ctx.config.temporary_dir()));
- ctx.register_pending_tmp_file(tmp_stdout.path);
-
- // stdout_path needs the proper cpp_extension for the compiler to do its
- // thing correctly.
- stdout_path = FMT("{}.{}", tmp_stdout.path, ctx.config.cpp_extension());
- Util::hard_link(tmp_stdout.path, stdout_path);
- ctx.register_pending_tmp_file(stdout_path);
+ // preprocessed_path needs the proper cpp_extension for the compiler to do
+ // its thing correctly.
+ TemporaryFile tmp_stdout(FMT("{}/cpp_stdout", ctx.config.temporary_dir()),
+ FMT(".{}", ctx.config.cpp_extension()));
+ preprocessed_path = tmp_stdout.path;
+ tmp_stdout.fd.close(); // We're only using the path.
+ ctx.register_pending_tmp_file(preprocessed_path);
- TemporaryFile tmp_stderr(
- FMT("{}/tmp.cpp_stderr", ctx.config.temporary_dir()));
- stderr_path = tmp_stderr.path;
- ctx.register_pending_tmp_file(stderr_path);
+ const size_t orig_args_size = args.size();
- size_t args_added = 2;
- args.push_back("-E");
- if (ctx.args_info.actual_language == "hip") {
- args.push_back("-o");
- args.push_back("-");
- args_added += 2;
- }
if (ctx.config.keep_comments_cpp()) {
args.push_back("-C");
- args_added++;
}
+
+ // Send preprocessor output to a file instead of stdout to work around
+ // compilers that don't exit with a proper status on write error to stdout.
+ // See also <https://github.com/llvm/llvm-project/issues/56499>.
+ if (ctx.config.is_compiler_group_msvc()) {
+ args.push_back("-P");
+ args.push_back(FMT("-Fi{}", preprocessed_path));
+ } else {
+ args.push_back("-E");
+ args.push_back("-o");
+ args.push_back(preprocessed_path);
+ }
+
args.push_back(ctx.args_info.input_file);
+
add_prefix(ctx, args, ctx.config.prefix_command_cpp());
LOG_RAW("Running preprocessor");
MTR_BEGIN("execute", "preprocessor");
- status =
- do_execute(ctx, args, std::move(tmp_stdout), std::move(tmp_stderr));
+ const auto result = do_execute(ctx, args, false);
MTR_END("execute", "preprocessor");
- args.pop_back(args_added);
- }
+ args.pop_back(args.size() - orig_args_size);
+
+ if (!result) {
+ return nonstd::make_unexpected(result.error());
+ } else if (result->exit_status != 0) {
+ LOG("Preprocessor gave exit status {}", result->exit_status);
+ return nonstd::make_unexpected(Statistic::preprocessor_error);
+ }
- if (status != 0) {
- LOG("Preprocessor gave exit status {}", status);
- throw Failure(Statistic::preprocessor_error);
+ cpp_stderr_data = result->stderr_data;
}
hash.hash_delimiter("cpp");
- const bool is_pump = ctx.config.compiler_type() == CompilerType::pump;
- const Statistic error =
- process_preprocessed_file(ctx, hash, stdout_path, is_pump);
- if (error != Statistic::none) {
- throw Failure(error);
- }
+ TRY(process_preprocessed_file(ctx, hash, preprocessed_path));
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));
- throw Failure(Statistic::internal_error);
- }
+ hash.hash(util::to_string_view(cpp_stderr_data));
- if (ctx.args_info.direct_i_file) {
- ctx.i_tmpfile = ctx.args_info.input_file;
- } else {
- ctx.i_tmpfile = stdout_path;
- }
+ ctx.i_tmpfile = preprocessed_path;
if (!ctx.config.run_second_cpp()) {
// If we are using the CPP trick, we need to remember this stderr data and
// output it just before the main stderr from the compiler pass.
- ctx.cpp_stderr = stderr_path;
+ ctx.cpp_stderr_data = std::move(cpp_stderr_data);
hash.hash_delimiter("runsecondcpp");
hash.hash("false");
}
// Hash mtime or content of a file, or the output of a command, according to
// the CCACHE_COMPILERCHECK setting.
-static void
+static nonstd::expected<void, Failure>
hash_compiler(const Context& ctx,
Hash& hash,
const Stat& st,
} else if (ctx.config.compiler_check() == "mtime") {
hash.hash_delimiter("cc_mtime");
hash.hash(st.size());
- hash.hash(st.mtime());
- } else if (Util::starts_with(ctx.config.compiler_check(), "string:")) {
+ hash.hash(st.mtime().nsec());
+ } else if (util::starts_with(ctx.config.compiler_check(), "string:")) {
hash.hash_delimiter("cc_hash");
hash.hash(&ctx.config.compiler_check()[7]);
} else if (ctx.config.compiler_check() == "content" || !allow_command) {
hash, ctx.config.compiler_check(), ctx.orig_args[0])) {
LOG("Failure running compiler check command: {}",
ctx.config.compiler_check());
- throw Failure(Statistic::compiler_check_failed);
+ return nonstd::make_unexpected(Statistic::compiler_check_failed);
}
}
+ return {};
}
// Hash the host compiler(s) invoked by nvcc.
// If `ccbin_st` and `ccbin` are set, they refer to a directory or compiler set
// with -ccbin/--compiler-bindir. If `ccbin_st` is nullptr or `ccbin` is the
// empty string, the compilers are looked up in PATH instead.
-static void
+static nonstd::expected<void, Failure>
hash_nvcc_host_compiler(const Context& ctx,
Hash& hash,
const Stat* ccbin_st = nullptr,
std::string path = FMT("{}/{}", ccbin, compiler);
auto st = Stat::stat(path);
if (st) {
- hash_compiler(ctx, hash, st, path, false);
+ TRY(hash_compiler(ctx, hash, st, path, false));
}
} else {
- std::string path = find_executable(ctx, compiler, CCACHE_NAME);
+ std::string path = find_executable(ctx, compiler, ctx.orig_args[0]);
if (!path.empty()) {
auto st = Stat::stat(path, Stat::OnError::log);
- hash_compiler(ctx, hash, st, ccbin, false);
+ TRY(hash_compiler(ctx, hash, st, ccbin, false));
}
}
}
} else {
- hash_compiler(ctx, hash, *ccbin_st, ccbin, false);
+ TRY(hash_compiler(ctx, hash, *ccbin_st, ccbin, false));
}
-}
-static bool
-should_rewrite_dependency_target(const ArgsInfo& args_info)
-{
- return !args_info.dependency_target_specified && args_info.seen_MD_MMD;
+ return {};
}
// update a hash with information common for the direct and preprocessor modes.
-static void
+static nonstd::expected<void, Failure>
hash_common_info(const Context& ctx,
const Args& args,
Hash& hash,
{
hash.hash(HASH_PREFIX);
+ if (!ctx.config.namespace_().empty()) {
+ hash.hash_delimiter("namespace");
+ hash.hash(ctx.config.namespace_());
+ }
+
// We have to hash the extension, as a .i file isn't treated the same by the
// compiler as a .ii file.
hash.hash_delimiter("ext");
auto st = Stat::stat(compiler_path, Stat::OnError::log);
if (!st) {
- throw Failure(Statistic::could_not_find_compiler);
+ return nonstd::make_unexpected(Statistic::could_not_find_compiler);
}
// Hash information about the compiler.
- hash_compiler(ctx, hash, st, compiler_path, true);
+ TRY(hash_compiler(ctx, hash, st, compiler_path, true));
// Also hash the compiler name as some compilers use hard links and behave
// differently depending on the real name.
// Hash variables that may affect the compilation.
const char* always_hash_env_vars[] = {
- // From <https://gcc.gnu.org/onlinedocs/gcc/Environment-Variables.html>:
+ // From <https://gcc.gnu.org/onlinedocs/gcc/Environment-Variables.html>
+ // (note: SOURCE_DATE_EPOCH is handled in hash_source_code_string()):
"COMPILER_PATH",
"GCC_COMPARE_DEBUG",
"GCC_EXEC_PREFIX",
- // Note: SOURCE_DATE_EPOCH is handled in hash_source_code_string().
+ // Variables that affect which underlying compiler ICC uses. Reference:
+ // <https://www.intel.com/content/www/us/en/develop/documentation/
+ // mpi-developer-reference-windows/top/environment-variable-reference/
+ // compilation-environment-variables.html>
+ "I_MPI_CC",
+ "I_MPI_CXX",
+#ifdef __APPLE__
+ // On macOS, /usr/bin/clang is a compiler wrapper that switches compiler
+ // based on at least these variables:
+ "DEVELOPER_DIR",
+ "MACOSX_DEPLOYMENT_TARGET",
+#endif
};
for (const char* name : always_hash_env_vars) {
const char* value = getenv(name);
}
}
- if (!(ctx.config.sloppiness() & SLOPPY_LOCALE)) {
+ if (!(ctx.config.sloppiness().is_enabled(core::Sloppy::locale))) {
// Hash environment variables that may affect localization of compiler
// warning messages.
const char* envvars[] = {
old_path,
new_path,
ctx.apparent_cwd);
- if (Util::starts_with(ctx.apparent_cwd, old_path)) {
+ if (util::starts_with(ctx.apparent_cwd, old_path)) {
dir_to_hash = new_path + ctx.apparent_cwd.substr(old_path.size());
}
}
hash.hash(dir_to_hash);
}
- if ((!should_rewrite_dependency_target(ctx.args_info)
- && ctx.args_info.generating_dependencies)
- || ctx.args_info.seen_split_dwarf || ctx.args_info.profile_arcs) {
- // If generating dependencies: The output object file name is part of the .d
- // file, so include the path in the hash.
- //
+ // The object file produced by MSVC includes the full path to the source file
+ // even without debug flags. Hashing the directory should be enough since the
+ // filename is included in the hash anyway.
+ if (ctx.config.is_compiler_group_msvc() && ctx.config.hash_dir()) {
+ const std::string output_obj_dir =
+ util::is_absolute_path(args_info.output_obj)
+ ? std::string(Util::dir_name(args_info.output_obj))
+ : ctx.actual_cwd;
+ LOG("Hashing object file directory {}", output_obj_dir);
+ hash.hash_delimiter("source path");
+ hash.hash(output_obj_dir);
+ }
+
+ if (ctx.args_info.seen_split_dwarf || ctx.args_info.profile_arcs) {
// When using -gsplit-dwarf: Object files include a link to the
// corresponding .dwo file based on the target object filename, so hashing
// the object file path will do it, although just hashing the object file
hash.hash(ctx.args_info.output_obj);
}
+ if (ctx.args_info.generating_coverage
+ && !(ctx.config.sloppiness().is_enabled(core::Sloppy::gcno_cwd))) {
+ // GCC 9+ includes $PWD in the .gcno file. Since we don't have knowledge
+ // about compiler version we always (unless sloppiness is wanted) include
+ // the directory in the hash for now.
+ LOG_RAW("Hashing apparent CWD due to generating a .gcno file");
+ hash.hash_delimiter("CWD in .gcno");
+ hash.hash(ctx.apparent_cwd);
+ }
+
// Possibly hash the coverage data file path.
if (ctx.args_info.generating_coverage && ctx.args_info.profile_arcs) {
std::string dir;
dir =
Util::real_path(std::string(Util::dir_name(ctx.args_info.output_obj)));
}
- string_view stem =
+ std::string_view stem =
Util::remove_extension(Util::base_name(ctx.args_info.output_obj));
std::string gcda_path = FMT("{}/{}.gcda", dir, stem);
LOG("Hashing coverage path {}", gcda_path);
// Possibly hash the sanitize blacklist file path.
for (const auto& sanitize_blacklist : args_info.sanitize_blacklists) {
LOG("Hashing sanitize blacklist {}", sanitize_blacklist);
- hash.hash("sanitizeblacklist");
+ hash.hash_delimiter("sanitizeblacklist");
if (!hash_binary_file(ctx, hash, sanitize_blacklist)) {
- throw Failure(Statistic::error_hashing_extra_file);
+ return nonstd::make_unexpected(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)) {
+ for (const std::string& path :
+ util::split_path_list(ctx.config.extra_files_to_hash())) {
LOG("Hashing extra file {}", path);
hash.hash_delimiter("extrafile");
if (!hash_binary_file(ctx, hash, path)) {
- throw Failure(Statistic::error_hashing_extra_file);
+ return nonstd::make_unexpected(Statistic::error_hashing_extra_file);
}
}
}
hash.hash(gcc_colors);
}
}
+
+ return {};
}
static bool
-hash_profile_data_file(const Context& ctx, Hash& hash)
+option_should_be_ignored(const std::string& arg,
+ const std::vector<std::string>& patterns)
{
- const std::string& profile_path = ctx.args_info.profile_path;
- string_view base_name = Util::remove_extension(ctx.args_info.output_obj);
- std::string hashified_cwd = ctx.apparent_cwd;
- std::replace(hashified_cwd.begin(), hashified_cwd.end(), '/', '#');
+ return std::any_of(
+ patterns.cbegin(), patterns.cend(), [&arg](const auto& pattern) {
+ const auto& prefix =
+ std::string_view(pattern).substr(0, pattern.length() - 1);
+ return (
+ pattern == arg
+ || (util::ends_with(pattern, "*") && util::starts_with(arg, prefix)));
+ });
+}
- std::vector<std::string> paths_to_try{
- // -fprofile-use[=dir]/-fbranch-probabilities (GCC <9)
- FMT("{}/{}.gcda", profile_path, base_name),
- // -fprofile-use[=dir]/-fbranch-probabilities (GCC >=9)
- 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("{}/default.profdata", profile_path),
- // -fauto-profile (GCC >=5)
- "fbdata.afdo", // -fprofile-dir is not used
- };
+static std::tuple<std::optional<std::string_view>,
+ std::optional<std::string_view>>
+get_option_and_value(std::string_view option, const Args& args, size_t& i)
+{
+ if (args[i] == option) {
+ if (i + 1 < args.size()) {
+ ++i;
+ return {option, args[i]};
+ } else {
+ return {std::nullopt, std::nullopt};
+ }
+ } else if (util::starts_with(args[i], option)) {
+ return {option, std::string_view(args[i]).substr(option.length())};
+ } else {
+ return {std::nullopt, std::nullopt};
+ }
+}
- bool found = false;
- for (const std::string& p : paths_to_try) {
- 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);
- hash.hash_delimiter("-fprofile-use");
- if (hash_binary_file(ctx, hash, p)) {
- found = true;
- }
+static nonstd::expected<void, Failure>
+hash_argument(const Context& ctx,
+ const Args& args,
+ size_t& i,
+ Hash& hash,
+ const bool is_clang,
+ const bool direct_mode,
+ bool& found_ccbin)
+{
+ // 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]);
+ if (i + 1 < args.size() && compopt_takes_arg(args[i])) {
+ i++;
+ LOG("Not hashing argument of ignored option: {}", args[i]);
}
+ return {};
}
- return found;
-}
-
-static bool
-option_should_be_ignored(const std::string& arg,
- const std::vector<std::string>& patterns)
-{
- return std::any_of(
- patterns.cbegin(), patterns.cend(), [&arg](const std::string& pattern) {
- const auto& prefix = string_view(pattern).substr(0, pattern.length() - 1);
- return (
- pattern == arg
- || (Util::ends_with(pattern, "*") && Util::starts_with(arg, prefix)));
- });
-}
-
-// Update a hash sum with information specific to the direct and preprocessor
-// modes and calculate the result name. Returns the result name on success,
-// otherwise nullopt.
-static optional<Digest>
-calculate_result_name(Context& ctx,
- const Args& args,
- Args& preprocessor_args,
- Hash& hash,
- bool direct_mode)
-{
- bool found_ccbin = false;
-
- hash.hash_delimiter("result version");
- hash.hash(Result::k_version);
+ // -L doesn't affect compilation (except for clang).
+ if (i < args.size() - 1 && args[i] == "-L" && !is_clang) {
+ i++;
+ return {};
+ }
+ if (util::starts_with(args[i], "-L") && !is_clang) {
+ return {};
+ }
- if (direct_mode) {
- hash.hash_delimiter("manifest version");
- hash.hash(Manifest::k_version);
+ // -Wl,... doesn't affect compilation (except for clang).
+ if (util::starts_with(args[i], "-Wl,") && !is_clang) {
+ return {};
}
- // clang will emit warnings for unused linker flags, so we shouldn't skip
- // those arguments.
- int is_clang = ctx.config.compiler_type() == CompilerType::clang
- || ctx.config.compiler_type() == CompilerType::other;
+ if (util::starts_with(args[i], "-Wa,")) {
+ // We have to distinguish between three cases:
+ //
+ // Case 1: -Wa,-a (write to stdout)
+ // Case 2: -Wa,-a= (write to stdout and stderr)
+ // Case 3: -Wa,-a=file (write to file)
+ //
+ // No need to include the file part in case 3 in the hash since the filename
+ // is not part of the output.
- // 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]);
- if (i + 1 < args.size() && compopt_takes_arg(args[i])) {
- i++;
- LOG("Not hashing argument of ignored option: {}", args[i]);
+ hash.hash_delimiter("arg");
+ bool first = true;
+ for (const auto part :
+ util::Tokenizer(args[i], ",", util::Tokenizer::Mode::include_empty)) {
+ if (first) {
+ first = false;
+ } else {
+ hash.hash(",");
}
- continue;
- }
-
- // -L doesn't affect compilation (except for clang).
- if (i < args.size() - 1 && args[i] == "-L" && !is_clang) {
- i++;
- continue;
- }
- if (Util::starts_with(args[i], "-L") && !is_clang) {
- continue;
+ if (util::starts_with(part, "-a")) {
+ const auto eq_pos = part.find('=');
+ if (eq_pos < part.size() - 1) {
+ // Case 3:
+ hash.hash(part.substr(0, eq_pos + 1));
+ hash.hash("file");
+ continue;
+ }
+ }
+ // Case 1 and 2:
+ hash.hash(part);
}
+ return {};
+ }
- // -Wl,... doesn't affect compilation (except for clang).
- if (Util::starts_with(args[i], "-Wl,") && !is_clang) {
- continue;
- }
+ // The -fdebug-prefix-map option may be used in combination with
+ // CCACHE_BASEDIR to reuse results across different directories. Skip using
+ // the value of the option from hashing but still hash the existence of the
+ // option.
+ if (util::starts_with(args[i], "-fdebug-prefix-map=")) {
+ hash.hash_delimiter("arg");
+ hash.hash("-fdebug-prefix-map=");
+ return {};
+ }
+ if (util::starts_with(args[i], "-ffile-prefix-map=")) {
+ hash.hash_delimiter("arg");
+ hash.hash("-ffile-prefix-map=");
+ return {};
+ }
+ if (util::starts_with(args[i], "-fmacro-prefix-map=")) {
+ hash.hash_delimiter("arg");
+ hash.hash("-fmacro-prefix-map=");
+ return {};
+ }
- // The -fdebug-prefix-map option may be used in combination with
- // CCACHE_BASEDIR to reuse results across different directories. Skip using
- // the value of the option from hashing but still hash the existence of the
- // option.
- if (Util::starts_with(args[i], "-fdebug-prefix-map=")) {
- hash.hash_delimiter("arg");
- hash.hash("-fdebug-prefix-map=");
- continue;
- }
- if (Util::starts_with(args[i], "-ffile-prefix-map=")) {
- hash.hash_delimiter("arg");
- hash.hash("-ffile-prefix-map=");
- continue;
- }
- if (Util::starts_with(args[i], "-fmacro-prefix-map=")) {
- hash.hash_delimiter("arg");
- hash.hash("-fmacro-prefix-map=");
- continue;
- }
+ if (util::starts_with(args[i], "-frandom-seed=")
+ && ctx.config.sloppiness().is_enabled(core::Sloppy::random_seed)) {
+ LOG("Ignoring {} since random_seed sloppiness is requested", args[i]);
+ return {};
+ }
- // When using the preprocessor, some arguments don't contribute to the
- // hash. The theory is that these arguments will change the output of -E if
- // they are going to have any effect at all. For precompiled headers this
- // might not be the case.
- if (!direct_mode && !ctx.args_info.output_is_precompiled_header
- && !ctx.args_info.using_precompiled_header) {
- if (compopt_affects_cpp_output(args[i])) {
- if (compopt_takes_arg(args[i])) {
- i++;
- }
- continue;
- }
- if (compopt_affects_cpp_output(args[i].substr(0, 2))) {
- continue;
+ // When using the preprocessor, some arguments don't contribute to the hash.
+ // The theory is that these arguments will change the output of -E if they are
+ // going to have any effect at all. For precompiled headers this might not be
+ // the case.
+ if (!direct_mode && !ctx.args_info.output_is_precompiled_header
+ && !ctx.args_info.using_precompiled_header) {
+ if (compopt_affects_cpp_output(args[i])) {
+ if (compopt_takes_arg(args[i])) {
+ i++;
}
+ return {};
}
+ if (compopt_affects_cpp_output(args[i].substr(0, 2))) {
+ return {};
+ }
+ }
- // If we're generating dependencies, we make sure to skip the filename of
- // the dependency file, since it doesn't impact the output.
- if (ctx.args_info.generating_dependencies) {
- if (Util::starts_with(args[i], "-Wp,")) {
- if (Util::starts_with(args[i], "-Wp,-MD,")
- && args[i].find(',', 8) == std::string::npos) {
- hash.hash(args[i].data(), 8);
- continue;
- } else if (Util::starts_with(args[i], "-Wp,-MMD,")
- && args[i].find(',', 9) == std::string::npos) {
- hash.hash(args[i].data(), 9);
- continue;
- }
- } else if (Util::starts_with(args[i], "-MF")) {
- // In either case, hash the "-MF" part.
- hash.hash_delimiter("arg");
- hash.hash(args[i].data(), 3);
-
- if (ctx.args_info.output_dep != "/dev/null") {
- bool separate_argument = (args[i].size() == 3);
- if (separate_argument) {
- // Next argument is dependency name, so skip it.
- i++;
- }
- }
- continue;
+ if (ctx.args_info.generating_dependencies) {
+ std::optional<std::string_view> option;
+ std::optional<std::string_view> value;
+
+ if (util::starts_with(args[i], "-Wp,")) {
+ // Skip the dependency filename since it doesn't impact the output.
+ if (util::starts_with(args[i], "-Wp,-MD,")
+ && args[i].find(',', 8) == std::string::npos) {
+ hash.hash(args[i].data(), 8);
+ return {};
+ } else if (util::starts_with(args[i], "-Wp,-MMD,")
+ && args[i].find(',', 9) == std::string::npos) {
+ hash.hash(args[i].data(), 9);
+ return {};
}
+ } else if (std::tie(option, value) = get_option_and_value("-MF", args, i);
+ option) {
+ // Skip the dependency filename since it doesn't impact the output.
+ hash.hash(*option);
+ return {};
+ } else if (std::tie(option, value) = get_option_and_value("-MQ", args, i);
+ option) {
+ hash.hash(*option);
+ // No need to hash the dependency target since we always calculate it on
+ // a cache hit.
+ return {};
+ } else if (std::tie(option, value) = get_option_and_value("-MT", args, i);
+ option) {
+ hash.hash(*option);
+ // No need to hash the dependency target since we always calculate it on
+ // a cache hit.
+ return {};
}
+ }
- if (Util::starts_with(args[i], "-specs=")
- || Util::starts_with(args[i], "--specs=")
- || (args[i] == "-specs" || args[i] == "--specs")) {
- std::string path;
- size_t eq_pos = args[i].find('=');
- if (eq_pos == std::string::npos) {
- if (i + 1 >= args.size()) {
- LOG("missing argument for \"{}\"", args[i]);
- throw Failure(Statistic::bad_compiler_arguments);
- }
- path = args[i + 1];
- i++;
- } else {
- path = args[i].substr(eq_pos + 1);
- }
- auto st = Stat::stat(path, Stat::OnError::log);
- if (st) {
- // If given an explicit specs file, then hash that file, but don't
- // include the path to it in the hash.
- hash.hash_delimiter("specs");
- hash_compiler(ctx, hash, st, path, false);
- continue;
+ if (util::starts_with(args[i], "-specs=")
+ || util::starts_with(args[i], "--specs=")
+ || (args[i] == "-specs" || args[i] == "--specs")
+ || args[i] == "--config") {
+ std::string path;
+ size_t eq_pos = args[i].find('=');
+ if (eq_pos == std::string::npos) {
+ if (i + 1 >= args.size()) {
+ LOG("missing argument for \"{}\"", args[i]);
+ return nonstd::make_unexpected(Statistic::bad_compiler_arguments);
}
+ path = args[i + 1];
+ i++;
+ } else {
+ path = args[i].substr(eq_pos + 1);
+ }
+ auto st = Stat::stat(path, Stat::OnError::log);
+ if (st) {
+ // If given an explicit specs file, then hash that file, but don't
+ // include the path to it in the hash.
+ hash.hash_delimiter("specs");
+ TRY(hash_compiler(ctx, hash, st, path, false));
+ return {};
}
+ }
- if (Util::starts_with(args[i], "-fplugin=")) {
- auto st = Stat::stat(&args[i][9], Stat::OnError::log);
- if (st) {
- hash.hash_delimiter("plugin");
- hash_compiler(ctx, hash, st, &args[i][9], false);
- continue;
- }
+ if (util::starts_with(args[i], "-fplugin=")) {
+ auto st = Stat::stat(&args[i][9], Stat::OnError::log);
+ if (st) {
+ hash.hash_delimiter("plugin");
+ TRY(hash_compiler(ctx, hash, st, &args[i][9], false));
+ return {};
}
+ }
- if (args[i] == "-Xclang" && i + 3 < args.size() && args[i + 1] == "-load"
- && args[i + 2] == "-Xclang") {
- auto st = Stat::stat(args[i + 3], Stat::OnError::log);
- if (st) {
- hash.hash_delimiter("plugin");
- hash_compiler(ctx, hash, st, args[i + 3], false);
- i += 3;
- continue;
- }
+ if (args[i] == "-Xclang" && i + 3 < args.size() && args[i + 1] == "-load"
+ && args[i + 2] == "-Xclang") {
+ auto st = Stat::stat(args[i + 3], Stat::OnError::log);
+ if (st) {
+ hash.hash_delimiter("plugin");
+ TRY(hash_compiler(ctx, hash, st, args[i + 3], false));
+ i += 3;
+ return {};
}
+ }
- if ((args[i] == "-ccbin" || args[i] == "--compiler-bindir")
- && i + 1 < args.size()) {
- auto st = Stat::stat(args[i + 1]);
- if (st) {
- found_ccbin = true;
- hash.hash_delimiter("ccbin");
- hash_nvcc_host_compiler(ctx, hash, &st, args[i + 1]);
- i++;
- continue;
- }
+ if ((args[i] == "-ccbin" || args[i] == "--compiler-bindir")
+ && i + 1 < args.size()) {
+ auto st = Stat::stat(args[i + 1]);
+ if (st) {
+ found_ccbin = true;
+ hash.hash_delimiter("ccbin");
+ TRY(hash_nvcc_host_compiler(ctx, hash, &st, args[i + 1]));
+ i++;
+ return {};
}
+ }
- // All other arguments are included in the hash.
+ // All other arguments are included in the hash.
+ hash.hash_delimiter("arg");
+ hash.hash(args[i]);
+ if (i + 1 < args.size() && compopt_takes_arg(args[i])) {
+ i++;
hash.hash_delimiter("arg");
hash.hash(args[i]);
- if (i + 1 < args.size() && compopt_takes_arg(args[i])) {
- i++;
- hash.hash_delimiter("arg");
- hash.hash(args[i]);
- }
}
- // Make results with dependency file /dev/null different from those without
- // it.
- if (ctx.args_info.generating_dependencies
- && ctx.args_info.output_dep == "/dev/null") {
- hash.hash_delimiter("/dev/null dependency file");
+ return {};
+}
+
+static nonstd::expected<std::optional<Digest>, Failure>
+get_manifest_key(Context& ctx, Hash& hash)
+{
+ // Hash environment variables that affect the preprocessor output.
+ const char* envvars[] = {"CPATH",
+ "C_INCLUDE_PATH",
+ "CPLUS_INCLUDE_PATH",
+ "OBJC_INCLUDE_PATH",
+ "OBJCPLUS_INCLUDE_PATH", // clang
+ nullptr};
+ for (const char** p = envvars; *p; ++p) {
+ const char* v = getenv(*p);
+ if (v) {
+ hash.hash_delimiter(*p);
+ hash.hash(v);
+ }
+ }
+
+ // Make sure that the direct mode hash is unique for the input file path. If
+ // this would not be the case:
+ //
+ // * A false cache hit may be produced. Scenario:
+ // - a/r.h exists.
+ // - a/x.c has #include "r.h".
+ // - b/x.c is identical to a/x.c.
+ // - Compiling a/x.c records a/r.h in the manifest.
+ // - Compiling b/x.c results in a false cache hit since a/x.c and b/x.c
+ // share manifests and a/r.h exists.
+ // * The expansion of __FILE__ may be incorrect.
+ hash.hash_delimiter("inputfile");
+ hash.hash(ctx.args_info.input_file);
+
+ hash.hash_delimiter("sourcecode hash");
+ Digest input_file_digest;
+ int result =
+ hash_source_code_file(ctx, input_file_digest, ctx.args_info.input_file);
+ if (result & HASH_SOURCE_CODE_ERROR) {
+ return nonstd::make_unexpected(Statistic::internal_error);
+ }
+ if (result & HASH_SOURCE_CODE_FOUND_TIME) {
+ LOG_RAW("Disabling direct mode");
+ ctx.config.set_direct_mode(false);
+ return {};
}
+ hash.hash(input_file_digest.to_string());
+ return hash.digest();
+}
- if (!found_ccbin && ctx.args_info.actual_language == "cu") {
- hash_nvcc_host_compiler(ctx, hash);
+static bool
+hash_profile_data_file(const Context& ctx, Hash& hash)
+{
+ const std::string& profile_path = ctx.args_info.profile_path;
+ std::string_view base_name = Util::remove_extension(ctx.args_info.output_obj);
+ std::string hashified_cwd = ctx.apparent_cwd;
+ std::replace(hashified_cwd.begin(), hashified_cwd.end(), '/', '#');
+
+ std::vector<std::string> paths_to_try{
+ // -fprofile-use[=dir]/-fbranch-probabilities (GCC <9)
+ FMT("{}/{}.gcda", profile_path, base_name),
+ // -fprofile-use[=dir]/-fbranch-probabilities (GCC >=9)
+ 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("{}/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);
+ auto st = Stat::stat(p);
+ if (st && !st.is_directory()) {
+ LOG("Adding profile data {} to the hash", p);
+ hash.hash_delimiter("-fprofile-use");
+ if (hash_binary_file(ctx, hash, p)) {
+ found = true;
+ }
+ }
}
+ return found;
+}
+
+static nonstd::expected<void, Failure>
+hash_profiling_related_data(const Context& ctx, Hash& hash)
+{
// For profile generation (-fprofile(-instr)-generate[=path])
// - hash profile path
//
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);
+
+ // For a relative profile directory D the compiler stores $PWD/D as part of
+ // the profile filename so we need to include the same information in the
+ // hash.
+ const std::string profile_path =
+ util::is_absolute_path(ctx.args_info.profile_path)
+ ? ctx.args_info.profile_path
+ : FMT("{}/{}", ctx.apparent_cwd, ctx.args_info.profile_path);
+ LOG("Adding profile directory {} to our hash", profile_path);
hash.hash_delimiter("-fprofile-dir");
- hash.hash(ctx.args_info.profile_path);
+ hash.hash(profile_path);
}
if (ctx.args_info.profile_use && !hash_profile_data_file(ctx, hash)) {
LOG_RAW("No profile data file found");
- throw Failure(Statistic::no_input_file);
+ return nonstd::make_unexpected(Statistic::no_input_file);
}
- // Adding -arch to hash since cpp output is affected.
- for (const auto& arch : ctx.args_info.arch_args) {
- hash.hash_delimiter("-arch");
- hash.hash(arch);
+ return {};
+}
+
+static std::optional<Digest>
+get_result_key_from_manifest(Context& ctx, const Digest& manifest_key)
+{
+ MTR_BEGIN("manifest", "manifest_get");
+ std::optional<Digest> result_key;
+ size_t read_manifests = 0;
+ ctx.storage.get(
+ manifest_key, core::CacheEntryType::manifest, [&](util::Bytes&& value) {
+ try {
+ read_manifest(ctx, value);
+ ++read_manifests;
+ result_key = ctx.manifest.look_up_result_digest(ctx);
+ } catch (const core::Error& e) {
+ LOG("Failed to look up result key in manifest: {}", e.what());
+ }
+ if (result_key) {
+ LOG_RAW("Got result key from manifest");
+ return true;
+ } else {
+ LOG_RAW("Did not find result key in manifest");
+ return false;
+ }
+ });
+ MTR_END("manifest", "manifest_get");
+ if (read_manifests > 1 && !ctx.config.remote_only()) {
+ MTR_SCOPE("manifest", "merge");
+ LOG("Storing merged manifest {} locally", manifest_key.to_string());
+ core::CacheEntry::Header header(ctx.config, core::CacheEntryType::manifest);
+ ctx.storage.local.put(manifest_key,
+ core::CacheEntryType::manifest,
+ core::CacheEntry::serialize(header, ctx.manifest));
}
- optional<Digest> result_name;
+ return result_key;
+}
+
+// Update a hash sum with information specific to the direct and preprocessor
+// modes and calculate the result key. Returns the result key on success, and
+// if direct_mode is true also the manifest key.
+static nonstd::expected<std::pair<std::optional<Digest>, std::optional<Digest>>,
+ Failure>
+calculate_result_and_manifest_key(Context& ctx,
+ const Args& args,
+ Args& preprocessor_args,
+ Hash& hash,
+ bool direct_mode)
+{
+ bool found_ccbin = false;
+
+ hash.hash_delimiter("cache entry version");
+ hash.hash(core::CacheEntry::k_format_version);
+
+ hash.hash_delimiter("result version");
+ hash.hash(core::Result::k_format_version);
+
if (direct_mode) {
- // Hash environment variables that affect the preprocessor output.
- const char* envvars[] = {"CPATH",
- "C_INCLUDE_PATH",
- "CPLUS_INCLUDE_PATH",
- "OBJC_INCLUDE_PATH",
- "OBJCPLUS_INCLUDE_PATH", // clang
- nullptr};
- for (const char** p = envvars; *p; ++p) {
- const char* v = getenv(*p);
- if (v) {
- hash.hash_delimiter(*p);
- hash.hash(v);
- }
- }
+ hash.hash_delimiter("manifest version");
+ hash.hash(core::Manifest::k_format_version);
+ }
- // Make sure that the direct mode hash is unique for the input file path.
- // If this would not be the case:
- //
- // * An false cache hit may be produced. Scenario:
- // - a/r.h exists.
- // - a/x.c has #include "r.h".
- // - b/x.c is identical to a/x.c.
- // - Compiling a/x.c records a/r.h in the manifest.
- // - Compiling b/x.c results in a false cache hit since a/x.c and b/x.c
- // share manifests and a/r.h exists.
- // * The expansion of __FILE__ may be incorrect.
- hash.hash_delimiter("inputfile");
- hash.hash(ctx.args_info.input_file);
-
- hash.hash_delimiter("sourcecode");
- int result = hash_source_code_file(ctx, hash, ctx.args_info.input_file);
- if (result & HASH_SOURCE_CODE_ERROR) {
- throw Failure(Statistic::internal_error);
- }
- if (result & HASH_SOURCE_CODE_FOUND_TIME) {
- LOG_RAW("Disabling direct mode");
- ctx.config.set_direct_mode(false);
- return nullopt;
- }
+ // clang will emit warnings for unused linker flags, so we shouldn't skip
+ // those arguments.
+ int is_clang = ctx.config.is_compiler_group_clang()
+ || ctx.config.compiler_type() == CompilerType::other;
- const auto manifest_name = hash.digest();
- ctx.set_manifest_name(manifest_name);
+ // First the arguments.
+ for (size_t i = 1; i < args.size(); i++) {
+ TRY(hash_argument(ctx, args, i, hash, is_clang, direct_mode, found_ccbin));
+ }
- const auto manifest_file = look_up_cache_file(
- ctx.config.cache_dir(), manifest_name, Manifest::k_file_suffix);
- ctx.set_manifest_path(manifest_file.path);
+ // Make results with dependency file /dev/null different from those without
+ // it.
+ if (ctx.args_info.generating_dependencies
+ && ctx.args_info.output_dep == "/dev/null") {
+ hash.hash_delimiter("/dev/null dependency file");
+ }
- if (manifest_file.stat) {
- 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_RAW("Got result name from manifest");
- } else {
- LOG_RAW("Did not find result name in manifest");
- }
- } else {
- LOG("No manifest with name {} in the cache", manifest_name.to_string());
- }
+ if (!found_ccbin && ctx.args_info.actual_language == "cu") {
+ TRY(hash_nvcc_host_compiler(ctx, hash));
+ }
+
+ TRY(hash_profiling_related_data(ctx, hash));
+
+ // Adding -arch to hash since cpp output is affected.
+ for (const auto& arch : ctx.args_info.arch_args) {
+ hash.hash_delimiter("-arch");
+ hash.hash(arch);
+ }
+
+ std::optional<Digest> result_key;
+ std::optional<Digest> manifest_key;
+
+ if (direct_mode) {
+ const auto manifest_key_result = get_manifest_key(ctx, hash);
+ if (!manifest_key_result) {
+ return nonstd::make_unexpected(manifest_key_result.error());
+ }
+ manifest_key = *manifest_key_result;
+ if (manifest_key) {
+ LOG("Manifest key: {}", manifest_key->to_string());
+ result_key = get_result_key_from_manifest(ctx, *manifest_key);
+ }
+ } else if (ctx.args_info.arch_args.empty()) {
+ const auto digest = get_result_key_from_cpp(ctx, preprocessor_args, hash);
+ if (!digest) {
+ return nonstd::make_unexpected(digest.error());
+ }
+ result_key = *digest;
+ LOG_RAW("Got result key from preprocessor");
} else {
- if (ctx.args_info.arch_args.empty()) {
- result_name = get_result_name_from_cpp(ctx, preprocessor_args, hash);
- 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 {}",
- ctx.args_info.arch_args[i]);
- if (i != ctx.args_info.arch_args.size() - 1) {
- result_name = nullopt;
- }
- preprocessor_args.pop_back();
+ 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]);
+ const auto digest = get_result_key_from_cpp(ctx, preprocessor_args, hash);
+ if (!digest) {
+ return nonstd::make_unexpected(digest.error());
+ }
+ result_key = *digest;
+ LOG("Got result key from preprocessor with -arch {}",
+ ctx.args_info.arch_args[i]);
+ if (i != ctx.args_info.arch_args.size() - 1) {
+ result_key = std::nullopt;
}
preprocessor_args.pop_back();
}
+ preprocessor_args.pop_back();
}
- return result_name;
+ if (result_key) {
+ LOG("Result key: {}", result_key->to_string());
+ }
+ return std::make_pair(result_key, manifest_key);
}
enum class FromCacheCallMode { direct, cpp };
// Try to return the compile result from cache.
-static optional<Statistic>
-from_cache(Context& ctx, FromCacheCallMode mode)
+static nonstd::expected<bool, Failure>
+from_cache(Context& ctx, FromCacheCallMode mode, const Digest& result_key)
{
UmaskScope umask_scope(ctx.original_umask);
// The user might be disabling cache hits.
if (ctx.config.recache()) {
- return nullopt;
+ return false;
}
// If we're using Clang, we can't trust a precompiled header object based on
//
// file 'foo.h' has been modified since the precompiled header 'foo.pch'
// was built
- if ((ctx.config.compiler_type() == CompilerType::clang
+ if ((ctx.config.is_compiler_group_clang()
|| ctx.config.compiler_type() == CompilerType::other)
&& ctx.args_info.output_is_precompiled_header
- && !ctx.args_info.fno_pch_timestamp && mode == FromCacheCallMode::cpp) {
+ && mode == FromCacheCallMode::cpp) {
LOG_RAW("Not considering cached precompiled header in preprocessor mode");
- return nullopt;
+ return false;
}
- MTR_BEGIN("cache", "from_cache");
+ MTR_SCOPE("cache", "from_cache");
// Get result from cache.
- 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());
- return nullopt;
+ util::Bytes cache_entry_data;
+ ctx.storage.get(
+ result_key, core::CacheEntryType::result, [&](util::Bytes&& value) {
+ cache_entry_data = std::move(value);
+ return true;
+ });
+ if (cache_entry_data.empty()) {
+ return false;
}
- ctx.set_result_path(result_file.path);
- Result::Reader result_reader(result_file.path);
- ResultRetriever result_retriever(
- ctx, should_rewrite_dependency_target(ctx.args_info));
- auto error = result_reader.read(result_retriever);
- MTR_END("cache", "from_cache");
- if (error) {
- LOG("Failed to get result from cache: {}", *error);
- return nullopt;
+ try {
+ core::CacheEntry cache_entry(cache_entry_data);
+ cache_entry.verify_checksum();
+ core::Result::Deserializer deserializer(cache_entry.payload());
+ core::ResultRetriever result_retriever(ctx, result_key);
+ deserializer.visit(result_retriever);
+ } catch (core::ResultRetriever::WriteError& e) {
+ LOG("Write error when retrieving result from {}: {}",
+ result_key.to_string(),
+ e.what());
+ return nonstd::make_unexpected(Statistic::bad_output_file);
+ } catch (core::Error& e) {
+ LOG("Failed to get result from {}: {}", result_key.to_string(), e.what());
+ return false;
}
- // Update modification timestamp to save file from LRU cleanup.
- Util::update_mtime(*ctx.result_path());
-
LOG_RAW("Succeeded getting cached result");
-
- return mode == FromCacheCallMode::direct ? Statistic::direct_cache_hit
- : Statistic::preprocessed_cache_hit;
+ return true;
}
// Find the real compiler and put it into ctx.orig_args[0]. We just search the
-// PATH to find an executable of the same name that isn't a link to ourselves.
-// Pass find_executable function as second parameter.
+// PATH to find an executable of the same name that isn't ourselves.
void
find_compiler(Context& ctx,
const FindExecutableFunction& find_executable_function)
// 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)) {
+ && Util::is_ccache_executable(ctx.orig_args[compiler_pos])) {
++compiler_pos;
}
: ctx.orig_args[compiler_pos]);
const std::string resolved_compiler =
- Util::is_full_path(compiler)
+ util::is_full_path(compiler)
? compiler
- : find_executable_function(ctx, compiler, CCACHE_NAME);
+ : find_executable_function(ctx, compiler, ctx.orig_args[0]);
if (resolved_compiler.empty()) {
- throw Fatal("Could not find compiler \"{}\" in PATH", compiler);
+ throw core::Fatal(FMT("Could not find compiler \"{}\" in PATH", compiler));
}
- if (Util::same_program_name(Util::base_name(resolved_compiler),
- CCACHE_NAME)) {
- throw Fatal(
- "Recursive invocation (the name of the ccache binary must be \"{}\")",
- CCACHE_NAME);
+ if (Util::is_ccache_executable(resolved_compiler)) {
+ throw core::Fatal("Recursive invocation of ccache");
}
ctx.orig_args.pop_front(compiler_pos);
ctx.orig_args[0] = resolved_compiler;
}
-static std::string
-default_cache_dir(const std::string& home_dir)
-{
-#ifdef _WIN32
- return home_dir + "/ccache";
-#elif defined(__APPLE__)
- return home_dir + "/Library/Caches/ccache";
-#else
- return home_dir + "/.cache/ccache";
-#endif
-}
-
-static std::string
-default_config_dir(const std::string& home_dir)
-{
-#ifdef _WIN32
- return home_dir + "/ccache";
-#elif defined(__APPLE__)
- return home_dir + "/Library/Preferences/ccache";
-#else
- return home_dir + "/.config/ccache";
-#endif
-}
-
-// Read config file(s), populate variables, create configuration file in cache
-// directory if missing, etc.
-static void
-set_up_config(Config& config)
-{
- const std::string home_dir = Util::get_home_directory();
- const std::string legacy_ccache_dir = home_dir + "/.ccache";
- const bool legacy_ccache_dir_exists =
- Stat::stat(legacy_ccache_dir).is_directory();
- const char* const env_xdg_cache_home = getenv("XDG_CACHE_HOME");
- const char* const env_xdg_config_home = getenv("XDG_CONFIG_HOME");
-
- const char* env_ccache_configpath = getenv("CCACHE_CONFIGPATH");
- if (env_ccache_configpath) {
- config.set_primary_config_path(env_ccache_configpath);
- } else {
- // 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("{}/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());
- MTR_END("config", "conf_read_secondary");
-
- const char* const env_ccache_dir = getenv("CCACHE_DIR");
- std::string primary_config_dir;
- if (env_ccache_dir && *env_ccache_dir) {
- primary_config_dir = env_ccache_dir;
- } else if (!config.cache_dir().empty() && !env_ccache_dir) {
- primary_config_dir = config.cache_dir();
- } else if (legacy_ccache_dir_exists) {
- primary_config_dir = legacy_ccache_dir;
- } else if (env_xdg_config_home) {
- primary_config_dir = FMT("{}/ccache", env_xdg_config_home);
- } else {
- primary_config_dir = default_config_dir(home_dir);
- }
- config.set_primary_config_path(primary_config_dir + "/ccache.conf");
- }
-
- const std::string& cache_dir_before_primary_config = config.cache_dir();
-
- MTR_BEGIN("config", "conf_read_primary");
- config.update_from_file(config.primary_config_path());
- MTR_END("config", "conf_read_primary");
-
- // Ignore cache_dir set in primary config.
- config.set_cache_dir(cache_dir_before_primary_config);
-
- MTR_BEGIN("config", "conf_update_from_environment");
- config.update_from_environment();
- // (config.cache_dir is set above if CCACHE_DIR is set.)
- MTR_END("config", "conf_update_from_environment");
-
- if (config.cache_dir().empty()) {
- if (legacy_ccache_dir_exists) {
- config.set_cache_dir(legacy_ccache_dir);
- } else if (env_xdg_cache_home) {
- config.set_cache_dir(FMT("{}/ccache", env_xdg_cache_home));
- } else {
- config.set_cache_dir(default_cache_dir(home_dir));
- }
- }
- // else: cache_dir was set explicitly via environment or via secondary config.
-
- // We have now determined config.cache_dir and populated the rest of config in
- // prio order (1. environment, 2. primary config, 3. secondary config).
-}
-
-static void
-set_up_context(Context& ctx, int argc, const char* const* argv)
-{
- ctx.orig_args = Args::from_argv(argc, argv);
- ctx.ignore_header_paths = Util::split_into_strings(
- ctx.config.ignore_headers_in_manifest(), PATH_DELIM);
- ctx.set_ignore_options(
- Util::split_into_strings(ctx.config.ignore_options(), " "));
-}
-
-// Initialize ccache, must be called once before anything else is run.
+// Initialize ccache. Must be called once before anything else is run.
static void
initialize(Context& ctx, int argc, const char* const* argv)
{
- set_up_config(ctx.config);
- set_up_context(ctx, argc, argv);
- Logging::init(ctx.config);
-
- // Set default umask for all files created by ccache from now on (if
- // configured to). This is intentionally done after calling init_log so that
- // the log file won't be affected by the umask but before creating the initial
- // configuration file. The intention is that all files and directories in the
- // cache directory should be affected by the configured umask and that no
- // other files and directories should.
- if (ctx.config.umask() != std::numeric_limits<uint32_t>::max()) {
- ctx.original_umask = umask(ctx.config.umask());
- }
+ ctx.orig_args = Args::from_argv(argc, argv);
+ ctx.storage.initialize();
LOG("=== CCACHE {} STARTED =========================================",
CCACHE_VERSION);
+ LOG("Configuration file: {}", ctx.config.config_path());
+ LOG("System configuration file: {}", ctx.config.system_config_path());
+
if (getenv("CCACHE_INTERNAL_TRACE")) {
#ifdef MTR_ENABLED
ctx.mini_trace = std::make_unique<MiniTrace>(ctx.args_info);
// Make a copy of stderr that will not be cached, so things like distcc can
// send networking errors to it.
-static void
+static nonstd::expected<void, Failure>
set_up_uncached_err()
{
int uncached_fd =
dup(STDERR_FILENO); // The file descriptor is intentionally leaked.
if (uncached_fd == -1) {
LOG("dup(2) failed: {}", strerror(errno));
- throw Failure(Statistic::internal_error);
+ return nonstd::make_unexpected(Statistic::internal_error);
}
Util::setenv("UNCACHED_ERR_FD", FMT("{}", uncached_fd));
-}
-
-static void
-configuration_logger(const std::string& key,
- const std::string& value,
- const std::string& origin)
-{
- BULK_LOG("Config: ({}) {} = {}", origin, key, value);
-}
-
-static void
-configuration_printer(const std::string& key,
- const std::string& value,
- const std::string& origin)
-{
- PRINT(stdout, "({}) {} = {}\n", origin, key, value);
+ return {};
}
static int cache_compilation(int argc, const char* const* argv);
-static Statistic do_cache_compilation(Context& ctx, const char* const* argv);
-static uint8_t
-calculate_wanted_cache_level(uint64_t files_in_level_1)
-{
- uint64_t files_per_directory = files_in_level_1 / 16;
- for (uint8_t i = k_min_cache_levels; i <= k_max_cache_levels; ++i) {
- if (files_per_directory < k_max_cache_files_per_directory) {
- return i;
- }
- files_per_directory /= 16;
- }
- return k_max_cache_levels;
-}
-
-static optional<Counters>
-update_stats_and_maybe_move_cache_file(const Context& ctx,
- const Digest& name,
- const std::string& current_path,
- const Counters& counter_updates,
- const std::string& file_suffix)
-{
- if (counter_updates.all_zero()) {
- return nullopt;
- }
-
- // Use stats file in the level one subdirectory for cache bookkeeping counters
- // since cleanup is performed on level one. Use stats file in the level two
- // subdirectory for other counters to reduce lock contention.
- 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("{:x}", name.bytes()[0] >> 4);
- if (!use_stats_on_level_1) {
- level_string += FMT("/{:x}", name.bytes()[0] & 0xF);
- }
- const auto stats_file =
- FMT("{}/{}/stats", ctx.config.cache_dir(), level_string);
-
- auto counters =
- Statistics::update(stats_file, [&counter_updates](Counters& cs) {
- cs.increment(counter_updates);
- });
- if (!counters) {
- return nullopt;
- }
-
- if (use_stats_on_level_1) {
- // Only consider moving the cache file to another level when we have read
- // the level 1 stats file since it's only then we know the proper
- // files_in_cache value.
- const auto wanted_level =
- calculate_wanted_cache_level(counters->get(Statistic::files_in_cache));
- const auto wanted_path = Util::get_path_in_cache(
- 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);
- try {
- Util::rename(current_path, wanted_path);
- } catch (const Error&) {
- // Two ccache processes may move the file at the same time, so failure
- // to rename is OK.
- }
- }
- }
- return counters;
-}
+static nonstd::expected<core::StatisticsCounters, Failure>
+do_cache_compilation(Context& ctx, const char* const* argv);
static void
-finalize_stats_and_trigger_cleanup(Context& ctx)
+log_result_to_debug_log(Context& ctx)
{
- const auto& config = ctx.config;
-
- if (config.disable()) {
- // Just log result, don't update statistics.
- LOG_RAW("Result: disabled");
+ if (ctx.config.log_file().empty() && !ctx.config.debug()) {
return;
}
- if (!config.log_file().empty() || config.debug()) {
- const auto result = Statistics::get_result(ctx.counter_updates);
- if (result) {
- LOG("Result: {}", *result);
- }
- }
-
- if (!config.stats()) {
- return;
+ core::Statistics statistics(ctx.storage.local.get_statistics_updates());
+ for (const auto& message : statistics.get_statistics_ids()) {
+ LOG("Result: {}", message);
}
+}
- if (!ctx.result_path()) {
- ASSERT(ctx.counter_updates.get(Statistic::cache_size_kibibyte) == 0);
- ASSERT(ctx.counter_updates.get(Statistic::files_in_cache) == 0);
-
- // 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("{}/{:x}/{:x}/stats", config.cache_dir(), bucket / 16, bucket % 16);
- Statistics::update(
- stats_file, [&ctx](Counters& cs) { cs.increment(ctx.counter_updates); });
+static void
+log_result_to_stats_log(Context& ctx)
+{
+ if (ctx.config.stats_log().empty()) {
return;
}
- if (ctx.manifest_path()) {
- update_stats_and_maybe_move_cache_file(ctx,
- *ctx.manifest_name(),
- *ctx.manifest_path(),
- ctx.manifest_counter_updates,
- Manifest::k_file_suffix);
- }
-
- const auto counters =
- update_stats_and_maybe_move_cache_file(ctx,
- *ctx.result_name(),
- *ctx.result_path(),
- ctx.counter_updates,
- Result::k_file_suffix);
- if (!counters) {
+ core::Statistics statistics(ctx.storage.local.get_statistics_updates());
+ const auto ids = statistics.get_statistics_ids();
+ if (ids.empty()) {
return;
}
- 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)",
- subdir,
- counters->get(Statistic::files_in_cache),
- config.max_files() / 16);
- need_cleanup = true;
- }
- 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)",
- subdir,
- counters->get(Statistic::cache_size_kibibyte),
- config.max_size() / 1024 / 16);
- need_cleanup = true;
- }
-
- if (need_cleanup) {
- const double factor = config.limit_multiple() / 16;
- const uint64_t max_size = round(config.max_size() * factor);
- const uint32_t max_files = round(config.max_files() * factor);
- const time_t max_age = 0;
- clean_up_dir(
- subdir, max_size, max_files, max_age, [](double /*progress*/) {});
- }
+ core::StatsLog(ctx.config.stats_log())
+ .log_result(ctx.args_info.input_file, ids);
}
static void
finalize_at_exit(Context& ctx)
{
try {
- finalize_stats_and_trigger_cleanup(ctx);
- } catch (const ErrorBase& e) {
+ if (ctx.config.disable()) {
+ // Just log result, don't update statistics.
+ LOG_RAW("Result: disabled");
+ return;
+ }
+
+ log_result_to_debug_log(ctx);
+ log_result_to_stats_log(ctx);
+
+ ctx.storage.finalize();
+ } catch (const core::ErrorBase& e) {
// finalize_at_exit must not throw since it's called by a destructor.
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()) {
- Logging::dump_log(prepare_debug_path(
- ctx.config.debug_dir(), ctx.args_info.output_obj, "log"));
+ Logging::dump_log(prepare_debug_path(ctx.config.debug_dir(),
+ ctx.time_of_invocation,
+ ctx.args_info.output_obj,
+ "log"));
}
}
bool fall_back_to_original_compiler = false;
Args saved_orig_args;
- nonstd::optional<mode_t> original_umask;
+ std::optional<uint32_t> original_umask;
std::string saved_temp_dir;
{
Context ctx;
+ ctx.initialize();
SignalHandler signal_handler(ctx);
Finalizer finalizer([&ctx] { finalize_at_exit(ctx); });
find_compiler(ctx, &find_executable);
MTR_END("main", "find_compiler");
- try {
- Statistic statistic = do_cache_compilation(ctx, argv);
- ctx.counter_updates.increment(statistic);
- } catch (const Failure& e) {
- if (e.statistic() != Statistic::none) {
- ctx.counter_updates.increment(e.statistic());
- }
-
- if (e.exit_code()) {
- return *e.exit_code();
+ const auto result = do_cache_compilation(ctx, argv);
+ const auto& counters = result ? *result : result.error().counters();
+ ctx.storage.local.increment_statistics(counters);
+ if (!result) {
+ if (result.error().exit_code()) {
+ return *result.error().exit_code();
}
// Else: Fall back to running the real compiler.
fall_back_to_original_compiler = true;
if (fall_back_to_original_compiler) {
if (original_umask) {
- umask(*original_umask);
+ Util::set_umask(*original_umask);
}
auto execv_argv = saved_orig_args.to_argv();
execute_noreturn(execv_argv.data(), saved_temp_dir);
- throw Fatal(
- "execute_noreturn of {} failed: {}", execv_argv[0], strerror(errno));
+ throw core::Fatal(
+ FMT("execute_noreturn of {} failed: {}", execv_argv[0], strerror(errno)));
}
return EXIT_SUCCESS;
}
-static Statistic
+static nonstd::expected<core::StatisticsCounters, Failure>
do_cache_compilation(Context& ctx, const char* const* argv)
{
if (ctx.actual_cwd.empty()) {
LOG("Unable to determine current working directory: {}", strerror(errno));
- throw Failure(Statistic::internal_error);
+ return nonstd::make_unexpected(Statistic::internal_error);
}
- MTR_BEGIN("main", "clean_up_internal_tempdir");
- if (ctx.config.temporary_dir() == ctx.config.cache_dir() + "/tmp") {
- clean_up_internal_tempdir(ctx.config);
- }
- MTR_END("main", "clean_up_internal_tempdir");
-
if (!ctx.config.log_file().empty() || ctx.config.debug()) {
- ctx.config.visit_items(configuration_logger);
+ ctx.config.visit_items([&ctx](const std::string& key,
+ const std::string& value,
+ const std::string& origin) {
+ const auto& log_value =
+ key == "remote_storage"
+ ? ctx.storage.get_remote_storage_config_for_logging()
+ : value;
+ BULK_LOG("Config: ({}) {} = {}", origin, key, log_value);
+ });
}
// Guess compiler after logging the config value in order to be able to
- // display "compiler_type = auto" before overwriting the value with the guess.
+ // 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]));
}
if (ctx.config.disable()) {
LOG_RAW("ccache is disabled");
- // Statistic::cache_miss is a dummy to trigger stats_flush.
- throw Failure(Statistic::cache_miss);
+ return nonstd::make_unexpected(Statistic::none);
}
LOG("Command line: {}", Util::format_argv_for_logging(argv));
LOG("Compiler type: {}", compiler_type_to_string(ctx.config.compiler_type()));
+ // Set CCACHE_DISABLE so no process ccache executes from now on will risk
+ // calling ccache second time. For instance, if the real compiler is a wrapper
+ // script that calls "ccache $compiler ..." we want that inner ccache call to
+ // be disabled.
+ Util::setenv("CCACHE_DISABLE", "1");
+
MTR_BEGIN("main", "process_args");
ProcessArgsResult processed = process_args(ctx);
MTR_END("main", "process_args");
if (processed.error) {
- throw Failure(*processed.error);
+ return nonstd::make_unexpected(*processed.error);
+ }
+
+ TRY(set_up_uncached_err());
+
+ // VS_UNICODE_OUTPUT prevents capturing stdout/stderr, as the output is sent
+ // directly to Visual Studio.
+ if (ctx.config.compiler_type() == CompilerType::msvc) {
+ Util::unsetenv("VS_UNICODE_OUTPUT");
+ }
+
+ for (const auto& name : {"DEPENDENCIES_OUTPUT", "SUNPRO_DEPENDENCIES"}) {
+ if (getenv(name)) {
+ LOG("Unsupported environment variable: {}", name);
+ return Statistic::unsupported_environment_variable;
+ }
}
- set_up_uncached_err();
+ if (ctx.config.is_compiler_group_msvc()) {
+ for (const auto& name : {"CL", "_CL_"}) {
+ if (getenv(name)) {
+ LOG("Unsupported environment variable: {}", name);
+ return Statistic::unsupported_environment_variable;
+ }
+ }
+ }
+
+ if (!ctx.config.run_second_cpp() && ctx.config.is_compiler_group_msvc()) {
+ LOG_RAW("Second preprocessor cannot be disabled");
+ ctx.config.set_run_second_cpp(true);
+ }
+
+ if (ctx.config.depend_mode()) {
+ const bool deps = ctx.args_info.generating_dependencies
+ && ctx.args_info.output_dep != "/dev/null";
+ const bool includes = ctx.args_info.generating_includes;
+ if (!ctx.config.run_second_cpp() || (!deps && !includes)) {
+ LOG_RAW("Disabling depend mode");
+ ctx.config.set_depend_mode(false);
+ }
+ }
- if (ctx.config.depend_mode()
- && (!ctx.args_info.generating_dependencies
- || ctx.args_info.output_dep == "/dev/null"
- || !ctx.config.run_second_cpp())) {
- LOG_RAW("Disabling depend mode");
- ctx.config.set_depend_mode(false);
+ if (ctx.storage.has_remote_storage()) {
+ if (ctx.config.file_clone()) {
+ LOG_RAW("Disabling file clone mode since remote storage is enabled");
+ ctx.config.set_file_clone(false);
+ }
+ if (ctx.config.hard_link()) {
+ LOG_RAW("Disabling hard link mode since remote storage is enabled");
+ ctx.config.set_hard_link(false);
+ }
}
LOG("Source file: {}", ctx.args_info.input_file);
MTR_META_THREAD_NAME(ctx.args_info.output_obj.c_str());
if (ctx.config.debug()) {
- const auto path = prepare_debug_path(
- ctx.config.debug_dir(), ctx.args_info.output_obj, "input-text");
+ const auto path = prepare_debug_path(ctx.config.debug_dir(),
+ ctx.time_of_invocation,
+ ctx.args_info.orig_output_obj,
+ "input-text");
File debug_text_file(path, "w");
if (debug_text_file) {
ctx.hash_debug_files.push_back(std::move(debug_text_file));
Hash common_hash;
init_hash_debug(ctx, common_hash, 'c', "COMMON", debug_text_file);
- MTR_BEGIN("hash", "common_hash");
- hash_common_info(
- ctx, processed.preprocessor_args, common_hash, ctx.args_info);
- MTR_END("hash", "common_hash");
+ {
+ MTR_SCOPE("hash", "common_hash");
+ TRY(hash_common_info(
+ ctx, processed.preprocessor_args, common_hash, ctx.args_info));
+ }
+
+ if (processed.hash_actual_cwd) {
+ common_hash.hash_delimiter("actual_cwd");
+ common_hash.hash(ctx.actual_cwd);
+ }
// Try to find the hash using the manifest.
Hash direct_hash = common_hash;
args_to_hash.push_back(processed.extra_args_to_hash);
bool put_result_in_manifest = false;
- optional<Digest> result_name;
- optional<Digest> result_name_from_manifest;
+ std::optional<Digest> result_key;
+ std::optional<Digest> result_key_from_manifest;
+ std::optional<Digest> manifest_key;
+
if (ctx.config.direct_mode()) {
LOG_RAW("Trying direct lookup");
- MTR_BEGIN("hash", "direct_hash");
Args dummy_args;
- result_name =
- calculate_result_name(ctx, args_to_hash, dummy_args, direct_hash, true);
+ MTR_BEGIN("hash", "direct_hash");
+ const auto result_and_manifest_key = calculate_result_and_manifest_key(
+ ctx, args_to_hash, dummy_args, direct_hash, true);
MTR_END("hash", "direct_hash");
- if (result_name) {
- ctx.set_result_name(*result_name);
-
+ if (!result_and_manifest_key) {
+ return nonstd::make_unexpected(result_and_manifest_key.error());
+ }
+ std::tie(result_key, manifest_key) = *result_and_manifest_key;
+ if (result_key) {
// If we can return from cache at this point then do so.
- auto result = from_cache(ctx, FromCacheCallMode::direct);
- if (result) {
- return *result;
+ const auto from_cache_result =
+ from_cache(ctx, FromCacheCallMode::direct, *result_key);
+ if (!from_cache_result) {
+ return nonstd::make_unexpected(from_cache_result.error());
+ } else if (*from_cache_result) {
+ return Statistic::direct_cache_hit;
}
// Wasn't able to return from cache at this point. However, the result
// was already found in manifest, so don't re-add it later.
put_result_in_manifest = false;
- result_name_from_manifest = result_name;
+ result_key_from_manifest = result_key;
} else {
// Add result to manifest later.
put_result_in_manifest = true;
}
+
+ if (!ctx.config.recache()) {
+ ctx.storage.local.increment_statistic(Statistic::direct_cache_miss);
+ }
}
if (ctx.config.read_only_direct()) {
LOG_RAW("Read-only direct mode; running real compiler");
- throw Failure(Statistic::cache_miss);
+ return nonstd::make_unexpected(Statistic::cache_miss);
}
if (!ctx.config.depend_mode()) {
init_hash_debug(ctx, cpp_hash, 'p', "PREPROCESSOR MODE", debug_text_file);
MTR_BEGIN("hash", "cpp_hash");
- result_name = calculate_result_name(
+ const auto result_and_manifest_key = calculate_result_and_manifest_key(
ctx, args_to_hash, processed.preprocessor_args, cpp_hash, false);
MTR_END("hash", "cpp_hash");
+ if (!result_and_manifest_key) {
+ return nonstd::make_unexpected(result_and_manifest_key.error());
+ }
+ result_key = result_and_manifest_key->first;
- // calculate_result_name does not return nullopt if the last (direct_mode)
- // argument is false.
- ASSERT(result_name);
- ctx.set_result_name(*result_name);
-
- if (result_name_from_manifest && result_name_from_manifest != result_name) {
- // manifest_path is guaranteed to be set when calculate_result_name
- // returns a non-nullopt result in direct mode, i.e. when
- // result_name_from_manifest is set.
- ASSERT(ctx.manifest_path());
+ // calculate_result_and_manifest_key always returns a non-nullopt result_key
+ // if the last argument (direct_mode) is false.
+ ASSERT(result_key);
+ if (result_key_from_manifest && result_key_from_manifest != result_key) {
// The hash from manifest differs from the hash of the preprocessor
// output. This could be because:
//
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());
+ ctx.storage.remove(*result_key, core::CacheEntryType::result);
put_result_in_manifest = true;
}
// If we can return from cache at this point then do.
- auto result = from_cache(ctx, FromCacheCallMode::cpp);
- if (result) {
- if (put_result_in_manifest) {
- update_manifest_file(ctx);
+ const auto from_cache_result =
+ from_cache(ctx, FromCacheCallMode::cpp, *result_key);
+ if (!from_cache_result) {
+ return nonstd::make_unexpected(from_cache_result.error());
+ } else if (*from_cache_result) {
+ if (ctx.config.direct_mode() && manifest_key && put_result_in_manifest) {
+ MTR_SCOPE("cache", "update_manifest");
+ update_manifest(ctx, *manifest_key, *result_key);
}
- return *result;
+ return Statistic::preprocessed_cache_hit;
+ }
+
+ if (!ctx.config.recache()) {
+ ctx.storage.local.increment_statistic(Statistic::preprocessed_cache_miss);
}
}
if (ctx.config.read_only()) {
LOG_RAW("Read-only mode; running real compiler");
- throw Failure(Statistic::cache_miss);
+ return nonstd::make_unexpected(Statistic::cache_miss);
}
add_prefix(ctx, processed.compiler_args, ctx.config.prefix_command());
// Run real compiler, sending output to cache.
MTR_BEGIN("cache", "to_cache");
- to_cache(ctx,
- processed.compiler_args,
- ctx.args_info.depend_extra_args,
- depend_mode_hash);
- update_manifest_file(ctx);
+ const auto digest = to_cache(ctx,
+ processed.compiler_args,
+ result_key,
+ ctx.args_info.depend_extra_args,
+ depend_mode_hash);
MTR_END("cache", "to_cache");
-
- return Statistic::cache_miss;
-}
-
-// The main program when not doing a compile.
-static int
-handle_main_options(int argc, const char* const* argv)
-{
- enum longopts {
- CHECKSUM_FILE,
- CONFIG_PATH,
- DUMP_MANIFEST,
- DUMP_RESULT,
- EVICT_OLDER_THAN,
- EXTRACT_RESULT,
- HASH_FILE,
- PRINT_STATS,
- };
- static const struct option options[] = {
- {"checksum-file", required_argument, nullptr, CHECKSUM_FILE},
- {"cleanup", no_argument, nullptr, 'c'},
- {"clear", no_argument, nullptr, 'C'},
- {"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},
- {"extract-result", required_argument, nullptr, EXTRACT_RESULT},
- {"get-config", required_argument, nullptr, 'k'},
- {"hash-file", required_argument, nullptr, HASH_FILE},
- {"help", no_argument, nullptr, 'h'},
- {"max-files", required_argument, nullptr, 'F'},
- {"max-size", required_argument, nullptr, 'M'},
- {"print-stats", no_argument, nullptr, PRINT_STATS},
- {"recompress", required_argument, nullptr, 'X'},
- {"set-config", required_argument, nullptr, 'o'},
- {"show-compression", no_argument, nullptr, 'x'},
- {"show-config", no_argument, nullptr, 'p'},
- {"show-stats", no_argument, nullptr, 's'},
- {"version", no_argument, nullptr, 'V'},
- {"zero-stats", no_argument, nullptr, 'z'},
- {nullptr, 0, nullptr, 0}};
-
- Context ctx;
- initialize(ctx, argc, argv);
-
- int c;
- while ((c = getopt_long(argc,
- const_cast<char* const*>(argv),
- "cCd:k:hF:M:po:sVxX:z",
- options,
- nullptr))
- != -1) {
- std::string arg = optarg ? optarg : std::string();
-
- switch (c) {
- case CHECKSUM_FILE: {
- Checksum checksum;
- Fd fd(arg == "-" ? STDIN_FILENO : open(arg.c_str(), O_RDONLY));
- Util::read_fd(*fd, [&checksum](const void* data, size_t size) {
- checksum.update(data, size);
- });
- 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;
-
- case DUMP_RESULT: {
- ResultDumper result_dumper(stdout);
- Result::Reader result_reader(arg);
- auto error = result_reader.read(result_dumper);
- if (error) {
- PRINT(stderr, "Error: {}\n", *error);
- }
- return error ? EXIT_FAILURE : EXIT_SUCCESS;
- }
-
- case EVICT_OLDER_THAN: {
- auto seconds = Util::parse_duration(arg);
- ProgressBar progress_bar("Evicting...");
- clean_old(
- ctx, [&](double progress) { progress_bar.update(progress); }, seconds);
- if (isatty(STDOUT_FILENO)) {
- PRINT_RAW(stdout, "\n");
- }
- break;
- }
-
- case EXTRACT_RESULT: {
- ResultExtractor result_extractor(".");
- Result::Reader result_reader(arg);
- auto error = result_reader.read(result_extractor);
- if (error) {
- PRINT(stderr, "Error: {}\n", *error);
- }
- return error ? EXIT_FAILURE : EXIT_SUCCESS;
- }
-
- case HASH_FILE: {
- Hash hash;
- if (arg == "-") {
- hash.hash_fd(STDIN_FILENO);
- } else {
- hash.hash_file(arg);
- }
- PRINT(stdout, "{}\n", hash.digest().to_string());
- break;
- }
-
- case PRINT_STATS:
- PRINT_RAW(stdout, Statistics::format_machine_readable(ctx.config));
- break;
-
- case 'c': // --cleanup
- {
- ProgressBar progress_bar("Cleaning...");
- clean_up_all(ctx.config,
- [&](double progress) { progress_bar.update(progress); });
- if (isatty(STDOUT_FILENO)) {
- PRINT_RAW(stdout, "\n");
- }
- break;
- }
-
- case 'C': // --clear
- {
- ProgressBar progress_bar("Clearing...");
- wipe_all(ctx, [&](double progress) { progress_bar.update(progress); });
- if (isatty(STDOUT_FILENO)) {
- PRINT_RAW(stdout, "\n");
- }
- break;
- }
-
- case 'd': // --directory
- Util::setenv("CCACHE_DIR", arg);
- break;
-
- case 'h': // --help
- PRINT(stdout, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
- exit(EXIT_SUCCESS);
-
- case 'k': // --get-config
- PRINT(stdout, "{}\n", ctx.config.get_string_value(arg));
- break;
-
- case 'F': { // --max-files
- auto files = Util::parse_unsigned(arg);
- Config::set_value_in_file(
- ctx.config.primary_config_path(), "max_files", arg);
- if (files == 0) {
- PRINT_RAW(stdout, "Unset cache file limit\n");
- } else {
- PRINT(stdout, "Set cache file limit to {}\n", files);
- }
- break;
- }
-
- case 'M': { // --max-size
- uint64_t size = Util::parse_size(arg);
- Config::set_value_in_file(
- ctx.config.primary_config_path(), "max_size", arg);
- if (size == 0) {
- PRINT_RAW(stdout, "Unset cache size limit\n");
- } else {
- PRINT(stdout,
- "Set cache size limit to {}\n",
- Util::format_human_readable_size(size));
- }
- break;
- }
-
- case 'o': { // --set-config
- // Start searching for equal sign at position 1 to improve error message
- // for the -o=K=V case (key "=K" and value "V").
- size_t eq_pos = arg.find('=', 1);
- if (eq_pos == std::string::npos) {
- throw Error("missing equal sign in \"{}\"", arg);
- }
- std::string key = arg.substr(0, eq_pos);
- std::string value = arg.substr(eq_pos + 1);
- Config::set_value_in_file(ctx.config.primary_config_path(), key, value);
- break;
- }
-
- case 'p': // --show-config
- ctx.config.visit_items(configuration_printer);
- break;
-
- case 's': // --show-stats
- PRINT_RAW(stdout, Statistics::format_human_readable(ctx.config));
- break;
-
- case 'V': // --version
- PRINT(VERSION_TEXT, CCACHE_NAME, CCACHE_VERSION);
- exit(EXIT_SUCCESS);
-
- case 'x': // --show-compression
- {
- ProgressBar progress_bar("Scanning...");
- compress_stats(ctx.config,
- [&](double progress) { progress_bar.update(progress); });
- break;
- }
-
- case 'X': // --recompress
- {
- optional<int8_t> wanted_level;
- if (arg == "uncompressed") {
- wanted_level = nullopt;
- } else {
- wanted_level =
- Util::parse_signed(arg, INT8_MIN, INT8_MAX, "compression level");
- }
-
- ProgressBar progress_bar("Recompressing...");
- compress_recompress(ctx, wanted_level, [&](double progress) {
- progress_bar.update(progress);
- });
- break;
- }
-
- case 'z': // --zero-stats
- Statistics::zero_all_counters(ctx.config);
- PRINT_RAW(stdout, "Statistics zeroed\n");
- break;
-
- default:
- PRINT(stderr, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
- exit(EXIT_FAILURE);
- }
-
- // Some of the above switches might have changed config settings, so run the
- // setup again.
- ctx.config = Config();
- set_up_config(ctx.config);
+ if (!digest) {
+ return nonstd::make_unexpected(digest.error());
+ }
+ result_key = *digest;
+ if (ctx.config.direct_mode()) {
+ ASSERT(manifest_key);
+ MTR_SCOPE("cache", "update_manifest");
+ update_manifest(ctx, *manifest_key, *result_key);
}
- return 0;
+ return ctx.config.recache() ? Statistic::recache : Statistic::cache_miss;
}
-int ccache_main(int argc, const char* const* argv);
-
int
ccache_main(int argc, const char* const* argv)
{
try {
- // Check if we are being invoked as "ccache".
- std::string program_name(Util::base_name(argv[0]));
- if (Util::same_program_name(program_name, CCACHE_NAME)) {
+ if (Util::is_ccache_executable(argv[0])) {
if (argc < 2) {
- PRINT(stderr, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME);
+ PRINT_RAW(stderr, core::get_usage_text(Util::base_name(argv[0])));
exit(EXIT_FAILURE);
}
- // If the first argument isn't an option, then assume we are being passed
- // a compiler name and options.
+ // If the first argument isn't an option, then assume we are being
+ // passed a compiler name and options.
if (argv[1][0] == '-') {
- return handle_main_options(argc, argv);
+ return core::process_main_options(argc, argv);
}
}
return cache_compilation(argc, argv);
- } catch (const ErrorBase& e) {
+ } catch (const core::ErrorBase& e) {
PRINT(stderr, "ccache: error: {}\n", e.what());
return EXIT_FAILURE;
}
// Copyright (C) 2002-2007 Andrew Tridgell
-// Copyright (C) 2009-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include "Config.hpp"
-#include "third_party/nonstd/string_view.hpp"
-
#include <functional>
#include <string>
+#include <string_view>
class Context;
using FindExecutableFunction =
std::function<std::string(const Context& ctx,
const std::string& name,
- const std::string& exclude_name)>;
+ const std::string& exclude_path)>;
+
+int ccache_main(int argc, const char* const* argv);
// Tested by unit tests.
void find_compiler(Context& ctx,
const FindExecutableFunction& find_executable_function);
-CompilerType guess_compiler(nonstd::string_view path);
+CompilerType guess_compiler(std::string_view path);
+++ /dev/null
-// Copyright (C) 2002-2006 Andrew Tridgell
-// Copyright (C) 2009-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 "cleanup.hpp"
-
-#include "CacheFile.hpp"
-#include "Config.hpp"
-#include "Context.hpp"
-#include "Logging.hpp"
-#include "Statistics.hpp"
-#include "Util.hpp"
-
-#ifdef INODE_CACHE_SUPPORTED
-# include "InodeCache.hpp"
-#endif
-
-#include <algorithm>
-
-static void
-delete_file(const std::string& path,
- uint64_t size,
- uint64_t* cache_size,
- uint64_t* files_in_cache)
-{
- bool deleted = Util::unlink_safe(path, Util::UnlinkLog::ignore_failure);
- if (!deleted && errno != ENOENT && errno != ESTALE) {
- 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
- // aren't. (This can happen when there are several parallel ongoing
- // cleanups of the same directory.)
- *cache_size -= size;
- --*files_in_cache;
- }
-}
-
-static void
-update_counters(const std::string& dir,
- uint64_t files_in_cache,
- uint64_t cache_size,
- bool cleanup_performed)
-{
- const std::string stats_file = dir + "/stats";
- Statistics::update(stats_file, [=](Counters& cs) {
- if (cleanup_performed) {
- cs.increment(Statistic::cleanups_performed);
- }
- cs.set(Statistic::files_in_cache, files_in_cache);
- cs.set(Statistic::cache_size_kibibyte, cache_size / 1024);
- });
-}
-
-void
-clean_old(const Context& ctx,
- const Util::ProgressReceiver& progress_receiver,
- uint64_t max_age)
-{
- Util::for_each_level_1_subdir(
- ctx.config.cache_dir(),
- [&](const std::string& subdir,
- const Util::ProgressReceiver& sub_progress_receiver) {
- clean_up_dir(subdir, 0, 0, max_age, sub_progress_receiver);
- },
- progress_receiver);
-}
-
-// Clean up one cache subdirectory.
-void
-clean_up_dir(const std::string& subdir,
- uint64_t max_size,
- uint64_t max_files,
- uint64_t max_age,
- const Util::ProgressReceiver& progress_receiver)
-{
- LOG("Cleaning up cache directory {}", subdir);
-
- std::vector<CacheFile> files = Util::get_level_1_files(
- subdir, [&](double progress) { progress_receiver(progress / 3); });
-
- uint64_t cache_size = 0;
- uint64_t files_in_cache = 0;
- time_t current_time = time(nullptr);
-
- for (size_t i = 0; i < files.size();
- ++i, progress_receiver(1.0 / 3 + 1.0 * i / files.size() / 3)) {
- const auto& file = files[i];
-
- if (!file.lstat().is_regular()) {
- // Not a file or missing file.
- continue;
- }
-
- // Delete any tmp files older than 1 hour right away.
- if (file.lstat().mtime() + 3600 < current_time
- && Util::base_name(file.path()).find(".tmp.") != std::string::npos) {
- Util::unlink_tmp(file.path());
- continue;
- }
-
- cache_size += file.lstat().size_on_disk();
- files_in_cache += 1;
- }
-
- // Sort according to modification time, oldest first.
- std::sort(
- files.begin(), files.end(), [](const CacheFile& f1, const CacheFile& f2) {
- return f1.lstat().mtime() < f2.lstat().mtime();
- });
-
- LOG("Before cleanup: {:.0f} KiB, {:.0f} files",
- static_cast<double>(cache_size) / 1024,
- static_cast<double>(files_in_cache));
-
- bool cleaned = false;
- for (size_t i = 0; i < files.size();
- ++i, progress_receiver(2.0 / 3 + 1.0 * i / files.size() / 3)) {
- const auto& file = files[i];
-
- if (!file.lstat() || file.lstat().is_directory()) {
- continue;
- }
-
- if ((max_size == 0 || cache_size <= max_size)
- && (max_files == 0 || files_in_cache <= max_files)
- && (max_age == 0
- || file.lstat().mtime()
- > (current_time - static_cast<int64_t>(max_age)))) {
- break;
- }
-
- if (Util::ends_with(file.path(), ".stderr")) {
- // In order to be nice to legacy ccache versions, make sure that the .o
- // file is deleted before .stderr, because if the ccache process gets
- // killed after deleting the .stderr but before deleting the .o, the
- // cached result will be inconsistent. (.stderr is the only file that is
- // optional for legacy ccache versions; any other file missing from the
- // cache will be detected.)
- std::string o_file = file.path().substr(0, file.path().size() - 6) + "o";
-
- // Don't subtract this extra deletion from the cache size; that
- // bookkeeping will be done when the loop reaches the .o file. If the
- // loop doesn't reach the .o file since the target limits have been
- // reached, the bookkeeping won't happen, but that small counter
- // discrepancy won't do much harm and it will correct itself in the next
- // cleanup.
- delete_file(o_file, 0, nullptr, nullptr);
- }
-
- delete_file(
- file.path(), file.lstat().size_on_disk(), &cache_size, &files_in_cache);
- cleaned = true;
- }
-
- 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);
- }
-
- update_counters(subdir, files_in_cache, cache_size, cleaned);
-}
-
-// Clean up all cache subdirectories.
-void
-clean_up_all(const Config& config,
- const Util::ProgressReceiver& progress_receiver)
-{
- Util::for_each_level_1_subdir(
- config.cache_dir(),
- [&](const std::string& subdir,
- const Util::ProgressReceiver& sub_progress_receiver) {
- clean_up_dir(subdir,
- config.max_size() / 16,
- config.max_files() / 16,
- 0,
- sub_progress_receiver);
- },
- progress_receiver);
-}
-
-// Wipe one cache subdirectory.
-static void
-wipe_dir(const std::string& subdir,
- const Util::ProgressReceiver& progress_receiver)
-{
- LOG("Clearing out cache directory {}", subdir);
-
- const std::vector<CacheFile> files = Util::get_level_1_files(
- subdir, [&](double progress) { progress_receiver(progress / 2); });
-
- for (size_t i = 0; i < files.size(); ++i) {
- Util::unlink_safe(files[i].path());
- progress_receiver(0.5 + 0.5 * i / files.size());
- }
-
- const bool cleared = !files.empty();
- if (cleared) {
- LOG("Cleared out cache directory {}", subdir);
- }
- update_counters(subdir, 0, 0, cleared);
-}
-
-// Wipe all cached files in all subdirectories.
-void
-wipe_all(const Context& ctx, const Util::ProgressReceiver& progress_receiver)
-{
- Util::for_each_level_1_subdir(
- ctx.config.cache_dir(), wipe_dir, progress_receiver);
-#ifdef INODE_CACHE_SUPPORTED
- ctx.inode_cache.drop();
-#endif
-}
+++ /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 "system.hpp"
-
-#include "Util.hpp"
-
-#include <string>
-
-class Config;
-class Context;
-
-void clean_old(const Context& ctx,
- const Util::ProgressReceiver& progress_receiver,
- uint64_t max_age);
-
-void clean_up_dir(const std::string& subdir,
- uint64_t max_size,
- uint64_t max_files,
- uint64_t max_age,
- const Util::ProgressReceiver& progress_receiver);
-
-void clean_up_all(const Config& config,
- const Util::ProgressReceiver& progress_receiver);
-
-void wipe_all(const Context& ctx,
- const Util::ProgressReceiver& progress_receiver);
-// Copyright (C) 2010-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
// The option only affects compilation; not passed to the preprocessor.
#define AFFECTS_COMP (1 << 6)
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
struct CompOpt
{
const char* name;
};
const CompOpt compopts[] = {
- {"--Werror", TAKES_ARG}, // nvcc
- {"--analyze", TOO_HARD}, // Clang
- {"--compiler-bindir", AFFECTS_CPP | TAKES_ARG}, // nvcc
- {"--libdevice-directory", AFFECTS_CPP | TAKES_ARG}, // nvcc
- {"--output-directory", AFFECTS_CPP | TAKES_ARG}, // nvcc
+ {"--Werror", TAKES_ARG}, // nvcc
+ {"--analyze", TOO_HARD}, // Clang
+ {"--compiler-bindir", AFFECTS_CPP | TAKES_ARG}, // nvcc
+ {"--config", TAKES_ARG}, // Clang
+ {"--gcc-toolchain=", TAKES_CONCAT_ARG | TAKES_PATH}, // Clang
+ {"--libdevice-directory", AFFECTS_CPP | TAKES_ARG}, // nvcc
+ {"--output-directory", AFFECTS_CPP | TAKES_ARG}, // nvcc
{"--param", TAKES_ARG},
{"--save-temps", TOO_HARD},
{"--save-temps=cwd", TOO_HARD},
{"--serialize-diagnostics", TAKES_ARG | TAKES_PATH},
{"--specs", TAKES_ARG},
{"-A", TAKES_ARG},
+ {"-AI", TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc
{"-B", TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
{"-D", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG},
{"-E", TOO_HARD},
+ {"-EP", TOO_HARD}, // msvc
{"-F", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
+ {"-FI", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc
+ {"-FU", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc
+ {"-Fp", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc
{"-G", TAKES_ARG},
{"-I", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
{"-L", TAKES_ARG},
{"-Xclang", TAKES_ARG},
{"-Xlinker", TAKES_ARG | TAKES_CONCAT_ARG | AFFECTS_COMP},
{"-Xpreprocessor", AFFECTS_CPP | TOO_HARD_DIRECT | TAKES_ARG},
+ {"-Yc", TAKES_ARG | TOO_HARD}, // msvc
+ {"-Yu", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc
+ {"-ZI", TOO_HARD}, // msvc
+ {"-Zi", TOO_HARD}, // msvc
{"-all_load", AFFECTS_COMP},
{"-analyze", TOO_HARD}, // Clang
{"-arch", TAKES_ARG},
{"-ccbin", AFFECTS_CPP | TAKES_ARG}, // nvcc
{"-emit-pch", AFFECTS_COMP}, // Clang
{"-emit-pth", AFFECTS_COMP}, // Clang
+ {"-external:I",
+ AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH}, // msvc
{"-fno-working-directory", AFFECTS_CPP},
{"-fplugin=libcc1plugin", TOO_HARD}, // interaction with GDB
{"-frepo", TOO_HARD},
{"-ftime-trace", TOO_HARD}, // Clang
{"-fworking-directory", AFFECTS_CPP},
+ {"-gcc-toolchain", TAKES_ARG | TAKES_PATH}, // Clang
{"-gen-cdb-fragment-path", TAKES_ARG | TOO_HARD}, // Clang
{"-gtoggle", TOO_HARD},
{"-idirafter", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
{"-iframework", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
{"-imacros", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
+ {"-imsvc", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
{"-imultilib", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
{"-include", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
{"-include-pch", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
{"-iwithprefixbefore",
AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
{"-ldir", AFFECTS_CPP | TAKES_ARG}, // nvcc
+ {"-link", TOO_HARD}, // msvc
{"-nolibc", AFFECTS_COMP},
{"-nostdinc", AFFECTS_CPP},
{"-nostdinc++", AFFECTS_CPP},
{"-stdlib=", AFFECTS_CPP | TAKES_CONCAT_ARG},
{"-trigraphs", AFFECTS_CPP},
{"-u", TAKES_ARG | TAKES_CONCAT_ARG},
+ {"-z", TAKES_ARG | TAKES_CONCAT_ARG | AFFECTS_COMP},
};
static int
-// Copyright (C) 2010-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include <string>
bool compopt_short(bool (*fn)(const std::string& option),
+++ /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
-
-#include "compress.hpp"
-
-#include "AtomicFile.hpp"
-#include "CacheEntryReader.hpp"
-#include "CacheEntryWriter.hpp"
-#include "Context.hpp"
-#include "File.hpp"
-#include "Logging.hpp"
-#include "Manifest.hpp"
-#include "Result.hpp"
-#include "Statistics.hpp"
-#include "StdMakeUnique.hpp"
-#include "ThreadPool.hpp"
-#include "ZstdCompressor.hpp"
-#include "assertions.hpp"
-#include "fmtmacros.hpp"
-
-#include "third_party/fmt/core.h"
-
-#include <string>
-#include <thread>
-
-using nonstd::optional;
-
-namespace {
-
-class RecompressionStatistics
-{
-public:
- void update(uint64_t content_size,
- uint64_t old_size,
- uint64_t new_size,
- uint64_t incompressible_size);
- uint64_t content_size() const;
- uint64_t old_size() const;
- uint64_t new_size() const;
- uint64_t incompressible_size() const;
-
-private:
- mutable std::mutex m_mutex;
- uint64_t m_content_size = 0;
- uint64_t m_old_size = 0;
- uint64_t m_new_size = 0;
- uint64_t m_incompressible_size = 0;
-};
-
-void
-RecompressionStatistics::update(uint64_t content_size,
- uint64_t old_size,
- uint64_t new_size,
- uint64_t incompressible_size)
-{
- std::unique_lock<std::mutex> lock(m_mutex);
- m_incompressible_size += incompressible_size;
- m_content_size += content_size;
- m_old_size += old_size;
- m_new_size += new_size;
-}
-
-uint64_t
-RecompressionStatistics::content_size() const
-{
- std::unique_lock<std::mutex> lock(m_mutex);
- return m_content_size;
-}
-
-uint64_t
-RecompressionStatistics::old_size() const
-{
- std::unique_lock<std::mutex> lock(m_mutex);
- return m_old_size;
-}
-
-uint64_t
-RecompressionStatistics::new_size() const
-{
- std::unique_lock<std::mutex> lock(m_mutex);
- return m_new_size;
-}
-
-uint64_t
-RecompressionStatistics::incompressible_size() const
-{
- std::unique_lock<std::mutex> lock(m_mutex);
- return m_incompressible_size;
-}
-
-File
-open_file(const std::string& path, const char* mode)
-{
- File f(path, mode);
- if (!f) {
- throw Error("failed to open {} for reading: {}", path, strerror(errno));
- }
- return f;
-}
-
-std::unique_ptr<CacheEntryReader>
-create_reader(const CacheFile& cache_file, FILE* stream)
-{
- if (cache_file.type() == CacheFile::Type::unknown) {
- throw Error("unknown file type for {}", cache_file.path());
- }
-
- switch (cache_file.type()) {
- case CacheFile::Type::result:
- return std::make_unique<CacheEntryReader>(
- stream, Result::k_magic, Result::k_version);
-
- case CacheFile::Type::manifest:
- return std::make_unique<CacheEntryReader>(
- stream, Manifest::k_magic, Manifest::k_version);
-
- case CacheFile::Type::unknown:
- ASSERT(false); // Handled at function entry.
- }
-
- ASSERT(false);
-}
-
-std::unique_ptr<CacheEntryWriter>
-create_writer(FILE* stream,
- const CacheEntryReader& reader,
- Compression::Type compression_type,
- int8_t compression_level)
-{
- return std::make_unique<CacheEntryWriter>(stream,
- reader.magic(),
- reader.version(),
- compression_type,
- compression_level,
- reader.payload_size());
-}
-
-void
-recompress_file(RecompressionStatistics& statistics,
- const std::string& stats_file,
- const CacheFile& cache_file,
- optional<int8_t> level)
-{
- auto file = open_file(cache_file.path(), "rb");
- auto reader = create_reader(cache_file, file.get());
-
- auto old_stat = Stat::stat(cache_file.path(), Stat::OnError::log);
- uint64_t content_size = reader->content_size();
- int8_t wanted_level =
- level ? (*level == 0 ? ZstdCompressor::default_compression_level : *level)
- : 0;
-
- if (reader->compression_level() == wanted_level) {
- statistics.update(content_size, old_stat.size(), old_stat.size(), 0);
- return;
- }
-
- LOG("Recompressing {} to {}",
- cache_file.path(),
- level ? FMT("level {}", wanted_level) : "uncompressed");
- AtomicFile atomic_new_file(cache_file.path(), AtomicFile::Mode::binary);
- auto writer =
- create_writer(atomic_new_file.stream(),
- *reader,
- level ? Compression::Type::zstd : Compression::Type::none,
- wanted_level);
-
- char buffer[READ_BUFFER_SIZE];
- size_t bytes_left = reader->payload_size();
- while (bytes_left > 0) {
- size_t bytes_to_read = std::min(bytes_left, sizeof(buffer));
- reader->read(buffer, bytes_to_read);
- writer->write(buffer, bytes_to_read);
- bytes_left -= bytes_to_read;
- }
- reader->finalize();
- writer->finalize();
-
- file.close();
-
- atomic_new_file.commit();
- auto new_stat = Stat::stat(cache_file.path(), Stat::OnError::log);
-
- Statistics::update(stats_file, [=](Counters& cs) {
- cs.increment(Statistic::cache_size_kibibyte,
- Util::size_change_kibibyte(old_stat, new_stat));
- });
-
- statistics.update(content_size, old_stat.size(), new_stat.size(), 0);
-
- LOG("Recompression of {} done", cache_file.path());
-}
-
-} // namespace
-
-void
-compress_stats(const Config& config,
- const Util::ProgressReceiver& progress_receiver)
-{
- uint64_t on_disk_size = 0;
- uint64_t compr_size = 0;
- uint64_t content_size = 0;
- uint64_t incompr_size = 0;
-
- Util::for_each_level_1_subdir(
- config.cache_dir(),
- [&](const std::string& subdir,
- const Util::ProgressReceiver& sub_progress_receiver) {
- const std::vector<CacheFile> files = Util::get_level_1_files(
- subdir, [&](double progress) { sub_progress_receiver(progress / 2); });
-
- for (size_t i = 0; i < files.size(); ++i) {
- const auto& cache_file = files[i];
- on_disk_size += cache_file.lstat().size_on_disk();
-
- try {
- auto file = open_file(cache_file.path(), "rb");
- auto reader = create_reader(cache_file, file.get());
- compr_size += cache_file.lstat().size();
- content_size += reader->content_size();
- } catch (Error&) {
- incompr_size += cache_file.lstat().size();
- }
-
- sub_progress_receiver(1.0 / 2 + 1.0 * i / files.size() / 2);
- }
- },
- progress_receiver);
-
- if (isatty(STDOUT_FILENO)) {
- PRINT_RAW(stdout, "\n\n");
- }
-
- double ratio =
- compr_size > 0 ? static_cast<double>(content_size) / compr_size : 0.0;
- double savings = ratio > 0.0 ? 100.0 - (100.0 / ratio) : 0.0;
-
- std::string on_disk_size_str = Util::format_human_readable_size(on_disk_size);
- std::string cache_size_str =
- Util::format_human_readable_size(compr_size + incompr_size);
- std::string compr_size_str = Util::format_human_readable_size(compr_size);
- std::string content_size_str = Util::format_human_readable_size(content_size);
- std::string incompr_size_str = Util::format_human_readable_size(incompr_size);
-
- 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
-compress_recompress(Context& ctx,
- optional<int8_t> level,
- const Util::ProgressReceiver& progress_receiver)
-{
- const size_t threads = std::thread::hardware_concurrency();
- const size_t read_ahead = 2 * threads;
- ThreadPool thread_pool(threads, read_ahead);
- RecompressionStatistics statistics;
-
- Util::for_each_level_1_subdir(
- ctx.config.cache_dir(),
- [&](const std::string& subdir,
- const Util::ProgressReceiver& sub_progress_receiver) {
- std::vector<CacheFile> files =
- Util::get_level_1_files(subdir, [&](double progress) {
- sub_progress_receiver(0.1 * progress);
- });
-
- auto stats_file = subdir + "/stats";
-
- for (size_t i = 0; i < files.size(); ++i) {
- const auto& file = files[i];
-
- if (file.type() != CacheFile::Type::unknown) {
- thread_pool.enqueue([&statistics, stats_file, file, level] {
- try {
- recompress_file(statistics, stats_file, file, level);
- } catch (Error&) {
- // Ignore for now.
- }
- });
- } else {
- statistics.update(0, 0, 0, file.lstat().size());
- }
-
- sub_progress_receiver(0.1 + 0.9 * i / files.size());
- }
-
- if (Util::ends_with(subdir, "f")) {
- // Wait here instead of after Util::for_each_level_1_subdir to avoid
- // updating the progress bar to 100% before all work is done.
- thread_pool.shut_down();
- }
- },
- progress_receiver);
-
- if (isatty(STDOUT_FILENO)) {
- PRINT_RAW(stdout, "\n\n");
- }
-
- double old_ratio =
- statistics.old_size() > 0
- ? static_cast<double>(statistics.content_size()) / statistics.old_size()
- : 0.0;
- double old_savings = old_ratio > 0.0 ? 100.0 - (100.0 / old_ratio) : 0.0;
- double new_ratio =
- statistics.new_size() > 0
- ? static_cast<double>(statistics.content_size()) / statistics.new_size()
- : 0.0;
- double new_savings = new_ratio > 0.0 ? 100.0 - (100.0 / new_ratio) : 0.0;
- int64_t size_difference = static_cast<int64_t>(statistics.new_size())
- - static_cast<int64_t>(statistics.old_size());
-
- std::string old_compr_size_str =
- Util::format_human_readable_size(statistics.old_size());
- std::string new_compr_size_str =
- Util::format_human_readable_size(statistics.new_size());
- std::string content_size_str =
- Util::format_human_readable_size(statistics.content_size());
- std::string incompr_size_str =
- Util::format_human_readable_size(statistics.incompressible_size());
- std::string 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);
-}
+++ /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 "system.hpp"
-
-#include "Util.hpp"
-
-#include "third_party/nonstd/optional.hpp"
-
-class Config;
-class Context;
-
-void compress_stats(const Config& config,
- const Util::ProgressReceiver& progress_receiver);
-
-// Recompress the cache.
-//
-// Arguments:
-// - ctx: The context.
-// - level: Target compression level (positive or negative value for actual
-// level, 0 for default level and nonstd::nullopt for no compression).
-// - progress_receiver: Function that will be called for progress updates.
-void compress_recompress(Context& ctx,
- nonstd::optional<int8_t> level,
- const Util::ProgressReceiver& progress_receiver);
--- /dev/null
+set(
+ sources
+ CacheEntry.cpp
+ Manifest.cpp
+ MsvcShowIncludesOutput.cpp
+ Result.cpp
+ ResultExtractor.cpp
+ ResultInspector.cpp
+ ResultRetriever.cpp
+ Statistics.cpp
+ StatisticsCounters.cpp
+ StatsLog.cpp
+ mainoptions.cpp
+ types.cpp
+)
+
+file(GLOB headers *.hpp)
+list(APPEND sources ${headers})
+
+target_sources(ccache_framework PRIVATE ${sources})
--- /dev/null
+// Copyright (C) 2022 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 "CacheEntry.hpp"
+
+#include <Logging.hpp>
+#include <ccache.hpp>
+#include <core/CacheEntryDataReader.hpp>
+#include <core/CacheEntryDataWriter.hpp>
+#include <core/Result.hpp>
+#include <core/exceptions.hpp>
+#include <core/types.hpp>
+#include <fmtmacros.hpp>
+#include <util/TimePoint.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+#include <util/zstd.hpp>
+
+#include <cstring>
+
+namespace {
+
+const size_t k_static_header_fields_size =
+ sizeof(core::CacheEntry::Header::magic)
+ + sizeof(core::CacheEntry::Header::entry_format_version)
+ + sizeof(core::CacheEntry::Header::entry_type)
+ + sizeof(core::CacheEntry::Header::compression_type)
+ + sizeof(core::CacheEntry::Header::compression_level)
+ + sizeof(core::CacheEntry::Header::self_contained)
+ + sizeof(core::CacheEntry::Header::creation_time)
+ + sizeof(core::CacheEntry::Header::entry_size)
+ // ccache_version length field:
+ + 1
+ // namespace_ length field:
+ + 1;
+
+const size_t k_epilogue_fields_size = sizeof(uint64_t) + sizeof(uint64_t);
+
+core::CacheEntryType
+cache_entry_type_from_int(const uint8_t entry_type)
+{
+ switch (entry_type) {
+ case 0:
+ return core::CacheEntryType::result;
+ break;
+ case 1:
+ return core::CacheEntryType::manifest;
+ break;
+ default:
+ throw core::Error(FMT("Unknown entry type: {}", entry_type));
+ }
+}
+
+} // namespace
+
+namespace core {
+
+// Version 0:
+// - First version.
+// Version 1:
+// - Added self_contained field.
+// - The checksum is now for the (potentially) compressed payload instead of
+// the uncompressed payload, and the checksum is now always stored
+// uncompressed.
+const uint8_t CacheEntry::k_format_version = 1;
+
+CacheEntry::Header::Header(const Config& config,
+ core::CacheEntryType entry_type)
+ : magic(k_ccache_magic),
+ entry_format_version(k_format_version),
+ entry_type(entry_type),
+ compression_type(compression_type_from_config(config)),
+ compression_level(compression_level_from_config(config)),
+ self_contained(entry_type != CacheEntryType::result
+ || !core::Result::Serializer::use_raw_files(config)),
+ creation_time(util::TimePoint::now().sec()),
+ ccache_version(CCACHE_VERSION),
+ namespace_(config.namespace_()),
+ entry_size(0)
+{
+ if (compression_level == 0) {
+ compression_level = default_compression_level;
+ LOG("Using default compression level {}", compression_level);
+ }
+}
+
+CacheEntry::Header::Header(nonstd::span<const uint8_t> data)
+{
+ parse(data);
+}
+
+CacheEntry::Header::Header(const std::string& path)
+{
+ const auto data = util::read_file_part<util::Bytes>(path, 0, 1000);
+ if (!data) {
+ throw core::Error(data.error());
+ }
+ parse(*data);
+}
+
+std::string
+CacheEntry::Header::inspect() const
+{
+ std::string result;
+ result += FMT("Magic: {:04x}\n", magic);
+ result += FMT("Entry format version: {}\n", entry_format_version);
+ result += FMT("Entry type: {} ({})\n",
+ static_cast<uint8_t>(entry_type),
+ to_string(entry_type));
+ result += FMT("Compression type: {}\n", to_string(compression_type));
+ result += FMT("Compression level: {}\n", compression_level);
+ result += FMT("Self-contained: {}\n", self_contained ? "yes" : "no");
+ result += FMT("Creation time: {}\n", creation_time);
+ result += FMT("Ccache version: {}\n", ccache_version);
+ result += FMT("Namespace: {}\n", namespace_);
+ result += FMT("Entry size: {}\n", entry_size);
+ return result;
+}
+
+void
+CacheEntry::Header::parse(nonstd::span<const uint8_t> data)
+{
+ CacheEntryDataReader reader(data);
+ reader.read_int(magic);
+ if (magic != core::k_ccache_magic) {
+ throw core::Error(FMT("Bad magic value: 0x{:04x}", magic));
+ }
+
+ reader.read_int(entry_format_version);
+ if (entry_format_version != k_format_version) {
+ throw core::Error(
+ FMT("Unknown entry format version: {}", entry_format_version));
+ }
+
+ entry_type = cache_entry_type_from_int(reader.read_int<uint8_t>());
+ compression_type = compression_type_from_int(reader.read_int<uint8_t>());
+ reader.read_int(compression_level);
+ self_contained = bool(reader.read_int<uint8_t>());
+ reader.read_int(creation_time);
+ ccache_version = reader.read_str(reader.read_int<uint8_t>());
+ namespace_ = reader.read_str(reader.read_int<uint8_t>());
+ reader.read_int(entry_size);
+}
+
+size_t
+CacheEntry::Header::serialized_size() const
+{
+ return k_static_header_fields_size + ccache_version.length()
+ + namespace_.length();
+}
+
+void
+CacheEntry::Header::serialize(util::Bytes& output) const
+{
+ core::CacheEntryDataWriter writer(output);
+ writer.write_int(magic);
+ writer.write_int(entry_format_version);
+ writer.write_int(static_cast<uint8_t>(entry_type));
+ writer.write_int(static_cast<uint8_t>(compression_type));
+ writer.write_int(compression_level);
+ writer.write_int<uint8_t>(self_contained);
+ writer.write_int(creation_time);
+ writer.write_int<uint8_t>(ccache_version.length());
+ writer.write_str(ccache_version);
+ writer.write_int<uint8_t>(namespace_.length());
+ writer.write_str(namespace_);
+ writer.write_int(entry_size);
+}
+
+uint32_t
+CacheEntry::Header::uncompressed_payload_size() const
+{
+ return entry_size - serialized_size() - k_epilogue_fields_size;
+}
+
+CacheEntry::CacheEntry(nonstd::span<const uint8_t> data) : m_header(data)
+{
+ const size_t non_payload_size =
+ m_header.serialized_size() + k_epilogue_fields_size;
+ if (data.size() <= non_payload_size) {
+ throw core::Error("CacheEntry data underflow");
+ }
+ m_payload =
+ data.subspan(m_header.serialized_size(), data.size() - non_payload_size);
+ m_checksum = data.last(k_epilogue_fields_size);
+
+ switch (m_header.compression_type) {
+ case CompressionType::none:
+ break;
+
+ case CompressionType::zstd:
+ m_uncompressed_payload.reserve(m_header.uncompressed_payload_size());
+ util::throw_on_error<core::Error>(
+ util::zstd_decompress(
+ m_payload, m_uncompressed_payload, m_uncompressed_payload.capacity()),
+ "Cache entry payload decompression error: ");
+
+ break;
+ }
+}
+
+void
+CacheEntry::verify_checksum() const
+{
+ util::Bytes header_data;
+ m_header.serialize(header_data);
+
+ util::XXH3_128 checksum;
+ checksum.update(header_data);
+ checksum.update(m_payload);
+ const auto actual = checksum.digest();
+
+ if (actual != m_checksum) {
+ throw core::Error(
+ FMT("Incorrect checksum (actual {}, expected {})",
+ Util::format_base16(actual.data(), actual.size()),
+ Util::format_base16(m_checksum.data(), m_checksum.size())));
+ }
+}
+
+const CacheEntry::Header&
+CacheEntry::header() const
+{
+ return m_header;
+}
+
+nonstd::span<const uint8_t>
+CacheEntry::payload() const
+{
+ return m_header.compression_type == CompressionType::none
+ ? m_payload
+ : nonstd::span<const uint8_t>(m_uncompressed_payload);
+}
+
+util::Bytes
+CacheEntry::serialize(const CacheEntry::Header& header,
+ Serializer& payload_serializer)
+{
+ return do_serialize(
+ header,
+ payload_serializer.serialized_size(),
+ [&payload_serializer](util::Bytes& result, const CacheEntry::Header& hdr) {
+ switch (hdr.compression_type) {
+ case CompressionType::none:
+ payload_serializer.serialize(result);
+ break;
+
+ case CompressionType::zstd:
+ util::Bytes payload;
+ payload_serializer.serialize(payload);
+ util::throw_on_error<core::Error>(
+ util::zstd_compress(payload, result, hdr.compression_level),
+ "Cache entry payload compression error: ");
+ break;
+ }
+ });
+}
+
+util::Bytes
+CacheEntry::serialize(const CacheEntry::Header& header,
+ nonstd::span<const uint8_t> payload)
+{
+ return do_serialize(
+ header,
+ payload.size(),
+ [&payload](util::Bytes& result, const CacheEntry::Header& hdr) {
+ switch (hdr.compression_type) {
+ case CompressionType::none:
+ result.insert(result.end(), payload.begin(), payload.end());
+ break;
+
+ case CompressionType::zstd:
+ util::throw_on_error<core::Error>(
+ util::zstd_compress(payload, result, hdr.compression_level),
+ "Cache entry payload compression error: ");
+ break;
+ }
+ });
+}
+
+util::Bytes
+CacheEntry::do_serialize(
+ const CacheEntry::Header& header,
+ size_t serialized_payload_size,
+ std::function<void(util::Bytes& result, const Header& hdr)> serialize_payload)
+{
+ CacheEntry::Header hdr(header);
+ const size_t non_payload_size =
+ hdr.serialized_size() + k_epilogue_fields_size;
+ hdr.entry_size = non_payload_size + serialized_payload_size;
+
+ if (hdr.compression_type == CompressionType::zstd) {
+ const auto [level, explanation] =
+ util::zstd_supported_compression_level(hdr.compression_level);
+ if (!explanation.empty()) {
+ LOG("Using ZSTD compression level {} ({}) instead of {}",
+ level,
+ explanation,
+ hdr.compression_level);
+ }
+ hdr.compression_level = level;
+ }
+
+ const size_t max_serialized_size =
+ hdr.compression_type == CompressionType::zstd
+ ? (non_payload_size + util::zstd_compress_bound(serialized_payload_size))
+ : hdr.entry_size;
+ util::Bytes result;
+ result.reserve(max_serialized_size);
+
+ hdr.serialize(result);
+ serialize_payload(result, hdr);
+
+ util::XXH3_128 checksum;
+ checksum.update(result);
+ const auto digest = checksum.digest();
+ result.insert(result.end(), digest.begin(), digest.end());
+
+ return result;
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021-2022 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 <core/Serializer.hpp>
+#include <core/types.hpp>
+#include <util/Bytes.hpp>
+#include <util/XXH3_128.hpp>
+
+#include <third_party/nonstd/span.hpp>
+
+#include <cstdint>
+#include <functional>
+#include <string>
+
+// Cache entry format
+// ==================
+//
+// Integers are big-endian.
+//
+// <entry> ::= <header> <payload> <epilogue>
+// <header> ::= <magic> <format_ver> <entry_type> <compr_type>
+// <compr_level> <creation_time> <ccache_ver> <namespace>
+// <entry_size>
+// <magic> ::= uint16_t (0xccac)
+// <format_ver> ::= uint8_t
+// <entry_type> ::= <result_entry> | <manifest_entry>
+// <result_entry> ::= 0 (uint8_t)
+// <manifest_entry> ::= 1 (uint8_t)
+// <self_contained> ::= 0/1 (uint8_t) ; whether suitable for remote storage
+// <compr_type> ::= <compr_none> | <compr_zstd>
+// <compr_none> ::= 0 (uint8_t)
+// <compr_zstd> ::= 1 (uint8_t)
+// <compr_level> ::= int8_t
+// <creation_time> ::= uint64_t (Unix epoch time when entry was created)
+// <ccache_ver> ::= string length (uint8_t) + string data
+// <namespace> ::= string length (uint8_t) + string data
+// <entry_size> ::= uint64_t ; = size of entry in uncompressed form
+// <payload> ::= depends on entry_type; potentially compressed
+// <epilogue> ::= <checksum_high> <checksum_low>
+// <checksum_high> ::= uint64_t ; XXH3-128 (high bits) of <header>+<payload>
+// <checksum_low> ::= uint64_t ; XXH3-128 (low bits) of <header>+<payload>
+
+class Config;
+
+namespace core {
+
+const uint16_t k_ccache_magic = 0xccac;
+
+class CacheEntry
+{
+public:
+ static const uint8_t k_format_version;
+ constexpr static uint8_t default_compression_level = 1;
+
+ class Header
+ {
+ public:
+ Header(const Config& config, CacheEntryType entry_type);
+ explicit Header(nonstd::span<const uint8_t> data);
+ explicit Header(const std::string& path);
+
+ std::string inspect() const;
+
+ uint16_t magic;
+ uint8_t entry_format_version;
+ CacheEntryType entry_type;
+ CompressionType compression_type;
+ int8_t compression_level;
+ bool self_contained;
+ uint64_t creation_time;
+ std::string ccache_version;
+ std::string namespace_;
+ uint64_t entry_size;
+
+ size_t serialized_size() const;
+ void serialize(util::Bytes& output) const;
+ uint32_t uncompressed_payload_size() const;
+
+ private:
+ void parse(nonstd::span<const uint8_t> data);
+ };
+
+ explicit CacheEntry(nonstd::span<const uint8_t> data);
+
+ void verify_checksum() const;
+ const Header& header() const;
+
+ // Return uncompressed payload.
+ nonstd::span<const uint8_t> payload() const;
+
+ static util::Bytes serialize(const Header& header,
+ Serializer& payload_serializer);
+ static util::Bytes serialize(const Header& header,
+ nonstd::span<const uint8_t> payload);
+
+private:
+ Header m_header;
+ nonstd::span<const uint8_t> m_payload; // Potentially compressed
+ util::Bytes m_checksum;
+
+ mutable util::Bytes m_uncompressed_payload;
+
+ static util::Bytes
+ do_serialize(const Header& header,
+ size_t serialized_payload_size,
+ std::function<void(util::Bytes& result, const Header& header)>
+ serialize_payload);
+};
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2022 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 <Util.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <util/string.hpp>
+
+#include <third_party/nonstd/span.hpp>
+
+#include <cstddef>
+#include <string_view>
+
+namespace core {
+
+class CacheEntryDataReader
+{
+public:
+ CacheEntryDataReader(nonstd::span<const uint8_t> data);
+
+ // Read `size` bytes. Throws `core::Error` on failure.
+ nonstd::span<const uint8_t> read_bytes(size_t size);
+
+ // Read and copy `buffer.size()` bytes into `buffer`. Throws `core::Error` on
+ // failure.
+ void read_and_copy_bytes(nonstd::span<uint8_t> buffer);
+
+ // Read a string of length `length`. Throws `core::Error` on failure.
+ std::string_view read_str(size_t length);
+
+ // Read an integer. Throws `core::Error` on failure.
+ template<typename T> T read_int();
+
+ // Read an integer into `value`. Throws `core::Error` on failure.
+ template<typename T> void read_int(T& value);
+
+private:
+ nonstd::span<const uint8_t> m_data;
+};
+
+inline CacheEntryDataReader::CacheEntryDataReader(
+ nonstd::span<const uint8_t> data)
+ : m_data(data)
+{
+}
+
+inline nonstd::span<const uint8_t>
+CacheEntryDataReader::read_bytes(size_t size)
+{
+ if (size > m_data.size()) {
+ throw core::Error(FMT("CacheEntryDataReader: data underflow of {} bytes",
+ size - m_data.size()));
+ }
+ const auto bytes = m_data.first(size);
+ m_data = m_data.subspan(size);
+ return bytes;
+}
+
+inline void
+CacheEntryDataReader::read_and_copy_bytes(nonstd::span<uint8_t> buffer)
+{
+ const auto span = read_bytes(buffer.size());
+ memcpy(buffer.data(), span.data(), span.size());
+}
+
+inline std::string_view
+CacheEntryDataReader::read_str(const size_t length)
+{
+ return util::to_string_view(read_bytes(length));
+}
+
+template<typename T>
+inline T
+CacheEntryDataReader::read_int()
+{
+ const auto buffer = read_bytes(sizeof(T));
+ T value;
+ Util::big_endian_to_int(buffer.data(), value);
+ return value;
+}
+
+template<typename T>
+inline void
+CacheEntryDataReader::read_int(T& value)
+{
+ value = read_int<T>();
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2022 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 <Util.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <util/string.hpp>
+
+#include <third_party/nonstd/span.hpp>
+
+#include <cstddef>
+#include <cstring>
+#include <string_view>
+
+namespace core {
+
+class CacheEntryDataWriter
+{
+public:
+ CacheEntryDataWriter(util::Bytes& output);
+
+ // Write `data`. Throws `core::Error` on failure.
+ void write_bytes(nonstd::span<const uint8_t> data);
+
+ // Write `data`. Throws `core::Error` on failure.
+ void write_str(std::string_view data);
+
+ // Write integer `value`. Throws `core::Error` on failure.
+ template<typename T> void write_int(T value);
+
+private:
+ util::Bytes& m_output;
+};
+
+inline CacheEntryDataWriter::CacheEntryDataWriter(util::Bytes& output)
+ : m_output(output)
+{
+}
+
+inline void
+CacheEntryDataWriter::write_bytes(nonstd::span<const uint8_t> data)
+{
+ m_output.insert(m_output.end(), data.begin(), data.end());
+}
+
+template<typename T>
+inline void
+CacheEntryDataWriter::write_int(const T value)
+{
+ uint8_t buffer[sizeof(T)];
+ Util::int_to_big_endian(value, buffer);
+ write_bytes(buffer);
+}
+
+inline void
+CacheEntryDataWriter::write_str(std::string_view value)
+{
+ write_bytes(util::to_span(value));
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2009-2022 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 "Manifest.hpp"
+
+#include <Context.hpp>
+#include <Hash.hpp>
+#include <Logging.hpp>
+#include <core/CacheEntryDataReader.hpp>
+#include <core/CacheEntryDataWriter.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <hashutil.hpp>
+#include <util/XXH3_64.hpp>
+
+// Manifest data format
+// ====================
+//
+// Integers are big-endian.
+//
+// <payload> ::= <format_ver> <paths> <includes> <results>
+// <format_ver> ::= uint8_t
+// <paths> ::= <n_paths> <path_entry>*
+// <n_paths> ::= uint32_t
+// <path_entry> ::= <path_len> <path>
+// <path_len> ::= uint16_t
+// <path> ::= path_len bytes
+// <includes> ::= <n_includes> <include_entry>*
+// <n_includes> ::= uint32_t
+// <include_entry> ::= <path_index> <digest> <fsize> <mtime> <ctime>
+// <path_index> ::= uint32_t
+// <digest> ::= Digest::size() bytes
+// <fsize> ::= uint64_t ; file size
+// <mtime> ::= int64_t ; modification time (ns), 0 = not recorded
+// <ctime> ::= int64_t ; status change time (ns), 0 = not recorded
+// <results> ::= <n_results> <result>*
+// <n_results> ::= uint32_t
+// <result> ::= <n_indexes> <include_index>* <key>
+// <n_indexes> ::= uint32_t
+// <include_index> ::= uint32_t
+// <result_key> ::= Digest::size() bytes
+
+const uint32_t k_max_manifest_entries = 100;
+const uint32_t k_max_manifest_file_info_entries = 10000;
+
+namespace std {
+
+template<> struct hash<core::Manifest::FileInfo>
+{
+ size_t
+ operator()(const core::Manifest::FileInfo& file_info) const
+ {
+ static_assert(sizeof(file_info) == 48); // No padding.
+ util::XXH3_64 hash;
+ hash.update(&file_info, sizeof(file_info));
+ return hash.digest();
+ }
+};
+
+} // namespace std
+
+namespace core {
+
+// Format version history:
+//
+// Version 0:
+// - First version.
+// Version 1:
+// - mtime and ctime are now stored with nanoseconds resolution.
+const uint8_t Manifest::k_format_version = 1;
+
+void
+Manifest::read(nonstd::span<const uint8_t> data)
+{
+ std::vector<std::string> files;
+ std::vector<FileInfo> file_infos;
+ std::vector<ResultEntry> results;
+
+ core::CacheEntryDataReader reader(data);
+
+ const auto format_version = reader.read_int<uint8_t>();
+ if (format_version != k_format_version) {
+ throw core::Error(FMT("Unknown manifest format version: {} != {}",
+ format_version,
+ k_format_version));
+ }
+
+ const auto file_count = reader.read_int<uint32_t>();
+ for (uint32_t i = 0; i < file_count; ++i) {
+ files.emplace_back(reader.read_str(reader.read_int<uint16_t>()));
+ }
+
+ const auto file_info_count = reader.read_int<uint32_t>();
+ for (uint32_t i = 0; i < file_info_count; ++i) {
+ file_infos.emplace_back();
+ auto& entry = file_infos.back();
+
+ reader.read_int(entry.index);
+ reader.read_and_copy_bytes({entry.digest.bytes(), Digest::size()});
+ reader.read_int(entry.fsize);
+ entry.mtime.set_nsec(reader.read_int<int64_t>());
+ entry.ctime.set_nsec(reader.read_int<int64_t>());
+ }
+
+ const auto result_count = reader.read_int<uint32_t>();
+ for (uint32_t i = 0; i < result_count; ++i) {
+ results.emplace_back();
+ auto& entry = results.back();
+
+ const auto file_info_index_count = reader.read_int<uint32_t>();
+ for (uint32_t j = 0; j < file_info_index_count; ++j) {
+ entry.file_info_indexes.push_back(reader.read_int<uint32_t>());
+ }
+ reader.read_and_copy_bytes({entry.key.bytes(), Digest::size()});
+ }
+
+ if (m_results.empty()) {
+ m_files = std::move(files);
+ m_file_infos = std::move(file_infos);
+ m_results = std::move(results);
+ } else {
+ for (const auto& result : results) {
+ std::unordered_map<std::string, Digest> included_files;
+ std::unordered_map<std::string, FileStats> included_files_stats;
+ for (auto file_info_index : result.file_info_indexes) {
+ const auto& file_info = file_infos[file_info_index];
+ included_files.emplace(files[file_info.index], file_info.digest);
+ included_files_stats.emplace(
+ files[file_info.index],
+ FileStats{file_info.fsize, file_info.mtime, file_info.ctime});
+ }
+ add_result(result.key, included_files, [&](const std::string& path) {
+ return included_files_stats[path];
+ });
+ }
+ }
+}
+
+std::optional<Digest>
+Manifest::look_up_result_digest(const Context& ctx) const
+{
+ std::unordered_map<std::string, FileStats> stated_files;
+ std::unordered_map<std::string, Digest> hashed_files;
+
+ // Check newest result first since it's a more likely to match.
+ for (size_t i = m_results.size(); i > 0; i--) {
+ const auto& result = m_results[i - 1];
+ if (result_matches(ctx, result, stated_files, hashed_files)) {
+ return result.key;
+ }
+ }
+
+ return std::nullopt;
+}
+
+bool
+Manifest::add_result(
+ const Digest& result_key,
+ const std::unordered_map<std::string, Digest>& included_files,
+ const FileStater& stat_file_function)
+{
+ if (m_results.size() > k_max_manifest_entries) {
+ // Normally, there shouldn't be many result entries in the manifest since
+ // new entries are added only if an include file has changed but not the
+ // source file, and you typically change source files more often than header
+ // files. However, it's certainly possible to imagine cases where the
+ // manifest will grow large (for instance, a generated header file that
+ // changes for every build), and this must be taken care of since processing
+ // an ever growing manifest eventually will take too much time. 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",
+ k_max_manifest_entries);
+ clear();
+ } else if (m_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",
+ k_max_manifest_file_info_entries);
+ clear();
+ }
+
+ std::unordered_map<std::string, uint32_t /*index*/> mf_files;
+ for (uint32_t i = 0; i < m_files.size(); ++i) {
+ mf_files.emplace(m_files[i], i);
+ }
+
+ std::unordered_map<FileInfo, uint32_t /*index*/> mf_file_infos;
+ for (uint32_t i = 0; i < m_file_infos.size(); ++i) {
+ mf_file_infos.emplace(m_file_infos[i], i);
+ }
+
+ std::vector<uint32_t> file_info_indexes;
+ file_info_indexes.reserve(included_files.size());
+
+ for (const auto& [path, digest] : included_files) {
+ file_info_indexes.push_back(get_file_info_index(
+ path, digest, mf_files, mf_file_infos, stat_file_function));
+ }
+
+ ResultEntry entry{std::move(file_info_indexes), result_key};
+ if (std::find(m_results.begin(), m_results.end(), entry) == m_results.end()) {
+ m_results.push_back(std::move(entry));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+uint32_t
+Manifest::serialized_size() const
+{
+ uint64_t size = 0;
+
+ size += 1; // format_ver
+ size += 4; // n_files
+ for (const auto& file : m_files) {
+ size += 2 + file.length();
+ }
+ size += 4; // n_file_infos
+ size += m_file_infos.size() * (4 + Digest::size() + 8 + 8 + 8);
+ size += 4; // n_results
+ for (const auto& result : m_results) {
+ size += 4; // n_file_info_indexes
+ size += result.file_info_indexes.size() * 4;
+ size += Digest::size();
+ }
+
+ // In order to support 32-bit ccache builds, restrict size to uint32_t for
+ // now. This restriction can be lifted when we drop 32-bit support.
+ const auto max = std::numeric_limits<uint32_t>::max();
+ if (size > max) {
+ throw core::Error(
+ FMT("Serialized manifest too large ({} > {})", size, max));
+ }
+ return size;
+}
+
+void
+Manifest::serialize(util::Bytes& output)
+{
+ core::CacheEntryDataWriter writer(output);
+
+ writer.write_int(k_format_version);
+ writer.write_int<uint32_t>(m_files.size());
+ for (const auto& file : m_files) {
+ writer.write_int<uint16_t>(file.length());
+ writer.write_str(file);
+ }
+
+ writer.write_int<uint32_t>(m_file_infos.size());
+ for (const auto& file_info : m_file_infos) {
+ writer.write_int<uint32_t>(file_info.index);
+ writer.write_bytes({file_info.digest.bytes(), Digest::size()});
+ writer.write_int(file_info.fsize);
+ writer.write_int(file_info.mtime.nsec());
+ writer.write_int(file_info.ctime.nsec());
+ }
+
+ writer.write_int<uint32_t>(m_results.size());
+ for (const auto& result : m_results) {
+ writer.write_int<uint32_t>(result.file_info_indexes.size());
+ for (auto index : result.file_info_indexes) {
+ writer.write_int(index);
+ }
+ writer.write_bytes({result.key.bytes(), Digest::size()});
+ }
+}
+
+bool
+Manifest::FileInfo::operator==(const FileInfo& other) const
+{
+ return index == other.index && digest == other.digest && fsize == other.fsize
+ && mtime == other.mtime && ctime == other.ctime;
+}
+
+bool
+Manifest::ResultEntry::operator==(const ResultEntry& other) const
+{
+ return file_info_indexes == other.file_info_indexes && key == other.key;
+}
+
+void
+Manifest::clear()
+{
+ m_files.clear();
+ m_file_infos.clear();
+ m_results.clear();
+}
+
+uint32_t
+Manifest::get_file_info_index(
+ const std::string& path,
+ const Digest& digest,
+ const std::unordered_map<std::string, uint32_t>& mf_files,
+ const std::unordered_map<FileInfo, uint32_t>& mf_file_infos,
+ const FileStater& file_stater)
+{
+ FileInfo fi;
+
+ const auto f_it = mf_files.find(path);
+ if (f_it != mf_files.end()) {
+ fi.index = f_it->second;
+ } else {
+ m_files.push_back(path);
+ fi.index = m_files.size() - 1;
+ }
+
+ fi.digest = digest;
+
+ const auto file_stat = file_stater(path);
+ fi.mtime = file_stat.mtime;
+ fi.ctime = file_stat.ctime;
+ fi.fsize = file_stat.size;
+
+ const auto fi_it = mf_file_infos.find(fi);
+ if (fi_it != mf_file_infos.end()) {
+ return fi_it->second;
+ } else {
+ m_file_infos.push_back(fi);
+ return m_file_infos.size() - 1;
+ }
+}
+
+bool
+Manifest::result_matches(
+ const Context& ctx,
+ const ResultEntry& result,
+ std::unordered_map<std::string, FileStats>& stated_files,
+ std::unordered_map<std::string, Digest>& hashed_files) const
+{
+ for (uint32_t file_info_index : result.file_info_indexes) {
+ const auto& fi = m_file_infos[file_info_index];
+ const auto& path = m_files[fi.index];
+
+ auto stated_files_iter = stated_files.find(path);
+ if (stated_files_iter == stated_files.end()) {
+ auto file_stat = Stat::stat(path, Stat::OnError::log);
+ if (!file_stat) {
+ return false;
+ }
+ FileStats st;
+ st.size = file_stat.size();
+ st.mtime = file_stat.mtime();
+ st.ctime = file_stat.ctime();
+ stated_files_iter = stated_files.emplace(path, st).first;
+ }
+ const FileStats& fs = stated_files_iter->second;
+
+ if (fi.fsize != fs.size) {
+ return false;
+ }
+
+ // 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.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);
+ return false;
+ }
+
+ if (ctx.config.sloppiness().is_enabled(core::Sloppy::file_stat_matches)) {
+ if (!(ctx.config.sloppiness().is_enabled(
+ core::Sloppy::file_stat_matches_ctime))) {
+ if (fi.mtime == fs.mtime && fi.ctime == fs.ctime) {
+ LOG("mtime/ctime hit for {}", path);
+ continue;
+ } else {
+ LOG("mtime/ctime miss for {}", path);
+ }
+ } else {
+ if (fi.mtime == fs.mtime) {
+ LOG("mtime hit for {}", path);
+ continue;
+ } else {
+ LOG("mtime miss for {}", path);
+ }
+ }
+ }
+
+ auto hashed_files_iter = hashed_files.find(path);
+ if (hashed_files_iter == hashed_files.end()) {
+ Digest actual_digest;
+ int ret = hash_source_code_file(ctx, actual_digest, path, fs.size);
+ if (ret & HASH_SOURCE_CODE_ERROR) {
+ LOG("Failed hashing {}", path);
+ return false;
+ }
+ if (ret & HASH_SOURCE_CODE_FOUND_TIME) {
+ return false;
+ }
+
+ hashed_files_iter = hashed_files.emplace(path, actual_digest).first;
+ }
+
+ if (fi.digest != hashed_files_iter->second) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+Manifest::inspect(FILE* const stream) const
+{
+ PRINT(stream, "Manifest format version: {}\n", k_format_version);
+
+ PRINT(stream, "File paths ({}):\n", m_files.size());
+ for (size_t i = 0; i < m_files.size(); ++i) {
+ PRINT(stream, " {}: {}\n", i, m_files[i]);
+ }
+
+ PRINT(stream, "File infos ({}):\n", m_file_infos.size());
+ for (size_t i = 0; i < m_file_infos.size(); ++i) {
+ PRINT(stream, " {}:\n", i);
+ PRINT(stream, " Path index: {}\n", m_file_infos[i].index);
+ PRINT(stream, " Hash: {}\n", m_file_infos[i].digest.to_string());
+ PRINT(stream, " File size: {}\n", m_file_infos[i].fsize);
+ if (m_file_infos[i].mtime == util::TimePoint()) {
+ PRINT_RAW(stream, " Mtime: -\n");
+ } else {
+ PRINT(stream,
+ " Mtime: {}.{:09}\n",
+ m_file_infos[i].mtime.sec(),
+ m_file_infos[i].mtime.nsec_decimal_part());
+ }
+ if (m_file_infos[i].ctime == util::TimePoint()) {
+ PRINT_RAW(stream, " Ctime: -\n");
+ } else {
+ PRINT(stream,
+ " Ctime: {}.{:09}\n",
+ m_file_infos[i].ctime.sec(),
+ m_file_infos[i].ctime.nsec_decimal_part());
+ }
+ }
+
+ PRINT(stream, "Results ({}):\n", m_results.size());
+ for (size_t i = 0; i < m_results.size(); ++i) {
+ PRINT(stream, " {}:\n", i);
+ PRINT_RAW(stream, " File info indexes:");
+ for (uint32_t file_info_index : m_results[i].file_info_indexes) {
+ PRINT(stream, " {}", file_info_index);
+ }
+ PRINT_RAW(stream, "\n");
+ PRINT(stream, " Key: {}\n", m_results[i].key.to_string());
+ }
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2009-2022 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 <Digest.hpp>
+#include <core/Serializer.hpp>
+#include <util/TimePoint.hpp>
+
+#include <third_party/nonstd/span.hpp>
+
+#include <cstdint>
+#include <functional>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+class Context;
+
+namespace core {
+
+class Manifest : public Serializer
+{
+public:
+ static const uint8_t k_format_version;
+
+ struct FileStats
+ {
+ uint64_t size;
+ util::TimePoint mtime;
+ util::TimePoint ctime;
+ };
+
+ using FileStater = std::function<FileStats(std::string)>;
+
+ Manifest() = default;
+
+ void read(nonstd::span<const uint8_t> data);
+
+ std::optional<Digest> look_up_result_digest(const Context& ctx) const;
+
+ bool add_result(const Digest& result_key,
+ const std::unordered_map<std::string, Digest>& included_files,
+ const FileStater& stat_file);
+
+ // core::Serializer
+ uint32_t serialized_size() const override;
+ void serialize(util::Bytes& output) override;
+
+ void inspect(FILE* stream) const;
+
+private:
+ struct FileInfo
+ {
+ uint32_t index; // Index to m_files.
+ Digest digest; // Digest of referenced file.
+ uint64_t fsize; // Size of referenced file.
+ util::TimePoint mtime; // mtime of referenced file.
+ util::TimePoint ctime; // ctime of referenced file.
+
+ bool operator==(const FileInfo& other) const;
+ };
+
+ friend std::hash<FileInfo>;
+
+ struct ResultEntry
+ {
+ std::vector<uint32_t> file_info_indexes; // Indexes to m_file_infos.
+ Digest key; // Key of the result.
+
+ bool operator==(const ResultEntry& other) const;
+ };
+
+ std::vector<std::string> m_files; // Names of referenced include files.
+ std::vector<FileInfo> m_file_infos; // Info about referenced include files.
+ std::vector<ResultEntry> m_results;
+
+ void clear();
+
+ uint32_t get_file_info_index(
+ const std::string& path,
+ const Digest& digest,
+ const std::unordered_map<std::string, uint32_t>& mf_files,
+ const std::unordered_map<FileInfo, uint32_t>& mf_file_infos,
+ const FileStater& file_state);
+
+ bool
+ result_matches(const Context& ctx,
+ const ResultEntry& result,
+ std::unordered_map<std::string, FileStats>& stated_files,
+ std::unordered_map<std::string, Digest>& hashed_files) const;
+};
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2022 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 "MsvcShowIncludesOutput.hpp"
+
+#include <Context.hpp>
+#include <Util.hpp>
+#include <util/string.hpp>
+
+namespace core::MsvcShowIncludesOutput {
+
+std::vector<std::string_view>
+get_includes(std::string_view file_content, std::string_view prefix)
+{
+ // /showIncludes output is written to stdout together with other messages.
+ // Every line of it is "<prefix> <spaces> <file>" where the prefix is "Note:
+ // including file:" in English but can be localized.
+
+ std::vector<std::string_view> result;
+ // This will split at each \r or \n, but that simply means there will be empty
+ // "lines".
+ for (std::string_view line : Util::split_into_views(file_content, "\r\n")) {
+ if (util::starts_with(line, prefix)) {
+ size_t pos = prefix.size();
+ while (pos < line.size() && isspace(line[pos])) {
+ ++pos;
+ }
+ std::string_view include = line.substr(pos);
+ if (!include.empty()) {
+ result.push_back(include);
+ }
+ }
+ }
+ return result;
+}
+
+util::Bytes
+strip_includes(const Context& ctx, util::Bytes&& stdout_data)
+{
+ using util::Tokenizer;
+ using Mode = Tokenizer::Mode;
+ using IncludeDelimiter = Tokenizer::IncludeDelimiter;
+
+ if (stdout_data.empty() || !ctx.auto_depend_mode
+ || ctx.config.compiler_type() != CompilerType::msvc) {
+ return std::move(stdout_data);
+ }
+
+ util::Bytes new_stdout_data;
+ for (const auto line : Tokenizer(util::to_string_view(stdout_data),
+ "\n",
+ Mode::include_empty,
+ IncludeDelimiter::yes)) {
+ if (!util::starts_with(line, ctx.config.msvc_dep_prefix())) {
+ new_stdout_data.insert(new_stdout_data.end(), line.data(), line.size());
+ }
+ }
+ return new_stdout_data;
+}
+
+} // namespace core::MsvcShowIncludesOutput
--- /dev/null
+// Copyright (C) 2022 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 <util/Bytes.hpp>
+
+#include <string_view>
+#include <vector>
+
+class Context;
+
+namespace core::MsvcShowIncludesOutput {
+
+std::vector<std::string_view> get_includes(std::string_view file_content,
+ std::string_view prefix);
+
+util::Bytes strip_includes(const Context& ctx, util::Bytes&& stdout_data);
+
+} // namespace core::MsvcShowIncludesOutput
--- /dev/null
+// Copyright (C) 2019-2022 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 "Result.hpp"
+
+#include "Config.hpp"
+#include "Context.hpp"
+#include "Fd.hpp"
+#include "File.hpp"
+#include "Logging.hpp"
+#include "Stat.hpp"
+#include "Util.hpp"
+
+#include <ccache.hpp>
+#include <core/CacheEntryDataReader.hpp>
+#include <core/CacheEntryDataWriter.hpp>
+#include <core/Statistic.hpp>
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+#include <util/Bytes.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+#include <util/path.hpp>
+#include <util/string.hpp>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <algorithm>
+
+// Result data format
+// ==================
+//
+// Integers are big-endian.
+//
+// <payload> ::= <format_ver> <n_files> <file_entry>*
+// <format_ver> ::= uint8_t
+// <n_files> ::= uint8_t
+// <file_entry> ::= <embedded_file_entry> | <raw_file_entry>
+// <embedded_file_entry> ::= <embedded_file_marker> <file_type> <file_size>
+// <file_data>
+// <embedded_file_marker> ::= 0 (uint8_t)
+// <file_type> ::= uint8_t (see Result::FileType)
+// <file_size> ::= uint64_t
+// <file_data> ::= file_size bytes
+// <raw_file_entry> ::= <raw_file_marker> <file_type> <file_size>
+// <raw_file_marker> ::= 1 (uint8_t)
+// <file_size> ::= uint64_t
+
+namespace {
+
+// File data stored inside the result file.
+const uint8_t k_embedded_file_marker = 0;
+
+// File stored as-is in the file system.
+const uint8_t k_raw_file_marker = 1;
+
+const uint8_t k_max_raw_file_entries = 10;
+
+bool
+should_store_raw_file(const Config& config, core::Result::FileType type)
+{
+ if (!core::Result::Serializer::use_raw_files(config)) {
+ return false;
+ }
+
+ // Only store object files as raw files since there are several problems with
+ // storing other file types:
+ //
+ // 1. The compiler unlinks object files before writing to them but it doesn't
+ // unlink .d files, so it's possible to corrupt .d files just by running
+ // the compiler (see ccache issue 599).
+ // 2. .d files cause trouble for automake if hard-linked (see ccache issue
+ // 378).
+ // 3. It's unknown how the compiler treats other file types, so better safe
+ // than sorry.
+ //
+ // It would be possible to store all files in raw form for the file_clone case
+ // and only hard link object files. However, most likely it's only object
+ // files that become large enough that it's of interest to clone or hard link
+ // them, so we keep things simple for now. This will also save i-nodes in the
+ // cache.
+ return type == core::Result::FileType::object;
+}
+
+} // namespace
+
+namespace core::Result {
+
+const uint8_t k_format_version = 0;
+
+const char* const k_unknown_file_type = "<unknown type>";
+
+const char*
+file_type_to_string(FileType type)
+{
+ switch (type) {
+ case FileType::object:
+ return ".o";
+
+ case FileType::dependency:
+ return ".d";
+
+ case FileType::stderr_output:
+ return "<stderr>";
+
+ case FileType::coverage_unmangled:
+ return ".gcno-unmangled";
+
+ case FileType::stackusage:
+ return ".su";
+
+ case FileType::diagnostic:
+ return ".dia";
+
+ case FileType::dwarf_object:
+ return ".dwo";
+
+ case FileType::coverage_mangled:
+ return ".gcno-mangled";
+
+ case FileType::stdout_output:
+ return "<stdout>";
+
+ case FileType::assembler_listing:
+ return ".al";
+ }
+
+ return k_unknown_file_type;
+}
+
+std::string
+gcno_file_in_mangled_form(const Context& ctx)
+{
+ const auto& output_obj = ctx.args_info.output_obj;
+ const std::string abs_output_obj =
+ util::is_absolute_path(output_obj)
+ ? 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");
+}
+
+std::string
+gcno_file_in_unmangled_form(const Context& ctx)
+{
+ return Util::change_extension(ctx.args_info.output_obj, ".gcno");
+}
+
+Deserializer::Deserializer(nonstd::span<const uint8_t> data) : m_data(data)
+{
+}
+
+void
+Deserializer::visit(Deserializer::Visitor& visitor) const
+{
+ Header header;
+
+ CacheEntryDataReader reader(m_data);
+ header.format_version = reader.read_int<uint8_t>();
+ if (header.format_version != k_format_version) {
+ visitor.on_header(header);
+ throw Error(FMT("Unknown result format version: {} != {}",
+ header.format_version,
+ k_format_version));
+ }
+
+ header.n_files = reader.read_int<uint8_t>();
+ if (header.n_files >= k_max_raw_file_entries) {
+ visitor.on_header(header);
+ throw Error(FMT("Too many raw file entries: {} > {}",
+ header.n_files,
+ k_max_raw_file_entries));
+ }
+
+ visitor.on_header(header);
+
+ uint8_t file_number;
+ for (file_number = 0; file_number < header.n_files; ++file_number) {
+ const auto marker = reader.read_int<uint8_t>();
+ switch (marker) {
+ case k_embedded_file_marker:
+ case k_raw_file_marker:
+ break;
+
+ default:
+ throw Error(FMT("Unknown entry type: {}", marker));
+ }
+
+ const auto type = reader.read_int<UnderlyingFileTypeInt>();
+ const auto file_type = FileType(type);
+ const auto file_size = reader.read_int<uint64_t>();
+
+ if (marker == k_embedded_file_marker) {
+ visitor.on_embedded_file(
+ file_number, file_type, reader.read_bytes(file_size));
+ } else {
+ ASSERT(marker == k_raw_file_marker);
+ visitor.on_raw_file(file_number, file_type, file_size);
+ }
+ }
+
+ if (file_number != header.n_files) {
+ throw Error(FMT(
+ "Too few entries (read {}, expected {})", file_number, header.n_files));
+ }
+}
+
+Serializer::Serializer(const Config& config)
+ : m_config(config),
+ m_serialized_size(1 + 1) // format_ver + n_files
+{
+}
+
+void
+Serializer::add_data(const FileType file_type, nonstd::span<const uint8_t> data)
+{
+ m_serialized_size += 1 + 1 + 8; // marker + file_type + file_size
+ m_serialized_size += data.size();
+ m_file_entries.push_back(FileEntry{file_type, data});
+}
+
+bool
+Serializer::add_file(const FileType file_type, const std::string& path)
+{
+ m_serialized_size += 1 + 1 + 8; // marker + file_type + file_size
+ if (!should_store_raw_file(m_config, file_type)) {
+ auto st = Stat::stat(path);
+ if (!st) {
+ return false;
+ }
+ m_serialized_size += st.size();
+ }
+ m_file_entries.push_back(FileEntry{file_type, path});
+ return true;
+}
+
+uint32_t
+Serializer::serialized_size() const
+{
+ // In order to support 32-bit ccache builds, restrict size to uint32_t for
+ // now. This restriction can be lifted when we drop 32-bit support.
+ const auto max = std::numeric_limits<uint32_t>::max();
+ if (m_serialized_size > max) {
+ throw Error(
+ FMT("Serialized result too large ({} > {})", m_serialized_size, max));
+ }
+ return m_serialized_size;
+}
+
+void
+Serializer::serialize(util::Bytes& output)
+{
+ CacheEntryDataWriter writer(output);
+
+ writer.write_int(k_format_version);
+ writer.write_int<uint8_t>(m_file_entries.size());
+
+ uint8_t file_number = 0;
+ for (const auto& entry : m_file_entries) {
+ const bool is_file_entry = std::holds_alternative<std::string>(entry.data);
+ const bool store_raw =
+ is_file_entry && should_store_raw_file(m_config, entry.file_type);
+ const uint64_t file_size =
+ is_file_entry ? Stat::stat(std::get<std::string>(entry.data),
+ Stat::OnError::throw_error)
+ .size()
+ : std::get<nonstd::span<const uint8_t>>(entry.data).size();
+
+ LOG("Storing {} entry #{} {} ({} bytes){}",
+ store_raw ? "raw" : "embedded",
+ file_number,
+ file_type_to_string(entry.file_type),
+ file_size,
+ is_file_entry ? FMT(" from {}", std::get<std::string>(entry.data))
+ : "");
+
+ writer.write_int<uint8_t>(store_raw ? k_raw_file_marker
+ : k_embedded_file_marker);
+ writer.write_int(UnderlyingFileTypeInt(entry.file_type));
+ writer.write_int(file_size);
+
+ if (store_raw) {
+ m_raw_files.push_back(
+ RawFile{file_number, std::get<std::string>(entry.data)});
+ } else if (is_file_entry) {
+ const auto& path = std::get<std::string>(entry.data);
+ const auto data = util::value_or_throw<Error>(
+ util::read_file<util::Bytes>(path), FMT("Failed to read {}: ", path));
+ writer.write_bytes(data);
+ } else {
+ writer.write_bytes(std::get<nonstd::span<const uint8_t>>(entry.data));
+ }
+
+ ++file_number;
+ }
+}
+
+bool
+Serializer::use_raw_files(const Config& config)
+{
+ return config.file_clone() || config.hard_link();
+}
+
+const std::vector<Serializer::RawFile>&
+Serializer::get_raw_files() const
+{
+ return m_raw_files;
+}
+
+} // namespace core::Result
--- /dev/null
+// Copyright (C) 2019-2022 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 <core/Serializer.hpp>
+#include <util/types.hpp>
+
+#include <third_party/nonstd/span.hpp>
+
+#include <cstdint>
+#include <string>
+#include <variant>
+#include <vector>
+
+class Config;
+class Context;
+
+namespace core {
+
+class CacheEntryDataParser;
+
+namespace Result {
+
+extern const uint8_t k_format_version;
+
+extern const char* const k_unknown_file_type;
+
+using UnderlyingFileTypeInt = uint8_t;
+enum class FileType : UnderlyingFileTypeInt {
+ // These values are written into the cache result file. This means they must
+ // never be changed or removed unless the result file version is incremented.
+ // Adding new values is OK.
+
+ // The main output specified with -o or implicitly from the input filename.
+ object = 0,
+
+ // Dependency file specified with -MF or implicitly from the output filename.
+ dependency = 1,
+
+ // Text sent to standard error output.
+ stderr_output = 2,
+
+ // Coverage notes file generated by -ftest-coverage with filename in unmangled
+ // form, i.e. output file but with a .gcno extension.
+ coverage_unmangled = 3,
+
+ // Stack usage file generated by -fstack-usage, i.e. output file but with a
+ // .su extension.
+ stackusage = 4,
+
+ // Diagnostics output file specified by --serialize-diagnostics.
+ diagnostic = 5,
+
+ // DWARF object file generated by -gsplit-dwarf, i.e. output file but with a
+ // .dwo extension.
+ dwarf_object = 6,
+
+ // Coverage notes file generated by -ftest-coverage with filename in mangled
+ // form, i.e. full output file path but with a .gcno extension and with
+ // slashes replaced with hashes.
+ coverage_mangled = 7,
+
+ // Text sent to standard output.
+ stdout_output = 8,
+
+ // Assembler listing file from -Wa,-a=file.
+ assembler_listing = 9,
+};
+
+const char* file_type_to_string(FileType type);
+
+std::string gcno_file_in_mangled_form(const Context& ctx);
+std::string gcno_file_in_unmangled_form(const Context& ctx);
+
+// This class knows how to deserializer a result cache entry.
+class Deserializer
+{
+public:
+ // Read a result from `data`.
+ Deserializer(nonstd::span<const uint8_t> data);
+
+ struct Header
+ {
+ uint8_t format_version = 0;
+ uint8_t n_files = 0;
+ };
+
+ class Visitor
+ {
+ public:
+ virtual ~Visitor() = default;
+
+ virtual void on_header(const Header& header);
+
+ virtual void on_embedded_file(uint8_t file_number,
+ FileType file_type,
+ nonstd::span<const uint8_t> data) = 0;
+ virtual void on_raw_file(uint8_t file_number,
+ FileType file_type,
+ uint64_t file_size) = 0;
+ };
+
+ // Throws core::Error on error.
+ void visit(Visitor& visitor) const;
+
+private:
+ nonstd::span<const uint8_t> m_data;
+
+ void parse_file_entry(CacheEntryDataParser& parser,
+ uint8_t file_number) const;
+};
+
+inline void
+Deserializer::Visitor::on_header(const Header& /*header*/)
+{
+}
+
+// This class knows how to serialize a result cache entry.
+class Serializer : public core::Serializer
+{
+public:
+ Serializer(const Config& config);
+
+ // Register data to include in the result. The data must live until
+ // serialize() has been called.
+ void add_data(FileType file_type, nonstd::span<const uint8_t> data);
+
+ // Register a file path whose content should be included in the result.
+ [[nodiscard]] bool add_file(FileType file_type, const std::string& path);
+
+ // core::Serializer
+ uint32_t serialized_size() const override;
+ void serialize(util::Bytes& output) override;
+
+ static bool use_raw_files(const Config& config);
+
+ struct RawFile
+ {
+ uint8_t file_number;
+ std::string path;
+ };
+
+ // Get raw files to store in local storage.
+ const std::vector<RawFile>& get_raw_files() const;
+
+private:
+ const Config& m_config;
+ uint64_t m_serialized_size;
+
+ struct FileEntry
+ {
+ FileType file_type;
+ std::variant<nonstd::span<const uint8_t>, std::string> data;
+ };
+ std::vector<FileEntry> m_file_entries;
+
+ std::vector<RawFile> m_raw_files;
+};
+
+} // namespace Result
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2020-2022 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 "ResultExtractor.hpp"
+
+#include "Util.hpp"
+#include "fmtmacros.hpp"
+
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+#include <util/Bytes.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <vector>
+
+namespace core {
+
+ResultExtractor::ResultExtractor(
+ const std::string& output_directory,
+ std::optional<GetRawFilePathFunction> get_raw_file_path)
+ : m_output_directory(output_directory),
+ m_get_raw_file_path(get_raw_file_path)
+{
+}
+
+void
+ResultExtractor::on_embedded_file(uint8_t /*file_number*/,
+ Result::FileType file_type,
+ nonstd::span<const uint8_t> data)
+{
+ std::string suffix = Result::file_type_to_string(file_type);
+ if (suffix == Result::k_unknown_file_type) {
+ suffix =
+ FMT(".type_{}", static_cast<Result::UnderlyingFileTypeInt>(file_type));
+ } else if (suffix[0] == '<') {
+ suffix[0] = '.';
+ suffix.resize(suffix.length() - 1);
+ }
+
+ const auto dest_path = FMT("{}/ccache-result{}", m_output_directory, suffix);
+ util::throw_on_error<Error>(util::write_file(dest_path, data),
+ FMT("Failed to write to {}: ", dest_path));
+}
+
+void
+ResultExtractor::on_raw_file(uint8_t file_number,
+ Result::FileType file_type,
+ uint64_t file_size)
+{
+ if (!m_get_raw_file_path) {
+ throw Error("Raw entry for non-local result");
+ }
+ const auto raw_file_path = (*m_get_raw_file_path)(file_number);
+ const auto st = Stat::stat(raw_file_path, Stat::OnError::throw_error);
+ if (st.size() != file_size) {
+ throw Error(FMT("Bad file size of {} (actual {} bytes, expected {} bytes)",
+ raw_file_path,
+ st.size(),
+ file_size));
+ }
+
+ const auto data = util::value_or_throw<Error>(
+ util::read_file<util::Bytes>(raw_file_path, file_size),
+ FMT("Failed to read {}: ", raw_file_path));
+ on_embedded_file(file_number, file_type, data);
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2020-2022 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 "Fd.hpp"
+
+#include <core/Result.hpp>
+
+#include <functional>
+#include <optional>
+#include <string>
+
+namespace core {
+
+// This class extracts the parts of a result entry to a directory.
+class ResultExtractor : public Result::Deserializer::Visitor
+{
+public:
+ using GetRawFilePathFunction = std::function<std::string(uint8_t)>;
+
+ //`result_path` should be the path to the local result entry file if the
+ // result comes from local storage.
+ ResultExtractor(
+ const std::string& output_directory,
+ std::optional<GetRawFilePathFunction> get_raw_file_path = std::nullopt);
+
+ void on_embedded_file(uint8_t file_number,
+ Result::FileType file_type,
+ nonstd::span<const uint8_t> data) override;
+ void on_raw_file(uint8_t file_number,
+ Result::FileType file_type,
+ uint64_t file_size) override;
+
+private:
+ std::string m_output_directory;
+ std::optional<GetRawFilePathFunction> m_get_raw_file_path;
+};
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2020-2022 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 "ResultInspector.hpp"
+
+#include "Context.hpp"
+#include "Logging.hpp"
+#include "fmtmacros.hpp"
+
+namespace core {
+
+ResultInspector::ResultInspector(FILE* stream) : m_stream(stream)
+{
+}
+
+void
+ResultInspector::on_header(const Result::Deserializer::Header& header)
+{
+ PRINT(m_stream, "Result format version: {}\n", header.format_version);
+ PRINT(m_stream, "Number of files: {}\n", header.n_files);
+}
+
+void
+ResultInspector::on_embedded_file(uint8_t file_number,
+ Result::FileType file_type,
+ nonstd::span<const uint8_t> data)
+{
+ PRINT(m_stream,
+ "Embedded file #{}: {} ({} bytes)\n",
+ file_number,
+ Result::file_type_to_string(file_type),
+ data.size());
+}
+
+void
+ResultInspector::on_raw_file(uint8_t file_number,
+ Result::FileType file_type,
+ uint64_t file_size)
+{
+ PRINT(m_stream,
+ "Raw file #{}: {} ({} bytes)\n",
+ file_number,
+ Result::file_type_to_string(file_type),
+ file_size);
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2020-2022 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 <core/Result.hpp>
+
+#include <cstdint>
+#include <cstdio>
+
+namespace core {
+
+// This class writes information about the result entry to `stream`.
+class ResultInspector : public Result::Deserializer::Visitor
+{
+public:
+ ResultInspector(FILE* stream);
+
+ void on_header(const Result::Deserializer::Header& header) override;
+
+ void on_embedded_file(uint8_t file_number,
+ Result::FileType file_type,
+ nonstd::span<const uint8_t> data) override;
+ void on_raw_file(uint8_t file_number,
+ Result::FileType file_type,
+ uint64_t file_size) override;
+
+private:
+ FILE* m_stream;
+};
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2020-2022 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 "ResultRetriever.hpp"
+
+#include "Context.hpp"
+#include "Depfile.hpp"
+#include "Logging.hpp"
+
+#include <Context.hpp>
+#include <Stat.hpp>
+#include <core/MsvcShowIncludesOutput.hpp>
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+#include <util/string.hpp>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+namespace core {
+
+using Result::FileType;
+
+ResultRetriever::ResultRetriever(const Context& ctx,
+ std::optional<Digest> result_key)
+ : m_ctx(ctx),
+ m_result_key(result_key)
+{
+}
+
+void
+ResultRetriever::on_embedded_file(uint8_t file_number,
+ FileType file_type,
+ nonstd::span<const uint8_t> data)
+{
+ LOG("Reading embedded entry #{} {} ({} bytes)",
+ file_number,
+ Result::file_type_to_string(file_type),
+ data.size());
+
+ if (file_type == FileType::stdout_output) {
+ Util::send_to_fd(
+ m_ctx,
+ util::to_string_view(MsvcShowIncludesOutput::strip_includes(m_ctx, data)),
+ STDOUT_FILENO);
+ } else if (file_type == FileType::stderr_output) {
+ Util::send_to_fd(m_ctx, util::to_string_view(data), STDERR_FILENO);
+ } else {
+ const auto dest_path = get_dest_path(file_type);
+ if (dest_path.empty()) {
+ LOG_RAW("Not writing");
+ } else if (dest_path == "/dev/null") {
+ LOG_RAW("Not writing to /dev/null");
+ } else {
+ LOG("Writing to {}", dest_path);
+ if (file_type == FileType::dependency) {
+ write_dependency_file(dest_path, data);
+ } else {
+ util::throw_on_error<WriteError>(
+ util::write_file(dest_path, data),
+ FMT("Failed to write to {}: ", dest_path));
+ }
+ }
+ }
+}
+
+void
+ResultRetriever::on_raw_file(uint8_t file_number,
+ FileType file_type,
+ uint64_t file_size)
+{
+ LOG("Reading raw entry #{} {} ({} bytes)",
+ file_number,
+ Result::file_type_to_string(file_type),
+ file_size);
+
+ if (!m_result_key) {
+ throw core::Error("Raw entry for non-local result");
+ }
+ const auto raw_file_path =
+ m_ctx.storage.local.get_raw_file_path(*m_result_key, file_number);
+ const auto st = Stat::stat(raw_file_path, Stat::OnError::throw_error);
+ if (st.size() != file_size) {
+ throw core::Error(
+ FMT("Bad file size of {} (actual {} bytes, expected {} bytes)",
+ raw_file_path,
+ st.size(),
+ file_size));
+ }
+
+ const auto dest_path = get_dest_path(file_type);
+ if (!dest_path.empty()) {
+ try {
+ Util::clone_hard_link_or_copy_file(
+ m_ctx.config, raw_file_path, dest_path, false);
+ } catch (core::Error& e) {
+ throw WriteError(FMT("Failed to clone/link/copy {} to {}: {}",
+ raw_file_path,
+ dest_path,
+ e.what()));
+ }
+
+ // Update modification timestamp to save the file from LRU cleanup (and, if
+ // hard-linked, to make the object file newer than the source file).
+ util::set_timestamps(raw_file_path);
+ } else {
+ // Should never happen.
+ LOG("Did not copy {} since destination path is unknown for type {}",
+ raw_file_path,
+ static_cast<Result::UnderlyingFileTypeInt>(file_type));
+ }
+}
+
+std::string
+ResultRetriever::get_dest_path(FileType file_type) const
+{
+ switch (file_type) {
+ case FileType::object:
+ return m_ctx.args_info.output_obj;
+
+ case FileType::dependency:
+ if (m_ctx.args_info.generating_dependencies) {
+ return m_ctx.args_info.output_dep;
+ }
+ break;
+
+ case FileType::stdout_output:
+ case FileType::stderr_output:
+ // Should never get here.
+ break;
+
+ case FileType::coverage_unmangled:
+ if (m_ctx.args_info.generating_coverage) {
+ return Util::change_extension(m_ctx.args_info.output_obj, ".gcno");
+ }
+ break;
+
+ case FileType::stackusage:
+ if (m_ctx.args_info.generating_stackusage) {
+ return m_ctx.args_info.output_su;
+ }
+ break;
+
+ case FileType::diagnostic:
+ if (m_ctx.args_info.generating_diagnostics) {
+ return m_ctx.args_info.output_dia;
+ }
+ break;
+
+ case FileType::dwarf_object:
+ if (m_ctx.args_info.seen_split_dwarf
+ && m_ctx.args_info.output_obj != "/dev/null") {
+ return m_ctx.args_info.output_dwo;
+ }
+ break;
+
+ case FileType::coverage_mangled:
+ if (m_ctx.args_info.generating_coverage) {
+ return Result::gcno_file_in_mangled_form(m_ctx);
+ }
+ break;
+
+ case FileType::assembler_listing:
+ return m_ctx.args_info.output_al;
+ }
+
+ return {};
+}
+
+void
+ResultRetriever::write_dependency_file(const std::string& path,
+ nonstd::span<const uint8_t> data)
+{
+ ASSERT(m_ctx.args_info.dependency_target);
+
+ Fd fd(open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666));
+ if (!fd) {
+ throw WriteError(FMT("Failed to open {} for writing", path));
+ }
+
+ auto write_data = [&](auto data, auto size) {
+ util::throw_on_error<WriteError>(util::write_fd(*fd, data, size),
+ FMT("Failed to write to {}: ", path));
+ };
+
+ std::string_view str_data = util::to_string_view(data);
+ size_t start_pos = 0;
+ const size_t colon_pos = str_data.find(": ");
+ if (colon_pos != std::string::npos) {
+ const auto obj_in_dep_file = str_data.substr(0, colon_pos);
+ const auto& dep_target = *m_ctx.args_info.dependency_target;
+ if (obj_in_dep_file != dep_target) {
+ write_data(dep_target.data(), dep_target.length());
+ start_pos = colon_pos;
+ }
+ }
+
+ write_data(str_data.data() + start_pos, str_data.length() - start_pos);
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2020-2022 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 "Fd.hpp"
+
+#include <Digest.hpp>
+#include <core/Result.hpp>
+#include <core/exceptions.hpp>
+
+#include <optional>
+
+class Context;
+
+namespace core {
+
+// This class retrieves a result entry to the local file system.
+class ResultRetriever : public Result::Deserializer::Visitor
+{
+public:
+ class WriteError : public Error
+ {
+ using Error::Error;
+ };
+
+ //`path` should be the path to the local result entry file if the result comes
+ // from local storage.
+ ResultRetriever(const Context& ctx,
+ std::optional<Digest> result_key = std::nullopt);
+
+ void on_embedded_file(uint8_t file_number,
+ Result::FileType file_type,
+ nonstd::span<const uint8_t> data) override;
+ void on_raw_file(uint8_t file_number,
+ Result::FileType file_type,
+ uint64_t file_size) override;
+
+private:
+ const Context& m_ctx;
+ std::optional<Digest> m_result_key;
+
+ std::string get_dest_path(Result::FileType file_type) const;
+
+ void write_dependency_file(const std::string& path,
+ nonstd::span<const uint8_t> data);
+};
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2022 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 <util/Bytes.hpp>
+
+#include <third_party/nonstd/span.hpp>
+
+#include <cstdint>
+
+namespace core {
+
+class Serializer
+{
+public:
+ virtual ~Serializer() = default;
+ virtual uint32_t serialized_size() const = 0;
+ virtual void serialize(util::Bytes& output) = 0;
+};
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021-2022 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 <cstdint>
+#include <string>
+
+namespace core {
+
+enum class Sloppy : uint32_t {
+ none = 0U,
+
+ include_file_mtime = 1U << 0,
+ include_file_ctime = 1U << 1,
+ time_macros = 1U << 2,
+ pch_defines = 1U << 3,
+ // Allow us to match files based on their stats (size, mtime, ctime), without
+ // looking at their contents.
+ file_stat_matches = 1U << 4,
+ // Allow us to not include any system headers in the manifest include files,
+ // similar to -MM versus -M for dependencies.
+ system_headers = 1U << 5,
+ // Allow us to ignore ctimes when comparing file stats, so we can fake mtimes
+ // if we want to (it is much harder to fake ctimes, requires changing clock)
+ file_stat_matches_ctime = 1U << 6,
+ // Allow us to not include the -index-store-path option in the manifest hash.
+ clang_index_store = 1U << 7,
+ // Ignore locale settings.
+ locale = 1U << 8,
+ // Allow caching even if -fmodules is used.
+ modules = 1U << 9,
+ // Ignore virtual file system (VFS) overlay file.
+ ivfsoverlay = 1U << 10,
+ // Allow us to include incorrect working directory in .gcno files.
+ gcno_cwd = 1U << 11,
+ // Ignore -frandom-seed=*string*.
+ random_seed = 1U << 12,
+};
+
+class Sloppiness
+{
+public:
+ Sloppiness(Sloppy value = Sloppy::none);
+ explicit Sloppiness(uint32_t value);
+
+ void enable(Sloppy value);
+ bool is_enabled(Sloppy value) const;
+ uint32_t to_bitmask() const;
+
+private:
+ Sloppy m_sloppiness = Sloppy::none;
+};
+
+// --- Inline implementations ---
+
+inline Sloppiness::Sloppiness(Sloppy value) : m_sloppiness(value)
+{
+}
+
+inline Sloppiness::Sloppiness(uint32_t value)
+ : m_sloppiness(static_cast<Sloppy>(value))
+{
+}
+
+inline void
+Sloppiness::enable(Sloppy value)
+{
+ m_sloppiness = static_cast<Sloppy>(static_cast<uint32_t>(m_sloppiness)
+ | static_cast<uint32_t>(value));
+}
+
+inline bool
+Sloppiness::is_enabled(Sloppy value) const
+{
+ return static_cast<uint32_t>(m_sloppiness) & static_cast<uint32_t>(value);
+}
+
+inline uint32_t
+Sloppiness::to_bitmask() const
+{
+ return static_cast<uint32_t>(m_sloppiness);
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021-2022 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
+
+namespace core {
+
+// Statistics fields in storage order.
+enum class Statistic {
+ none = 0,
+ compiler_produced_stdout = 1,
+ compile_failed = 2,
+ internal_error = 3,
+ cache_miss = 4,
+ preprocessor_error = 5,
+ could_not_find_compiler = 6,
+ missing_cache_file = 7,
+ preprocessed_cache_hit = 8,
+ bad_compiler_arguments = 9,
+ called_for_link = 10,
+ files_in_cache = 11,
+ cache_size_kibibyte = 12,
+ obsolete_max_files = 13,
+ obsolete_max_size = 14,
+ unsupported_source_language = 15,
+ bad_output_file = 16,
+ no_input_file = 17,
+ multiple_source_files = 18,
+ autoconf_test = 19,
+ unsupported_compiler_option = 20,
+ output_to_stdout = 21,
+ direct_cache_hit = 22,
+ compiler_produced_no_output = 23,
+ compiler_produced_empty_output = 24,
+ error_hashing_extra_file = 25,
+ compiler_check_failed = 26,
+ could_not_use_precompiled_header = 27,
+ called_for_preprocessing = 28,
+ cleanups_performed = 29,
+ unsupported_code_directive = 30,
+ stats_zeroed_timestamp = 31,
+ could_not_use_modules = 32,
+ direct_cache_miss = 33,
+ preprocessed_cache_miss = 34,
+ local_storage_read_hit = 35,
+ local_storage_read_miss = 36,
+ remote_storage_read_hit = 37,
+ remote_storage_read_miss = 38,
+ remote_storage_error = 39,
+ remote_storage_timeout = 40,
+ recache = 41,
+ unsupported_environment_variable = 42,
+ local_storage_write = 43,
+ local_storage_hit = 44,
+ local_storage_miss = 45,
+ remote_storage_write = 46,
+ remote_storage_hit = 47,
+ remote_storage_miss = 48,
+
+ END
+};
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021-2022 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 "Statistics.hpp"
+
+#include <Config.hpp>
+#include <Logging.hpp>
+#include <Util.hpp>
+#include <fmtmacros.hpp>
+#include <util/TextTable.hpp>
+#include <util/string.hpp>
+
+#include <algorithm>
+
+namespace core {
+
+using core::Statistic;
+
+const unsigned FLAG_NOZERO = 1U << 0; // don't zero with --zero-stats
+const unsigned FLAG_NEVER = 1U << 1; // don't include in --print-stats
+const unsigned FLAG_ERROR = 1U << 2; // include in error count
+const unsigned FLAG_UNCACHEABLE = 1U << 3; // include in uncacheable count
+
+namespace {
+
+struct StatisticsField
+{
+ StatisticsField(const Statistic statistic_,
+ const char* const id_,
+ const char* const description_,
+ const unsigned flags_ = 0)
+ : statistic(statistic_),
+ id(id_),
+ description(description_),
+ flags(flags_)
+ {
+ }
+
+ const Statistic statistic;
+ const char* const id; // for --print-stats
+ const char* const description; // for --show-stats --verbose
+ const unsigned flags; // bitmask of FLAG_* values
+};
+
+} // namespace
+
+#define FIELD(id, ...) \
+ { \
+ Statistic::id, #id, __VA_ARGS__ \
+ }
+
+const StatisticsField k_statistics_fields[] = {
+ // Field "none" intentionally omitted.
+ FIELD(autoconf_test, "Autoconf compile/link", FLAG_UNCACHEABLE),
+ FIELD(bad_compiler_arguments, "Bad compiler arguments", FLAG_UNCACHEABLE),
+ FIELD(bad_output_file, "Could not write to output file", FLAG_ERROR),
+ FIELD(cache_miss, nullptr),
+ FIELD(cache_size_kibibyte, nullptr, FLAG_NOZERO),
+ FIELD(called_for_link, "Called for linking", FLAG_UNCACHEABLE),
+ FIELD(called_for_preprocessing, "Called for preprocessing", FLAG_UNCACHEABLE),
+ FIELD(cleanups_performed, nullptr),
+ FIELD(compile_failed, "Compilation failed", FLAG_UNCACHEABLE),
+ FIELD(compiler_check_failed, "Compiler check failed", FLAG_ERROR),
+ FIELD(compiler_produced_no_output,
+ "Compiler output file missing",
+ FLAG_UNCACHEABLE),
+ FIELD(compiler_produced_empty_output,
+ "Compiler produced empty output",
+ FLAG_UNCACHEABLE),
+ FIELD(compiler_produced_stdout, "Compiler produced stdout", FLAG_UNCACHEABLE),
+ FIELD(could_not_find_compiler, "Could not find compiler", FLAG_ERROR),
+ FIELD(could_not_use_modules, "Could not use modules", FLAG_UNCACHEABLE),
+ FIELD(could_not_use_precompiled_header,
+ "Could not use precompiled header",
+ FLAG_UNCACHEABLE),
+ FIELD(direct_cache_hit, nullptr),
+ FIELD(direct_cache_miss, nullptr),
+ FIELD(error_hashing_extra_file, "Error hashing extra file", FLAG_ERROR),
+ FIELD(files_in_cache, nullptr, FLAG_NOZERO),
+ FIELD(internal_error, "Internal error", FLAG_ERROR),
+ FIELD(local_storage_hit, nullptr),
+ FIELD(local_storage_miss, nullptr),
+ FIELD(local_storage_read_hit, nullptr),
+ FIELD(local_storage_read_miss, nullptr),
+ FIELD(local_storage_write, nullptr),
+ FIELD(missing_cache_file, "Missing cache file", FLAG_ERROR),
+ FIELD(multiple_source_files, "Multiple source files", FLAG_UNCACHEABLE),
+ FIELD(no_input_file, "No input file", FLAG_UNCACHEABLE),
+ FIELD(obsolete_max_files, nullptr, FLAG_NOZERO | FLAG_NEVER),
+ FIELD(obsolete_max_size, nullptr, FLAG_NOZERO | FLAG_NEVER),
+ FIELD(output_to_stdout, "Output to stdout", FLAG_UNCACHEABLE),
+ FIELD(preprocessed_cache_hit, nullptr),
+ FIELD(preprocessed_cache_miss, nullptr),
+ FIELD(preprocessor_error, "Preprocessing failed", FLAG_UNCACHEABLE),
+ FIELD(recache, "Forced recache", FLAG_UNCACHEABLE),
+ FIELD(remote_storage_error, nullptr),
+ FIELD(remote_storage_hit, nullptr),
+ FIELD(remote_storage_miss, nullptr),
+ FIELD(remote_storage_read_hit, nullptr),
+ FIELD(remote_storage_read_miss, nullptr),
+ FIELD(remote_storage_write, nullptr),
+ FIELD(remote_storage_timeout, nullptr),
+ FIELD(stats_zeroed_timestamp, nullptr),
+ FIELD(
+ unsupported_code_directive, "Unsupported code directive", FLAG_UNCACHEABLE),
+ FIELD(unsupported_compiler_option,
+ "Unsupported compiler option",
+ FLAG_UNCACHEABLE),
+ FIELD(unsupported_environment_variable,
+ "Unsupported environment variable",
+ FLAG_UNCACHEABLE),
+ FIELD(unsupported_source_language,
+ "Unsupported source language",
+ FLAG_UNCACHEABLE),
+};
+
+static_assert(sizeof(k_statistics_fields) / sizeof(k_statistics_fields[0])
+ == static_cast<size_t>(Statistic::END) - 1);
+
+static std::string
+format_timestamp(const util::TimePoint& value)
+{
+ if (value.sec() == 0) {
+ return "never";
+ } else {
+ const auto tm = Util::localtime(value);
+ char buffer[100] = "?";
+ if (tm) {
+ strftime(buffer, sizeof(buffer), "%c", &*tm);
+ }
+ return buffer;
+ }
+}
+
+static std::string
+percent(const uint64_t nominator, const uint64_t denominator)
+{
+ if (denominator == 0) {
+ return "";
+ } else if (nominator >= denominator) {
+ return FMT("({:5.1f}%)", (100.0 * nominator) / denominator);
+ } else {
+ return FMT("({:5.2f}%)", (100.0 * nominator) / denominator);
+ }
+}
+
+Statistics::Statistics(const StatisticsCounters& counters)
+ : m_counters(counters)
+{
+}
+
+std::vector<std::string>
+Statistics::get_statistics_ids() const
+{
+ std::vector<std::string> result;
+ for (const auto& field : k_statistics_fields) {
+ if (!(field.flags & FLAG_NOZERO)) {
+ for (size_t i = 0; i < m_counters.get(field.statistic); ++i) {
+ result.emplace_back(field.id);
+ }
+ }
+ }
+ std::sort(result.begin(), result.end());
+ return result;
+}
+
+uint64_t
+Statistics::count_stats(const unsigned flags) const
+{
+ uint64_t sum = 0;
+ for (const auto& field : k_statistics_fields) {
+ if (field.flags & flags) {
+ sum += m_counters.get(field.statistic);
+ }
+ }
+ return sum;
+}
+
+std::vector<std::pair<std::string, uint64_t>>
+Statistics::get_stats(unsigned flags, const bool all) const
+{
+ std::vector<std::pair<std::string, uint64_t>> result;
+ for (const auto& field : k_statistics_fields) {
+ const auto count = m_counters.get(field.statistic);
+ if ((field.flags & flags) && (all || count > 0)) {
+ result.emplace_back(field.description, count);
+ }
+ }
+ return result;
+}
+
+static void
+add_ratio_row(util::TextTable& table,
+ const std::string& text,
+ const uint64_t nominator,
+ const uint64_t denominator)
+{
+ if (denominator > 0) {
+ table.add_row({
+ text,
+ nominator,
+ "/",
+ denominator,
+ percent(nominator, denominator),
+ });
+ } else {
+ table.add_row({text, nominator});
+ }
+}
+
+std::string
+Statistics::format_human_readable(const Config& config,
+ const util::TimePoint& last_updated,
+ const uint8_t verbosity,
+ const bool from_log) const
+{
+ util::TextTable table;
+ using C = util::TextTable::Cell;
+
+#define S(x_) m_counters.get(Statistic::x_)
+
+ const uint64_t d_hits = S(direct_cache_hit);
+ const uint64_t d_misses = S(direct_cache_miss);
+ const uint64_t p_hits = S(preprocessed_cache_hit);
+ const uint64_t p_misses = S(preprocessed_cache_miss);
+ const uint64_t hits = d_hits + p_hits;
+ const uint64_t misses = S(cache_miss);
+ const uint64_t uncacheable = count_stats(FLAG_UNCACHEABLE);
+ const uint64_t errors = count_stats(FLAG_ERROR);
+ const uint64_t total_calls = hits + misses + errors + uncacheable;
+
+ auto cmp_fn = [](const auto& e1, const auto& e2) {
+ return e1.first.compare(e2.first) < 0;
+ };
+
+ if (verbosity > 0 && !from_log) {
+ table.add_row({"Cache directory:", C(config.cache_dir()).colspan(4)});
+ table.add_row({"Config file:", C(config.config_path()).colspan(4)});
+ table.add_row(
+ {"System config file:", C(config.system_config_path()).colspan(4)});
+ table.add_row(
+ {"Stats updated:", C(format_timestamp(last_updated)).colspan(4)});
+ if (verbosity > 1) {
+ const util::TimePoint last_zeroed(S(stats_zeroed_timestamp));
+ table.add_row(
+ {"Stats zeroed:", C(format_timestamp(last_zeroed)).colspan(4)});
+ }
+ }
+
+ if (total_calls > 0 || verbosity > 1) {
+ add_ratio_row(table, "Cacheable calls:", hits + misses, total_calls);
+ add_ratio_row(table, " Hits:", hits, hits + misses);
+ add_ratio_row(table, " Direct:", d_hits, hits);
+ add_ratio_row(table, " Preprocessed:", p_hits, hits);
+ add_ratio_row(table, " Misses:", misses, hits + misses);
+ }
+
+ if (uncacheable > 0 || verbosity > 1) {
+ add_ratio_row(table, "Uncacheable calls:", uncacheable, total_calls);
+ if (verbosity > 0) {
+ auto uncacheable_stats = get_stats(FLAG_UNCACHEABLE, verbosity > 1);
+ std::sort(uncacheable_stats.begin(), uncacheable_stats.end(), cmp_fn);
+ for (const auto& [name, value] : uncacheable_stats) {
+ add_ratio_row(table, FMT(" {}:", name), value, uncacheable);
+ }
+ }
+ }
+
+ if (errors > 0 || verbosity > 1) {
+ add_ratio_row(table, "Errors:", errors, total_calls);
+ if (verbosity > 0) {
+ auto error_stats = get_stats(FLAG_ERROR, verbosity > 1);
+ std::sort(error_stats.begin(), error_stats.end(), cmp_fn);
+ for (const auto& [name, value] : error_stats) {
+ add_ratio_row(table, FMT(" {}:", name), value, errors);
+ }
+ }
+ }
+
+ if (total_calls > 0 && verbosity > 0) {
+ table.add_heading("Successful lookups:");
+ add_ratio_row(table, " Direct:", d_hits, d_hits + d_misses);
+ add_ratio_row(table, " Preprocessed:", p_hits, p_hits + p_misses);
+ }
+
+ const uint64_t g = 1'000'000'000;
+ const uint64_t local_hits = S(local_storage_hit);
+ const uint64_t local_misses = S(local_storage_miss);
+ const uint64_t local_reads =
+ S(local_storage_read_hit) + S(local_storage_read_miss);
+ const uint64_t local_writes = S(local_storage_write);
+ const uint64_t local_size = S(cache_size_kibibyte) * 1024;
+ const uint64_t cleanups = S(cleanups_performed);
+ const uint64_t remote_hits = S(remote_storage_hit);
+ const uint64_t remote_misses = S(remote_storage_miss);
+ const uint64_t remote_reads =
+ S(remote_storage_read_hit) + S(remote_storage_read_miss);
+ const uint64_t remote_writes = S(remote_storage_write);
+ const uint64_t remote_errors = S(remote_storage_error);
+ const uint64_t remote_timeouts = S(remote_storage_timeout);
+
+ table.add_heading("Local storage:");
+ if (!from_log) {
+ std::vector<C> size_cells{
+ " Cache size (GB):",
+ C(FMT("{:.2f}", static_cast<double>(local_size) / g)).right_align()};
+ if (config.max_size() != 0) {
+ size_cells.emplace_back("/");
+ size_cells.emplace_back(
+ C(FMT("{:.2f}", static_cast<double>(config.max_size()) / g))
+ .right_align());
+ size_cells.emplace_back(percent(local_size, config.max_size()));
+ }
+ table.add_row(size_cells);
+
+ if (verbosity > 0) {
+ std::vector<C> files_cells{" Files:", S(files_in_cache)};
+ if (config.max_files() > 0) {
+ files_cells.emplace_back("/");
+ files_cells.emplace_back(config.max_files());
+ files_cells.emplace_back(
+ percent(S(files_in_cache), config.max_files()));
+ }
+ table.add_row(files_cells);
+ }
+ if (cleanups > 0 || verbosity > 1) {
+ table.add_row({" Cleanups:", cleanups});
+ }
+ }
+ if (verbosity > 0 || (remote_hits + remote_misses) > 0) {
+ add_ratio_row(table, " Hits:", local_hits, local_hits + local_misses);
+ add_ratio_row(table, " Misses:", local_misses, local_hits + local_misses);
+ }
+ if (verbosity > 0) {
+ table.add_row({" Reads:", local_reads});
+ table.add_row({" Writes:", local_writes});
+ }
+
+ if (verbosity > 1
+ || remote_hits + remote_misses + remote_errors + remote_timeouts > 0) {
+ table.add_heading("Remote storage:");
+ add_ratio_row(table, " Hits:", remote_hits, remote_hits + remote_misses);
+ add_ratio_row(
+ table, " Misses:", remote_misses, remote_hits + remote_misses);
+ if (verbosity > 0) {
+ table.add_row({" Reads:", remote_reads});
+ table.add_row({" Writes:", remote_writes});
+ }
+ if (verbosity > 1 || remote_errors > 0) {
+ table.add_row({" Errors:", remote_errors});
+ }
+ if (verbosity > 1 || remote_timeouts > 0) {
+ table.add_row({" Timeouts:", remote_timeouts});
+ }
+ }
+
+ return table.render();
+}
+
+std::string
+Statistics::format_machine_readable(const util::TimePoint& last_updated) const
+{
+ std::vector<std::string> lines;
+
+ lines.push_back(FMT("stats_updated_timestamp\t{}\n", last_updated.sec()));
+
+ for (const auto& field : k_statistics_fields) {
+ if (!(field.flags & FLAG_NEVER)) {
+ lines.push_back(
+ FMT("{}\t{}\n", field.id, m_counters.get(field.statistic)));
+ }
+ }
+
+ std::sort(lines.begin(), lines.end());
+ return util::join(lines, "");
+}
+
+std::unordered_map<std::string, Statistic>
+Statistics::get_id_map()
+{
+ std::unordered_map<std::string, Statistic> result;
+ for (const auto& field : k_statistics_fields) {
+ result[field.id] = field.statistic;
+ }
+ return result;
+}
+
+std::vector<Statistic>
+Statistics::get_zeroable_fields()
+{
+ std::vector<Statistic> result;
+ for (const auto& field : k_statistics_fields) {
+ if (!(field.flags & FLAG_NOZERO)) {
+ result.push_back(field.statistic);
+ }
+ }
+ return result;
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2020-2022 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 <core/StatisticsCounters.hpp>
+#include <util/TimePoint.hpp>
+
+#include <cstdint>
+#include <ctime>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+class Config;
+
+namespace core {
+
+class Statistics
+{
+public:
+ Statistics(const StatisticsCounters& counters);
+
+ // Return machine-readable strings representing the statistics counters.
+ std::vector<std::string> get_statistics_ids() const;
+
+ // Format cache statistics in human-readable format.
+ std::string format_human_readable(const Config& config,
+ const util::TimePoint& last_updated,
+ uint8_t verbosity,
+ bool from_log) const;
+
+ // Format cache statistics in machine-readable format.
+ std::string
+ format_machine_readable(const util::TimePoint& last_updated) const;
+
+ const StatisticsCounters& counters() const;
+
+ static std::unordered_map<std::string, Statistic> get_id_map();
+
+ static std::vector<Statistic> get_zeroable_fields();
+
+private:
+ const StatisticsCounters m_counters;
+
+ uint64_t count_stats(unsigned flags) const;
+ std::vector<std::pair<std::string, uint64_t>> get_stats(unsigned flags,
+ bool all) const;
+};
+
+// --- Inline implementations ---
+
+inline const StatisticsCounters&
+Statistics::counters() const
+{
+ return m_counters;
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2010-2022 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 "StatisticsCounters.hpp"
+
+#include <assertions.hpp>
+
+#include <algorithm>
+
+namespace core {
+
+StatisticsCounters::StatisticsCounters()
+ : m_counters(static_cast<size_t>(Statistic::END))
+{
+}
+
+StatisticsCounters::StatisticsCounters(const Statistic statistic)
+ : StatisticsCounters({statistic})
+{
+}
+
+StatisticsCounters::StatisticsCounters(
+ const std::initializer_list<Statistic> statistics)
+ : StatisticsCounters()
+{
+ for (auto st : statistics) {
+ increment(st);
+ }
+}
+
+uint64_t
+StatisticsCounters::get(Statistic statistic) const
+{
+ const auto index = static_cast<size_t>(statistic);
+ ASSERT(index < static_cast<size_t>(Statistic::END));
+ return index < m_counters.size() ? m_counters[index] : 0;
+}
+
+void
+StatisticsCounters::set(Statistic statistic, uint64_t value)
+{
+ const auto index = static_cast<size_t>(statistic);
+ ASSERT(index < static_cast<size_t>(Statistic::END));
+ m_counters[index] = value;
+}
+
+uint64_t
+StatisticsCounters::get_raw(size_t index) const
+{
+ ASSERT(index < size());
+ return m_counters[index];
+}
+
+void
+StatisticsCounters::set_raw(size_t index, uint64_t value)
+{
+ if (index >= m_counters.size()) {
+ m_counters.resize(index + 1);
+ }
+ m_counters[index] = value;
+}
+
+void
+StatisticsCounters::increment(Statistic statistic, int64_t value)
+{
+ if (value == 0) {
+ return;
+ }
+ const auto i = static_cast<size_t>(statistic);
+ if (i >= m_counters.size()) {
+ m_counters.resize(i + 1);
+ }
+ auto& counter = m_counters[i];
+ counter =
+ std::max(static_cast<int64_t>(0), static_cast<int64_t>(counter + value));
+}
+
+void
+StatisticsCounters::increment(const StatisticsCounters& other)
+{
+ m_counters.resize(std::max(size(), other.size()));
+ for (size_t i = 0; i < other.size(); ++i) {
+ auto& counter = m_counters[i];
+ counter = std::max(static_cast<int64_t>(0),
+ static_cast<int64_t>(counter + other.m_counters[i]));
+ }
+}
+
+size_t
+StatisticsCounters::size() const
+{
+ return m_counters.size();
+}
+
+bool
+StatisticsCounters::all_zero() const
+{
+ return !std::any_of(
+ m_counters.begin(), m_counters.end(), [](unsigned v) { return v != 0; });
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2010-2021 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 "Statistic.hpp"
+
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <vector>
+
+namespace core {
+
+// A simple wrapper around a vector of integers used for the statistics
+// counters.
+class StatisticsCounters
+{
+public:
+ StatisticsCounters();
+ StatisticsCounters(Statistic statistic);
+ StatisticsCounters(std::initializer_list<Statistic> statistics);
+
+ uint64_t get(Statistic statistic) const;
+ void set(Statistic statistic, uint64_t value);
+
+ uint64_t get_raw(size_t index) const;
+ void set_raw(size_t index, uint64_t value);
+
+ void increment(Statistic statistic, int64_t value = 1);
+ void increment(const StatisticsCounters& other);
+
+ size_t size() const;
+
+ // Return true if all counters are zero, false otherwise.
+ bool all_zero() const;
+
+private:
+ std::vector<uint64_t> m_counters;
+};
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021 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 "StatsLog.hpp"
+
+#include <File.hpp>
+#include <Logging.hpp>
+#include <core/Statistics.hpp>
+#include <fmtmacros.hpp>
+
+#include <fstream>
+
+namespace core {
+
+StatisticsCounters
+StatsLog::read() const
+{
+ core::StatisticsCounters counters;
+
+ const auto id_map = Statistics::get_id_map();
+
+ std::ifstream in(m_path);
+ std::string line;
+ while (std::getline(in, line, '\n')) {
+ if (line[0] == '#') {
+ continue;
+ }
+ const auto entry = id_map.find(line);
+ if (entry != id_map.end()) {
+ Statistic statistic = entry->second;
+ counters.increment(statistic, 1);
+ } else {
+ LOG("Unknown statistic: {}", line);
+ }
+ }
+
+ return counters;
+}
+
+void
+StatsLog::log_result(const std::string& input_file,
+ const std::vector<std::string>& result_ids)
+{
+ File file(m_path, "ab");
+ if (!file) {
+ LOG("Failed to open {}: {}", m_path, strerror(errno));
+ return;
+ }
+
+ PRINT(*file, "# {}\n", input_file);
+ for (const auto& id : result_ids) {
+ PRINT(*file, "{}\n", id);
+ }
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021 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 "StatisticsCounters.hpp"
+
+#include <string>
+#include <vector>
+
+namespace core {
+
+class StatsLog
+{
+public:
+ StatsLog(const std::string& path);
+
+ StatisticsCounters read() const;
+ void log_result(const std::string& input_file,
+ const std::vector<std::string>& result_ids);
+
+private:
+ const std::string m_path;
+};
+
+// --- Inline implementations ---
+
+inline StatsLog::StatsLog(const std::string& path) : m_path(path)
+{
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2019-2022 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 <optional>
+#include <stdexcept>
+#include <string>
+#include <utility>
+
+namespace core {
+
+// Don't throw or catch ErrorBase directly, use a subclass.
+class ErrorBase : public std::runtime_error
+{
+ using std::runtime_error::runtime_error;
+};
+
+// Throw an Error to indicate a potentially non-fatal error that may be caught
+// and handled by callers. An uncaught Error that reaches the top level will be
+// treated similar to Fatal.
+class Error : public ErrorBase
+{
+ using ErrorBase::ErrorBase;
+};
+
+// Throw a Fatal to make ccache print the error message to stderr and exit
+// with a non-zero exit code.
+class Fatal : public ErrorBase
+{
+ using ErrorBase::ErrorBase;
+};
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021-2022 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 "mainoptions.hpp"
+
+#include <Config.hpp>
+#include <Fd.hpp>
+#include <File.hpp>
+#include <Hash.hpp>
+#include <InodeCache.hpp>
+#include <ProgressBar.hpp>
+#include <UmaskScope.hpp>
+#include <ccache.hpp>
+#include <core/CacheEntry.hpp>
+#include <core/Manifest.hpp>
+#include <core/Result.hpp>
+#include <core/ResultExtractor.hpp>
+#include <core/ResultInspector.hpp>
+#include <core/Statistics.hpp>
+#include <core/StatsLog.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <storage/Storage.hpp>
+#include <storage/local/LocalStorage.hpp>
+#include <util/TextTable.hpp>
+#include <util/XXH3_128.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+#include <util/string.hpp>
+
+#include <fcntl.h>
+
+#include <algorithm>
+#include <optional>
+#include <string>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_GETOPT_LONG
+# include <getopt.h>
+#elif defined(_WIN32)
+# include <third_party/win32/getopt.h>
+#else
+extern "C" {
+# include <third_party/getopt_long.h>
+}
+#endif
+
+namespace core {
+
+constexpr const char VERSION_TEXT[] =
+ R"({0} version {1}
+Features: {2}
+
+Copyright (C) 2002-2007 Andrew Tridgell
+Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+
+See <https://ccache.dev/credits.html> 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.
+)";
+
+constexpr const char USAGE_TEXT[] =
+ R"(Usage:
+ {0} [options]
+ {0} compiler [compiler options]
+ compiler [compiler options] (ccache masquerading as the compiler)
+
+Common options:
+ -c, --cleanup delete old files and recalculate size counters
+ (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, --dir PATH operate on cache directory PATH instead of the
+ default
+ --evict-namespace NAMESPACE
+ remove files created in namespace NAMESPACE
+ --evict-older-than AGE remove files older than AGE (unsigned integer
+ with a d (days) or s (seconds) suffix)
+ -F, --max-files NUM set maximum number of files in cache to NUM (use
+ 0 for no limit)
+ -M, --max-size SIZE set maximum size of cache to SIZE (use 0 for no
+ limit); available suffixes: k, M, G, T (decimal)
+ and Ki, Mi, Gi, Ti (binary); default suffix: G
+ -X, --recompress LEVEL recompress the cache to level LEVEL (integer or
+ "uncompressed") using the Zstandard algorithm;
+ see "Cache compression" in the manual for details
+ -o, --set-config KEY=VAL set configuration item KEY to value VAL
+ -x, --show-compression show compression statistics
+ -p, --show-config show current configuration options in
+ human-readable format
+ --show-log-stats print statistics counters from the stats log
+ in human-readable format
+ -s, --show-stats show summary of configuration and statistics
+ counters in human-readable format (use
+ -v/--verbose once or twice for more details)
+ -v, --verbose increase verbosity
+ -z, --zero-stats zero statistics counters
+
+ -h, --help print this help text
+ -V, --version print version and copyright information
+
+Options for remote file-based storage:
+ --trim-dir PATH remove old files from directory PATH until it is
+ at most the size specified by --trim-max-size
+ (note: don't use this option to trim the local
+ cache)
+ --trim-max-size SIZE specify the maximum size for --trim-dir;
+ available suffixes: k, M, G, T (decimal) and Ki,
+ Mi, Gi, Ti (binary); default suffix: G
+ --trim-method METHOD specify the method (atime or mtime) for
+ --trim-dir; default: atime
+
+Options for scripting or debugging:
+ --checksum-file PATH print the checksum (128 bit XXH3) of the file at
+ PATH
+ --extract-result PATH extract file data stored in result file at PATH
+ to the current working directory
+ -k, --get-config KEY print the value of configuration key KEY
+ --hash-file PATH print the hash (160 bit BLAKE3) of the file at
+ PATH
+ --inspect PATH print result/manifest file at PATH in
+ human-readable format
+ --print-stats print statistics counter IDs and corresponding
+ values in machine-parsable format
+
+See also the manual on <https://ccache.dev/documentation.html>.
+)";
+
+static void
+configuration_printer(const std::string& key,
+ const std::string& value,
+ const std::string& origin)
+{
+ PRINT(stdout, "({}) {} = {}\n", origin, key, value);
+}
+
+static nonstd::expected<std::vector<uint8_t>, std::string>
+read_from_path_or_stdin(const std::string& path)
+{
+ if (path == "-") {
+ std::vector<uint8_t> output;
+ const auto result =
+ util::read_fd(STDIN_FILENO, [&](const uint8_t* data, size_t size) {
+ output.insert(output.end(), data, data + size);
+ });
+ if (!result) {
+ return nonstd::make_unexpected(
+ FMT("Failed to read from stdin: {}", result.error()));
+ }
+ return output;
+ } else {
+ const auto result = util::read_file<std::vector<uint8_t>>(path);
+ if (!result) {
+ return nonstd::make_unexpected(
+ FMT("Failed to read {}: {}", path, result.error()));
+ }
+ return *result;
+ }
+}
+
+static int
+inspect_path(const std::string& path)
+{
+ const auto cache_entry_data = read_from_path_or_stdin(path);
+ if (!cache_entry_data) {
+ PRINT(stderr, "Error: {}\n", cache_entry_data.error());
+ return EXIT_FAILURE;
+ }
+
+ core::CacheEntry cache_entry(*cache_entry_data);
+ fputs(cache_entry.header().inspect().c_str(), stdout);
+
+ const auto payload = cache_entry.payload();
+
+ switch (cache_entry.header().entry_type) {
+ case core::CacheEntryType::manifest: {
+ core::Manifest manifest;
+ manifest.read(payload);
+ manifest.inspect(stdout);
+ break;
+ }
+ case core::CacheEntryType::result:
+ Result::Deserializer result_deserializer(payload);
+ ResultInspector result_inspector(stdout);
+ result_deserializer.visit(result_inspector);
+ break;
+ }
+
+ cache_entry.verify_checksum();
+
+ return EXIT_SUCCESS;
+}
+
+static void
+print_compression_statistics(const storage::local::CompressionStatistics& cs)
+{
+ const double ratio = cs.compr_size > 0
+ ? static_cast<double>(cs.content_size) / cs.compr_size
+ : 0.0;
+ const double savings = ratio > 0.0 ? 100.0 - (100.0 / ratio) : 0.0;
+
+ using C = util::TextTable::Cell;
+ auto human_readable = Util::format_human_readable_size;
+ util::TextTable table;
+
+ table.add_row({
+ "Total data:",
+ C(human_readable(cs.compr_size + cs.incompr_size)).right_align(),
+ FMT("({} disk blocks)", human_readable(cs.on_disk_size)),
+ });
+ table.add_row({
+ "Compressed data:",
+ C(human_readable(cs.compr_size)).right_align(),
+ FMT("({:.1f}% of original size)", 100.0 - savings),
+ });
+ table.add_row({
+ " Original size:",
+ C(human_readable(cs.content_size)).right_align(),
+ });
+ table.add_row({
+ " Compression ratio:",
+ C(FMT("{:.3f} x ", ratio)).right_align(),
+ FMT("({:.1f}% space savings)", savings),
+ });
+ table.add_row({
+ "Incompressible data:",
+ C(human_readable(cs.incompr_size)).right_align(),
+ });
+
+ PRINT_RAW(stdout, table.render());
+}
+
+static void
+trim_dir(const std::string& dir,
+ const uint64_t trim_max_size,
+ const bool trim_lru_mtime)
+{
+ struct File
+ {
+ std::string path;
+ Stat stat;
+ };
+ std::vector<File> files;
+ uint64_t size_before = 0;
+
+ Util::traverse(dir, [&](const std::string& path, const bool is_dir) {
+ const auto stat = Stat::lstat(path);
+ if (!stat) {
+ // Probably some race, ignore.
+ return;
+ }
+ size_before += stat.size_on_disk();
+ if (!is_dir) {
+ const auto name = Util::base_name(path);
+ if (name == "ccache.conf" || name == "stats") {
+ throw Fatal(
+ FMT("this looks like a local cache directory (found {})", path));
+ }
+ files.push_back({path, stat});
+ }
+ });
+
+ std::sort(files.begin(), files.end(), [&](const auto& f1, const auto& f2) {
+ return trim_lru_mtime ? f1.stat.mtime() < f2.stat.mtime()
+ : f1.stat.atime() < f2.stat.atime();
+ });
+
+ uint64_t size_after = size_before;
+
+ for (const auto& file : files) {
+ if (size_after <= trim_max_size) {
+ break;
+ }
+ Util::unlink_tmp(file.path);
+ size_after -= file.stat.size();
+ }
+
+ PRINT(stdout,
+ "Removed {} ({} -> {})\n",
+ Util::format_human_readable_size(size_before - size_after),
+ Util::format_human_readable_size(size_before),
+ Util::format_human_readable_size(size_after));
+}
+
+static std::string
+get_version_text(const std::string_view ccache_name)
+{
+ return FMT(
+ VERSION_TEXT, ccache_name, CCACHE_VERSION, storage::get_features());
+}
+
+std::string
+get_usage_text(const std::string_view ccache_name)
+{
+ return FMT(USAGE_TEXT, ccache_name);
+}
+
+enum {
+ CHECKSUM_FILE,
+ CONFIG_PATH,
+ DUMP_MANIFEST,
+ DUMP_RESULT,
+ EVICT_NAMESPACE,
+ EVICT_OLDER_THAN,
+ EXTRACT_RESULT,
+ HASH_FILE,
+ INSPECT,
+ PRINT_STATS,
+ SHOW_LOG_STATS,
+ TRIM_DIR,
+ TRIM_MAX_SIZE,
+ TRIM_METHOD,
+};
+
+const char options_string[] = "cCd:k:hF:M:po:svVxX:z";
+const option long_options[] = {
+ {"checksum-file", required_argument, nullptr, CHECKSUM_FILE},
+ {"cleanup", no_argument, nullptr, 'c'},
+ {"clear", no_argument, nullptr, 'C'},
+ {"config-path", required_argument, nullptr, CONFIG_PATH},
+ {"dir", required_argument, nullptr, 'd'},
+ {"directory", required_argument, nullptr, 'd'}, // bwd compat
+ {"dump-manifest", required_argument, nullptr, DUMP_MANIFEST}, // bwd compat
+ {"dump-result", required_argument, nullptr, DUMP_RESULT}, // bwd compat
+ {"evict-namespace", required_argument, nullptr, EVICT_NAMESPACE},
+ {"evict-older-than", required_argument, nullptr, EVICT_OLDER_THAN},
+ {"extract-result", required_argument, nullptr, EXTRACT_RESULT},
+ {"get-config", required_argument, nullptr, 'k'},
+ {"hash-file", required_argument, nullptr, HASH_FILE},
+ {"help", no_argument, nullptr, 'h'},
+ {"inspect", required_argument, nullptr, INSPECT},
+ {"max-files", required_argument, nullptr, 'F'},
+ {"max-size", required_argument, nullptr, 'M'},
+ {"print-stats", no_argument, nullptr, PRINT_STATS},
+ {"recompress", required_argument, nullptr, 'X'},
+ {"set-config", required_argument, nullptr, 'o'},
+ {"show-compression", no_argument, nullptr, 'x'},
+ {"show-config", no_argument, nullptr, 'p'},
+ {"show-log-stats", no_argument, nullptr, SHOW_LOG_STATS},
+ {"show-stats", no_argument, nullptr, 's'},
+ {"trim-dir", required_argument, nullptr, TRIM_DIR},
+ {"trim-max-size", required_argument, nullptr, TRIM_MAX_SIZE},
+ {"trim-method", required_argument, nullptr, TRIM_METHOD},
+ {"verbose", no_argument, nullptr, 'v'},
+ {"version", no_argument, nullptr, 'V'},
+ {"zero-stats", no_argument, nullptr, 'z'},
+ {nullptr, 0, nullptr, 0}};
+
+int
+process_main_options(int argc, const char* const* argv)
+{
+ int c;
+ std::optional<uint64_t> trim_max_size;
+ bool trim_lru_mtime = false;
+ uint8_t verbosity = 0;
+ std::optional<std::string> evict_namespace;
+ std::optional<uint64_t> evict_max_age;
+
+ // First pass: Handle non-command options that affect command options.
+ while ((c = getopt_long(argc,
+ const_cast<char* const*>(argv),
+ options_string,
+ long_options,
+ nullptr))
+ != -1) {
+ const std::string arg = optarg ? optarg : std::string();
+
+ switch (c) {
+ case 'd': // --dir
+ Util::setenv("CCACHE_DIR", arg);
+ break;
+
+ case CONFIG_PATH:
+ Util::setenv("CCACHE_CONFIGPATH", arg);
+ break;
+
+ case TRIM_MAX_SIZE:
+ trim_max_size = Util::parse_size(arg);
+ break;
+
+ case TRIM_METHOD:
+ trim_lru_mtime = (arg == "ctime");
+ break;
+
+ case 'v': // --verbose
+ ++verbosity;
+ break;
+
+ case '?': // unknown option
+ return EXIT_FAILURE;
+ }
+ }
+
+ // Second pass: Handle command options in order.
+ optind = 1;
+ while ((c = getopt_long(argc,
+ const_cast<char* const*>(argv),
+ options_string,
+ long_options,
+ nullptr))
+ != -1) {
+ Config config;
+ config.read();
+
+ UmaskScope umask_scope(config.umask());
+
+ const std::string arg = optarg ? optarg : std::string();
+
+ switch (c) {
+ case CONFIG_PATH:
+ case 'd': // --dir
+ case TRIM_MAX_SIZE:
+ case TRIM_METHOD:
+ case 'v': // --verbose
+ // Already handled in the first pass.
+ break;
+
+ case CHECKSUM_FILE: {
+ util::XXH3_128 checksum;
+ Fd fd(arg == "-" ? STDIN_FILENO : open(arg.c_str(), O_RDONLY));
+ if (fd) {
+ util::read_fd(*fd, [&checksum](const uint8_t* data, size_t size) {
+ checksum.update({data, size});
+ });
+ const auto digest = checksum.digest();
+ PRINT(
+ stdout, "{}\n", Util::format_base16(digest.data(), digest.size()));
+ } else {
+ PRINT(stderr, "Error: Failed to checksum {}\n", arg);
+ }
+ break;
+ }
+
+ case EVICT_NAMESPACE: {
+ evict_namespace = arg;
+ break;
+ }
+
+ case EVICT_OLDER_THAN: {
+ evict_max_age = Util::parse_duration(arg);
+ break;
+ }
+
+ case EXTRACT_RESULT: {
+ umask_scope.release(); // Use original umask for files outside cache dir
+ const auto cache_entry_data = read_from_path_or_stdin(arg);
+ if (!cache_entry_data) {
+ PRINT(stderr, "Error: \"{}\"", cache_entry_data.error());
+ return EXIT_FAILURE;
+ }
+ std::optional<ResultExtractor::GetRawFilePathFunction> get_raw_file_path;
+ if (arg != "-") {
+ get_raw_file_path = [&](uint8_t file_number) {
+ return storage::local::LocalStorage::get_raw_file_path(arg,
+ file_number);
+ };
+ }
+ ResultExtractor result_extractor(".", get_raw_file_path);
+ core::CacheEntry cache_entry(*cache_entry_data);
+ const auto payload = cache_entry.payload();
+
+ Result::Deserializer result_deserializer(payload);
+ result_deserializer.visit(result_extractor);
+ cache_entry.verify_checksum();
+ return EXIT_SUCCESS;
+ }
+
+ case HASH_FILE: {
+ Hash hash;
+ const auto result =
+ arg == "-" ? hash.hash_fd(STDIN_FILENO) : hash.hash_file(arg);
+ if (result) {
+ PRINT(stdout, "{}\n", hash.digest().to_string());
+ } else {
+ PRINT(stderr, "Error: Failed to hash {}: {}\n", arg, result.error());
+ return EXIT_FAILURE;
+ }
+ break;
+ }
+
+ case INSPECT:
+ case DUMP_MANIFEST: // Backward compatibility
+ case DUMP_RESULT: // Backward compatibility
+ return inspect_path(arg);
+
+ case PRINT_STATS: {
+ const auto [counters, last_updated] =
+ storage::local::LocalStorage(config).get_all_statistics();
+ Statistics statistics(counters);
+ PRINT_RAW(stdout, statistics.format_machine_readable(last_updated));
+ break;
+ }
+
+ case 'c': // --cleanup
+ {
+ ProgressBar progress_bar("Cleaning...");
+ storage::local::LocalStorage(config).clean_all(
+ [&](double progress) { progress_bar.update(progress); });
+ if (isatty(STDOUT_FILENO)) {
+ PRINT_RAW(stdout, "\n");
+ }
+ break;
+ }
+
+ case 'C': // --clear
+ {
+ ProgressBar progress_bar("Clearing...");
+ storage::local::LocalStorage(config).wipe_all(
+ [&](double progress) { progress_bar.update(progress); });
+ if (isatty(STDOUT_FILENO)) {
+ PRINT_RAW(stdout, "\n");
+ }
+ break;
+ }
+
+ case 'h': // --help
+ PRINT(stdout, USAGE_TEXT, Util::base_name(argv[0]));
+ return EXIT_SUCCESS;
+
+ case 'k': // --get-config
+ PRINT(stdout, "{}\n", config.get_string_value(arg));
+ break;
+
+ case 'F': { // --max-files
+ auto files = util::value_or_throw<Error>(util::parse_unsigned(arg));
+ config.set_value_in_file(config.config_path(), "max_files", arg);
+ if (files == 0) {
+ PRINT_RAW(stdout, "Unset cache file limit\n");
+ } else {
+ PRINT(stdout, "Set cache file limit to {}\n", files);
+ }
+ break;
+ }
+
+ case 'M': { // --max-size
+ uint64_t size = Util::parse_size(arg);
+ config.set_value_in_file(config.config_path(), "max_size", arg);
+ if (size == 0) {
+ PRINT_RAW(stdout, "Unset cache size limit\n");
+ } else {
+ PRINT(stdout,
+ "Set cache size limit to {}\n",
+ Util::format_human_readable_size(size));
+ }
+ break;
+ }
+
+ case 'o': { // --set-config
+ // Start searching for equal sign at position 1 to improve error message
+ // for the -o=K=V case (key "=K" and value "V").
+ size_t eq_pos = arg.find('=', 1);
+ if (eq_pos == std::string::npos) {
+ throw Error(FMT("missing equal sign in \"{}\"", arg));
+ }
+ std::string key = arg.substr(0, eq_pos);
+ std::string value = arg.substr(eq_pos + 1);
+ config.set_value_in_file(config.config_path(), key, value);
+ break;
+ }
+
+ case 'p': // --show-config
+ config.visit_items(configuration_printer);
+ break;
+
+ case SHOW_LOG_STATS: {
+ if (config.stats_log().empty()) {
+ throw Fatal("No stats log has been configured");
+ }
+ Statistics statistics(StatsLog(config.stats_log()).read());
+ const auto timestamp =
+ Stat::stat(config.stats_log(), Stat::OnError::log).mtime();
+ PRINT_RAW(
+ stdout,
+ statistics.format_human_readable(config, timestamp, verbosity, true));
+ break;
+ }
+
+ case 's': { // --show-stats
+ const auto [counters, last_updated] =
+ storage::local::LocalStorage(config).get_all_statistics();
+ Statistics statistics(counters);
+ PRINT_RAW(stdout,
+ statistics.format_human_readable(
+ config, last_updated, verbosity, false));
+ break;
+ }
+
+ case TRIM_DIR:
+ if (!trim_max_size) {
+ throw Error("please specify --trim-max-size when using --trim-dir");
+ }
+ trim_dir(arg, *trim_max_size, trim_lru_mtime);
+ break;
+
+ case 'V': // --version
+ {
+ std::string_view name = Util::base_name(argv[0]);
+#ifdef _WIN32
+ name = Util::remove_extension(name);
+#endif
+ PRINT_RAW(stdout, get_version_text(name));
+ break;
+ }
+
+ case 'x': // --show-compression
+ {
+ ProgressBar progress_bar("Scanning...");
+ const auto compression_statistics =
+ storage::local::LocalStorage(config).get_compression_statistics(
+ [&](double progress) { progress_bar.update(progress); });
+ if (isatty(STDOUT_FILENO)) {
+ PRINT_RAW(stdout, "\n\n");
+ }
+ print_compression_statistics(compression_statistics);
+ break;
+ }
+
+ case 'X': // --recompress
+ {
+ std::optional<int8_t> wanted_level;
+ if (arg == "uncompressed") {
+ wanted_level = std::nullopt;
+ } else {
+ wanted_level = util::value_or_throw<Error>(
+ util::parse_signed(arg, INT8_MIN, INT8_MAX, "compression level"));
+ }
+
+ ProgressBar progress_bar("Recompressing...");
+ storage::local::LocalStorage(config).recompress(
+ wanted_level, [&](double progress) { progress_bar.update(progress); });
+ break;
+ }
+
+ case 'z': // --zero-stats
+ storage::local::LocalStorage(config).zero_all_statistics();
+ PRINT_RAW(stdout, "Statistics zeroed\n");
+ break;
+
+ default:
+ PRINT(stderr, USAGE_TEXT, Util::base_name(argv[0]));
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (evict_max_age || evict_namespace) {
+ Config config;
+ config.read();
+
+ ProgressBar progress_bar("Evicting...");
+ storage::local::LocalStorage(config).evict(
+ [&](double progress) { progress_bar.update(progress); },
+ evict_max_age,
+ evict_namespace);
+ if (isatty(STDOUT_FILENO)) {
+ PRINT_RAW(stdout, "\n");
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021-2022 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 <string>
+#include <string_view>
+
+namespace core {
+
+// The main program when not doing a compile.
+int process_main_options(int argc, const char* const* argv);
+
+std::string get_usage_text(std::string_view ccache_name);
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021-2022 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 "types.hpp"
+
+#include <Config.hpp>
+#include <assertions.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+
+namespace core {
+
+std::string
+to_string(const CacheEntryType type)
+{
+ switch (type) {
+ case CacheEntryType::manifest:
+ return "manifest";
+
+ case CacheEntryType::result:
+ return "result";
+
+ default:
+ return "unknown";
+ }
+}
+
+int8_t
+compression_level_from_config(const Config& config)
+{
+ return config.compression() ? config.compression_level() : 0;
+}
+
+CompressionType
+compression_type_from_config(const Config& config)
+{
+ return config.compression() ? CompressionType::zstd : CompressionType::none;
+}
+
+CompressionType
+compression_type_from_int(const uint8_t type)
+{
+ switch (type) {
+ case static_cast<uint8_t>(CompressionType::none):
+ return CompressionType::none;
+
+ case static_cast<uint8_t>(CompressionType::zstd):
+ return CompressionType::zstd;
+ }
+
+ throw core::Error(FMT("Unknown type: {}", type));
+}
+
+std::string
+to_string(const CompressionType type)
+{
+ switch (type) {
+ case CompressionType::none:
+ return "none";
+
+ case CompressionType::zstd:
+ return "zstd";
+ }
+
+ ASSERT(false);
+}
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021-2022 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 <cstdint>
+#include <string>
+
+class Config;
+
+namespace core {
+
+enum class CacheEntryType : uint8_t { result = 0, manifest = 1 };
+
+std::string to_string(CacheEntryType type);
+
+enum class CompressionType : uint8_t {
+ none = 0,
+ zstd = 1,
+};
+
+int8_t compression_level_from_config(const Config& config);
+
+CompressionType compression_type_from_config(const Config& config);
+
+CompressionType compression_type_from_int(uint8_t type);
+
+std::string to_string(CompressionType type);
+
+} // namespace core
--- /dev/null
+// Copyright (C) 2021 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
+
+#ifdef _WIN32
+# include <sys/stat.h>
+
+# define NOMINMAX 1
+# define STDIN_FILENO 0
+# define STDOUT_FILENO 1
+# define STDERR_FILENO 2
+
+# ifdef _MSC_VER
+# define PATH_MAX MAX_PATH
+# endif // _MSC_VER
+
+// From:
+// http://mesos.apache.org/api/latest/c++/3rdparty_2stout_2include_2stout_2windows_8hpp_source.html
+# ifdef _MSC_VER
+const mode_t S_IRUSR = mode_t(_S_IREAD);
+const mode_t S_IWUSR = mode_t(_S_IWRITE);
+# endif
+
+# ifndef S_IFIFO
+# define S_IFIFO 0x1000
+# endif
+
+# ifndef S_IFBLK
+# define S_IFBLK 0x6000
+# endif
+
+# ifndef S_IFLNK
+# define S_IFLNK 0xA000
+# endif
+
+# ifndef S_ISREG
+# define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
+# endif
+# ifndef S_ISFIFO
+# define S_ISFIFO(m) (((m)&S_IFMT) == S_IFIFO)
+# endif
+# ifndef S_ISCHR
+# define S_ISCHR(m) (((m)&S_IFMT) == S_IFCHR)
+# endif
+# ifndef S_ISLNK
+# define S_ISLNK(m) (((m)&S_IFMT) == S_IFLNK)
+# endif
+# ifndef S_ISBLK
+# define S_ISBLK(m) (((m)&S_IFMT) == S_IFBLK)
+# endif
+
+# include <direct.h>
+# include <fcntl.h>
+# include <io.h>
+# include <process.h>
+# define NOMINMAX 1
+# define WIN32_NO_STATUS
+// clang-format off
+# include <winsock2.h> // struct timeval
+// windows must be included after winsock2
+// https://stackoverflow.com/questions/1372480/c-redefinition-header-files-winsock2-h
+# include <windows.h>
+// bccrypt must go after windows.h
+// https://stackoverflow.com/questions/57472787/compile-errors-when-using-c-and-bcrypt-header
+# include <bcrypt.h> // NTSTATUS
+// clang-format on
+# undef WIN32_NO_STATUS
+# include <ntstatus.h>
+# define mkdir(a, b) _mkdir(a)
+
+// Protect against incidental use of MinGW execv.
+# define execv(a, b) do_not_call_execv_on_windows
+
+# ifdef _MSC_VER
+# define PATH_MAX MAX_PATH
+# endif
+
+# ifdef _MSC_VER
+# define DLLIMPORT __declspec(dllimport)
+# else
+# define DLLIMPORT
+# endif
+
+# define STDIN_FILENO 0
+# define STDOUT_FILENO 1
+# define STDERR_FILENO 2
+
+# ifndef O_BINARY
+# define O_BINARY 0
+# endif
+
+#else
+# define DLLIMPORT
+#endif // _WIN32
+++ /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 "system.hpp"
-
-#include "FormatNonstdStringView.hpp"
-
-#include "third_party/fmt/core.h"
-#include "third_party/nonstd/optional.hpp"
-
-#include <stdexcept>
-#include <string>
-#include <utility>
-
-// Don't throw or catch ErrorBase directly, use a subclass.
-class ErrorBase : public std::runtime_error
-{
- using std::runtime_error::runtime_error;
-};
-
-// Throw an Error to indicate a potentially non-fatal error that may be caught
-// and handled by callers. An uncaught Error that reaches the top level will be
-// treated similar to Fatal.
-class Error : public ErrorBase
-{
-public:
- // Special case: If given only one string, don't parse it as a format string.
- Error(const std::string& message);
-
- // `args` are forwarded to `fmt::format`.
- template<typename... T> inline Error(T&&... args);
-};
-
-inline Error::Error(const std::string& message) : ErrorBase(message)
-{
-}
-
-template<typename... T>
-inline Error::Error(T&&... args)
- : ErrorBase(fmt::format(std::forward<T>(args)...))
-{
-}
-
-// Throw a Fatal to make ccache print the error message to stderr and exit
-// with a non-zero exit code.
-class Fatal : public ErrorBase
-{
-public:
- // Special case: If given only one string, don't parse it as a format string.
- Fatal(const std::string& message);
-
- // `args` are forwarded to `fmt::format`.
- template<typename... T> inline Fatal(T&&... args);
-};
-
-inline Fatal::Fatal(const std::string& message) : ErrorBase(message)
-{
-}
-
-template<typename... T>
-inline Fatal::Fatal(T&&... args)
- : ErrorBase(fmt::format(std::forward<T>(args)...))
-{
-}
// Copyright (C) 2002 Andrew Tridgell
-// Copyright (C) 2011-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2011-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Stat.hpp"
#include "TemporaryFile.hpp"
#include "Util.hpp"
-#include "fmtmacros.hpp"
+#include "Win32Util.hpp"
+
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+#include <util/file.hpp>
+#include <util/path.hpp>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#endif
#ifdef _WIN32
# include "Finalizer.hpp"
-# include "Win32Util.hpp"
#endif
-using nonstd::string_view;
-
#ifdef _WIN32
static int win32execute(const char* path,
const char* const* argv,
std::string
win32getshell(const std::string& path)
{
- const char* path_env = getenv("PATH");
+ const char* path_list = getenv("PATH");
std::string sh;
- if (Util::to_lowercase(Util::get_extension(path)) == ".sh" && path_env) {
- sh = find_executable_in_path("sh.exe", "", path_env);
+ if (Util::to_lowercase(Util::get_extension(path)) == ".sh" && path_list) {
+ sh = find_executable_in_path("sh.exe", path_list);
}
if (sh.empty() && getenv("CCACHE_DETECT_SHEBANG")) {
// Detect shebang.
if (fp) {
char buf[10] = {0};
fgets(buf, sizeof(buf) - 1, fp.get());
- if (std::string(buf) == "#!/bin/sh" && path_env) {
- sh = find_executable_in_path("sh.exe", "", path_env);
+ if (std::string(buf) == "#!/bin/sh" && path_list) {
+ sh = find_executable_in_path("sh.exe", path_list);
}
}
}
if (args.length() > 8192) {
TemporaryFile tmp_file(FMT("{}/cmd_args", temp_dir));
args = Win32Util::argv_to_string(argv + 1, sh, true);
- Util::write_fd(*tmp_file.fd, args.data(), args.length());
- args = FMT("{} @{}", full_path, tmp_file.path);
+ util::write_fd(*tmp_file.fd, args.data(), args.length());
+ args = FMT(R"("{}" "@{}")", full_path, tmp_file.path);
tmp_file_path = tmp_file.path;
LOG("Arguments from {}", tmp_file.path);
}
}
if (ctx.compiler_pid == -1) {
- throw Fatal("Failed to fork: {}", strerror(errno));
+ throw core::Fatal(FMT("Failed to fork: {}", strerror(errno)));
}
if (ctx.compiler_pid == 0) {
if (result == -1 && errno == EINTR) {
continue;
}
- throw Fatal("waitpid failed: {}", strerror(errno));
+ throw core::Fatal(FMT("waitpid failed: {}", strerror(errno)));
}
{
std::string
find_executable(const Context& ctx,
const std::string& name,
- const std::string& exclude_name)
+ const std::string& exclude_path)
{
- if (Util::is_absolute_path(name)) {
+ if (util::is_absolute_path(name)) {
return name;
}
- std::string path = ctx.config.path();
- if (path.empty()) {
- path = getenv("PATH");
+ std::string path_list = ctx.config.path();
+ if (path_list.empty()) {
+ path_list = getenv("PATH");
}
- if (path.empty()) {
+ if (path_list.empty()) {
LOG_RAW("No PATH variable");
return {};
}
- return find_executable_in_path(name, exclude_name, path);
+ return find_executable_in_path(name, path_list, exclude_path);
}
std::string
find_executable_in_path(const std::string& name,
- const std::string& exclude_name,
- const std::string& path)
+ const std::string& path_list,
+ std::optional<std::string> exclude_path)
{
- if (path.empty()) {
+ if (path_list.empty()) {
return {};
}
- // Search the path looking for the first compiler of the right name that isn't
- // us.
- for (const std::string& dir : Util::split_into_strings(path, PATH_DELIM)) {
+ const auto real_exclude_path =
+ exclude_path ? Util::real_path(*exclude_path) : "";
+
+ // Search the path list looking for the first compiler of the right name that
+ // isn't us.
+ for (const std::string& dir : util::split_path_list(path_list)) {
+ const std::vector<std::string> candidates = {
+ FMT("{}/{}", dir, name),
#ifdef _WIN32
- char namebuf[MAX_PATH];
- int ret = SearchPath(
- dir.c_str(), name.c_str(), nullptr, sizeof(namebuf), namebuf, nullptr);
- if (!ret) {
- std::string exename = FMT("{}.exe", name);
- ret = SearchPath(dir.c_str(),
- exename.c_str(),
- nullptr,
- sizeof(namebuf),
- namebuf,
- nullptr);
- }
- (void)exclude_name;
- if (ret) {
- return namebuf;
- }
+ FMT("{}/{}.exe", dir, name),
+#endif
+ };
+ for (const auto& candidate : candidates) {
+ // A valid candidate:
+ //
+ // 1. Must exist (e.g., should not be a broken symlink) and be an
+ // executable.
+ // 2. Must not resolve to the same program as argv[0] (i.e.,
+ // exclude_path). This can happen if ccache is masquerading as the
+ // compiler (with or without using a symlink).
+ // 3. As an extra safety measure: must not be a ccache executable after
+ // resolving symlinks. This can happen if the candidate compiler is a
+ // symlink to another ccache executable.
+ const bool candidate_exists =
+#ifdef _WIN32
+ Stat::stat(candidate);
#else
- ASSERT(!exclude_name.empty());
- std::string fname = FMT("{}/{}", dir, name);
- auto st1 = Stat::lstat(fname);
- auto st2 = Stat::stat(fname);
- // Look for a normal executable file.
- if (st1 && st2 && st2.is_regular() && access(fname.c_str(), X_OK) == 0) {
- if (st1.is_symlink()) {
- std::string real_path = Util::real_path(fname, true);
- if (Util::base_name(real_path) == exclude_name) {
- // It's a link to "ccache"!
- continue;
+ access(candidate.c_str(), X_OK) == 0;
+#endif
+ if (candidate_exists) {
+ const auto real_candidate = Util::real_path(candidate);
+ if ((real_exclude_path.empty() || real_candidate != real_exclude_path)
+ && !Util::is_ccache_executable(real_candidate)) {
+ return candidate;
}
}
-
- // Found it!
- return fname;
}
-#endif
}
return {};
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
#include "Fd.hpp"
+#include <optional>
#include <string>
class Context;
void execute_noreturn(const char* const* argv, const std::string& temp_dir);
// Find an executable named `name` in `$PATH`. Exclude any executables that are
-// links to `exclude_name`.
+// links to `exclude_path`.
std::string find_executable(const Context& ctx,
const std::string& name,
- const std::string& exclude_name);
+ const std::string& exclude_path);
-std::string find_executable_in_path(const std::string& name,
- const std::string& exclude_name,
- const std::string& path);
+std::string
+find_executable_in_path(const std::string& name,
+ const std::string& path_list,
+ std::optional<std::string> exclude_path = std::nullopt);
#ifdef _WIN32
std::string win32getshell(const std::string& path);
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "third_party/fmt/core.h"
-#include "third_party/fmt/format.h"
+#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.
-// Copyright (C) 2009-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "Context.hpp"
#include "Hash.hpp"
#include "Logging.hpp"
-#include "Sloppiness.hpp"
#include "Stat.hpp"
+#include "Util.hpp"
+#include "Win32Util.hpp"
#include "execute.hpp"
-#include "fmtmacros.hpp"
#include "macroskip.hpp"
-#include "third_party/blake3/blake3_cpu_supports_avx2.h"
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+#include <util/file.hpp>
+#include <util/string.hpp>
#ifdef INODE_CACHE_SUPPORTED
# include "InodeCache.hpp"
#endif
-#ifdef _WIN32
-# include "Win32Util.hpp"
+#include "third_party/blake3/blake3_cpu_supports_avx2.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h>
#endif
#ifdef HAVE_AVX2
# include <immintrin.h>
#endif
-using nonstd::string_view;
-
namespace {
// Returns one of HASH_SOURCE_CODE_FOUND_DATE, HASH_SOURCE_CODE_FOUND_TIME or
//
// Pre-condition: str[pos - 1] == '_'
int
-check_for_temporal_macros_helper(string_view str, size_t pos)
+check_for_temporal_macros_helper(std::string_view str, size_t pos)
{
if (pos + 7 > str.length()) {
return 0;
}
int
-check_for_temporal_macros_bmh(string_view str)
+check_for_temporal_macros_bmh(std::string_view str)
{
int result = 0;
}
#ifdef HAVE_AVX2
-int check_for_temporal_macros_avx2(string_view str)
+# ifndef _MSC_VER // MSVC does not need explicit enabling of AVX2.
+int check_for_temporal_macros_avx2(std::string_view str)
__attribute__((target("avx2")));
+# endif
// The following algorithm, which uses AVX2 instructions to find __DATE__,
// __TIME__ and __TIMESTAMP__, is heavily inspired by
// <http://0x80.pl/articles/simd-strfind.html>.
int
-check_for_temporal_macros_avx2(string_view str)
+check_for_temporal_macros_avx2(std::string_view str)
{
int result = 0;
// A bit set in mask now indicates a possible location for a temporal macro.
while (mask != 0) {
// The start position + 1 (as we know the first char is _).
+# ifndef _MSC_VER
const auto start = pos + __builtin_ctz(mask) + 1;
+# else
+ unsigned long index;
+ _BitScanForward(&index, mask);
+ const auto start = pos + index + 1;
+# endif
// Clear the least significant bit set.
mask = mask & (mask - 1);
#endif
int
-hash_source_code_file_nocache(const Context& ctx,
- Hash& hash,
- const std::string& path,
- size_t size_hint,
- bool is_precompiled)
+do_hash_file(const Context& ctx,
+ Digest& digest,
+ const std::string& path,
+ size_t size_hint,
+ bool check_temporal_macros)
{
- if (is_precompiled) {
- if (hash.hash_file(path)) {
- return HASH_SOURCE_CODE_OK;
- } else {
- return HASH_SOURCE_CODE_ERROR;
- }
- } else {
- std::string data;
- try {
- data = Util::read_file(path, size_hint);
- } catch (Error&) {
- return HASH_SOURCE_CODE_ERROR;
+#ifdef INODE_CACHE_SUPPORTED
+ const InodeCache::ContentType content_type =
+ check_temporal_macros ? InodeCache::ContentType::checked_for_temporal_macros
+ : InodeCache::ContentType::raw;
+ if (ctx.config.inode_cache()) {
+ int result;
+ if (ctx.inode_cache.get(path, content_type, digest, &result)) {
+ return result;
}
- int result = hash_source_code_string(ctx, hash, data, path);
- return result;
}
-}
+#else
+ (void)ctx;
+#endif
-#ifdef INODE_CACHE_SUPPORTED
-InodeCache::ContentType
-get_content_type(const Config& config, const std::string& path)
-{
- if (Util::is_precompiled_header(path)) {
- return InodeCache::ContentType::precompiled_header;
+ const auto data = util::read_file<std::string>(path, size_hint);
+ if (!data) {
+ LOG("Failed to read {}: {}", path, data.error());
+ return HASH_SOURCE_CODE_ERROR;
}
- if (config.sloppiness() & SLOPPY_TIME_MACROS) {
- return InodeCache::ContentType::code_with_sloppy_time_macros;
+
+ int result = HASH_SOURCE_CODE_OK;
+ if (check_temporal_macros) {
+ result |= check_for_temporal_macros(*data);
}
- return InodeCache::ContentType::code;
-}
+
+ Hash hash;
+ hash.hash(*data);
+ digest = hash.digest();
+
+#ifdef INODE_CACHE_SUPPORTED
+ ctx.inode_cache.put(path, content_type, digest, result);
#endif
+ return result;
+}
+
} // namespace
int
-check_for_temporal_macros(string_view str)
+check_for_temporal_macros(std::string_view str)
{
#ifdef HAVE_AVX2
if (blake3_cpu_supports_avx2()) {
}
int
-hash_source_code_string(const Context& ctx,
- Hash& hash,
- string_view str,
- const std::string& path)
+hash_source_code_file(const Context& ctx,
+ Digest& digest,
+ const std::string& path,
+ size_t size_hint)
{
- int result = HASH_SOURCE_CODE_OK;
+ const bool check_temporal_macros =
+ !ctx.config.sloppiness().is_enabled(core::Sloppy::time_macros);
+ int result =
+ do_hash_file(ctx, digest, path, size_hint, check_temporal_macros);
- // Check for __DATE__, __TIME__ and __TIMESTAMP__if the sloppiness
- // configuration tells us we should.
- if (!(ctx.config.sloppiness() & SLOPPY_TIME_MACROS)) {
- result |= check_for_temporal_macros(str);
+ if (!check_temporal_macros || result == HASH_SOURCE_CODE_OK
+ || (result & HASH_SOURCE_CODE_ERROR)) {
+ return result;
+ }
+
+ if (result & HASH_SOURCE_CODE_FOUND_TIME) {
+ // We don't know for sure that the program actually uses the __TIME__ macro,
+ // but we have to assume it anyway and hash the time stamp. However, that's
+ // 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);
+ return result;
}
- // Hash the source string.
- hash.hash(str);
+ // __DATE__ or __TIMESTAMP__ found. We now make sure that the digest changes
+ // if the (potential) expansion of those macros changes by computing a new
+ // digest comprising the file digest and time information that represents the
+ // macro expansions.
+
+ Hash hash;
+ hash.hash(digest.to_string());
if (result & HASH_SOURCE_CODE_FOUND_DATE) {
LOG("Found __DATE__ in {}", path);
- // Make sure that the hash sum changes if the (potential) expansion of
- // __DATE__ changes.
hash.hash_delimiter("date");
auto now = Util::localtime();
if (!now) {
hash.hash(source_date_epoch);
}
}
- if (result & HASH_SOURCE_CODE_FOUND_TIME) {
- // We don't know for sure that the program actually uses the __TIME__ macro,
- // but we have to assume it anyway and hash the time stamp. However, that's
- // 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);
- }
if (result & HASH_SOURCE_CODE_FOUND_TIMESTAMP) {
LOG("Found __TIMESTAMP__ in {}", path);
- // Make sure that the hash sum changes if the (potential) expansion of
- // __TIMESTAMP__ changes.
const auto stat = Stat::stat(path);
if (!stat) {
return HASH_SOURCE_CODE_ERROR;
hash.hash(timestamp);
}
+ digest = hash.digest();
return result;
}
-int
-hash_source_code_file(const Context& ctx,
- Hash& hash,
- const std::string& path,
- size_t size_hint)
+bool
+hash_binary_file(const Context& ctx,
+ Digest& digest,
+ const std::string& path,
+ size_t size_hint)
{
-#ifdef INODE_CACHE_SUPPORTED
- if (!ctx.config.inode_cache()) {
-#endif
- return hash_source_code_file_nocache(
- ctx, hash, path, size_hint, Util::is_precompiled_header(path));
-
-#ifdef INODE_CACHE_SUPPORTED
- }
-
- // Reusable file hashes must be independent of the outer context. Thus hash
- // files separately so that digests based on file contents can be reused. Then
- // add the digest into the outer hash instead.
- InodeCache::ContentType content_type = get_content_type(ctx.config, path);
- Digest digest;
- int return_value;
- if (!ctx.inode_cache.get(path, content_type, digest, &return_value)) {
- Hash file_hash;
- return_value = hash_source_code_file_nocache(
- ctx,
- file_hash,
- path,
- size_hint,
- content_type == InodeCache::ContentType::precompiled_header);
- if (return_value == HASH_SOURCE_CODE_ERROR) {
- return HASH_SOURCE_CODE_ERROR;
- }
- digest = file_hash.digest();
- ctx.inode_cache.put(path, content_type, digest, return_value);
- }
- hash.hash(digest.bytes(), Digest::size(), Hash::HashType::binary);
- return return_value;
-#endif
+ return do_hash_file(ctx, digest, path, size_hint, false)
+ == HASH_SOURCE_CODE_OK;
}
bool
hash_binary_file(const Context& ctx, Hash& hash, const std::string& path)
{
- if (!ctx.config.inode_cache()) {
- return hash.hash_file(path);
- }
-
-#ifdef INODE_CACHE_SUPPORTED
- // Reusable file hashes must be independent of the outer context. Thus hash
- // files separately so that digests based on file contents can be reused. Then
- // add the digest into the outer hash instead.
Digest digest;
- if (!ctx.inode_cache.get(path, InodeCache::ContentType::binary, digest)) {
- Hash file_hash;
- if (!file_hash.hash_file(path)) {
- return false;
- }
- digest = file_hash.digest();
- ctx.inode_cache.put(path, InodeCache::ContentType::binary, digest);
+ const bool success = hash_binary_file(ctx, digest, path);
+ if (success) {
+ hash.hash(digest.to_string());
}
- hash.hash(digest.bytes(), Digest::size(), Hash::HashType::binary);
- return true;
-#else
- return hash.hash_file(path);
-#endif
+ return success;
}
bool
const std::string& compiler)
{
#ifdef _WIN32
- std::string adjusted_command = Util::strip_whitespace(command);
+ std::string adjusted_command = util::strip_whitespace(command);
// Add "echo" command.
bool using_cmd_exe;
- if (Util::starts_with(adjusted_command, "echo")) {
+ if (util::starts_with(adjusted_command, "echo")) {
adjusted_command = FMT("cmd.exe /c \"{}\"", adjusted_command);
using_cmd_exe = true;
- } else if (Util::starts_with(adjusted_command, "%compiler%")
+ } else if (util::starts_with(adjusted_command, "%compiler%")
&& compiler == "echo") {
adjusted_command =
FMT("cmd.exe /c \"{}{}\"", compiler, adjusted_command.substr(10));
STARTUPINFO si;
memset(&si, 0x00, sizeof(si));
- std::string path = find_executable_in_path(args[0], "", getenv("PATH"));
+ std::string path = find_executable_in_path(args[0], getenv("PATH"));
if (path.empty()) {
path = args[0];
}
return false;
}
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));
+ const auto compiler_check_result = hash.hash_fd(fd);
+ if (!compiler_check_result) {
+ LOG("Error hashing compiler check command output: {}",
+ compiler_check_result.error());
}
WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exitcode;
LOG("Compiler check command returned {}", exitcode);
return false;
}
- return ok;
+ return bool(compiler_check_result);
#else
int pipefd[2];
if (pipe(pipefd) == -1) {
- throw Fatal("pipe failed: {}", strerror(errno));
+ throw core::Fatal(FMT("pipe failed: {}", strerror(errno)));
}
pid_t pid = fork();
if (pid == -1) {
- throw Fatal("fork failed: {}", strerror(errno));
+ throw core::Fatal(FMT("fork failed: {}", strerror(errno)));
}
if (pid == 0) {
} else {
// Parent.
close(pipefd[1]);
- bool ok = hash.hash_fd(pipefd[0]);
- if (!ok) {
- LOG("Error hashing compiler check command output: {}", strerror(errno));
+ const auto hash_result = hash.hash_fd(pipefd[0]);
+ if (!hash_result) {
+ LOG("Error hashing compiler check command output: {}",
+ hash_result.error());
}
close(pipefd[0]);
LOG("Compiler check command returned {}", WEXITSTATUS(status));
return false;
}
- return ok;
+ return bool(hash_result);
}
#endif
}
-// Copyright (C) 2009-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
-#include "third_party/nonstd/string_view.hpp"
-
+#include <cstddef>
#include <string>
+#include <string_view>
class Config;
class Context;
+class Digest;
class Hash;
const int HASH_SOURCE_CODE_OK = 0;
// Returns a bitmask with HASH_SOURCE_CODE_FOUND_DATE,
// HASH_SOURCE_CODE_FOUND_TIME and HASH_SOURCE_CODE_FOUND_TIMESTAMP set
// appropriately.
-int check_for_temporal_macros(nonstd::string_view str);
-
-// Hash a string. Returns a bitmask of HASH_SOURCE_CODE_* results.
-int hash_source_code_string(const Context& ctx,
- Hash& hash,
- nonstd::string_view str,
- const std::string& path);
+int check_for_temporal_macros(std::string_view str);
-// Hash a file ignoring comments. Returns a bitmask of HASH_SOURCE_CODE_*
-// results.
+// Hash a source code file using the inode cache if enabled. Returns a bitmask
+// of HASH_SOURCE_CODE_* results.
int hash_source_code_file(const Context& ctx,
- Hash& hash,
+ Digest& digest,
const std::string& path,
size_t size_hint = 0);
-// Hash a binary file using the inode cache if enabled.
+// Hash a binary file (using the inode cache if enabled) and put its digest in
+// `digest`
+//
+// Returns true on success, otherwise false.
+bool hash_binary_file(const Context& ctx,
+ Digest& digest,
+ const std::string& path,
+ size_t size_hint = 0);
+
+// Hash a binary file (using the inode cache if enabled) and hash the digest to
+// `hash`.
//
// Returns true on success, otherwise false.
bool hash_binary_file(const Context& ctx, Hash& hash, const std::string& path);
#pragma once
-#include "system.hpp"
-
#include "Config.hpp"
#include <string>
-// Copyright (C) 2010-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
+#include <cstdint>
// A Boyer-Moore-Horspool skip table used for searching for the strings
// "__TIME__", "__DATE__" and "__TIMESTAMP__".
--- /dev/null
+add_subdirectory(local)
+add_subdirectory(remote)
+
+set(
+ sources
+ Storage.cpp
+)
+
+file(GLOB headers *.hpp)
+list(APPEND sources ${headers})
+
+target_sources(ccache_framework PRIVATE ${sources})
--- /dev/null
+// Copyright (C) 2021-2022 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 "Storage.hpp"
+
+#include <Config.hpp>
+#include <Logging.hpp>
+#include <MiniTrace.hpp>
+#include <TemporaryFile.hpp>
+#include <Util.hpp>
+#include <assertions.hpp>
+#include <core/Statistic.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <storage/remote/FileStorage.hpp>
+#include <storage/remote/HttpStorage.hpp>
+#ifdef HAVE_REDIS_STORAGE_BACKEND
+# include <storage/remote/RedisStorage.hpp>
+#endif
+#include <core/CacheEntry.hpp>
+#include <util/Bytes.hpp>
+#include <util/Timer.hpp>
+#include <util/Tokenizer.hpp>
+#include <util/XXH3_64.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+#include <util/string.hpp>
+
+#include <third_party/url.hpp>
+
+#include <cmath>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace storage {
+
+const std::unordered_map<std::string /*scheme*/,
+ std::shared_ptr<remote::RemoteStorage>>
+ k_remote_storage_implementations = {
+ {"file", std::make_shared<remote::FileStorage>()},
+ {"http", std::make_shared<remote::HttpStorage>()},
+#ifdef HAVE_REDIS_STORAGE_BACKEND
+ {"redis", std::make_shared<remote::RedisStorage>()},
+ {"redis+unix", std::make_shared<remote::RedisStorage>()},
+#endif
+};
+
+std::string
+get_features()
+{
+ std::vector<std::string> features;
+ features.reserve(k_remote_storage_implementations.size());
+ std::transform(k_remote_storage_implementations.begin(),
+ k_remote_storage_implementations.end(),
+ std::back_inserter(features),
+ [](auto& entry) { return FMT("{}-storage", entry.first); });
+ std::sort(features.begin(), features.end());
+ return util::join(features, " ");
+}
+
+struct RemoteStorageShardConfig
+{
+ std::string name;
+ double weight;
+};
+
+struct RemoteStorageConfig
+{
+ std::vector<RemoteStorageShardConfig> shards;
+ remote::RemoteStorage::Backend::Params params;
+ bool read_only = false;
+};
+
+struct RemoteStorageBackendEntry
+{
+ Url url; // With expanded "*".
+ std::string url_for_logging; // With expanded "*".
+ std::unique_ptr<remote::RemoteStorage::Backend> impl;
+ bool failed = false;
+};
+
+struct RemoteStorageEntry
+{
+ RemoteStorageConfig config;
+ std::string url_for_logging; // With unexpanded "*".
+ std::shared_ptr<remote::RemoteStorage> storage;
+ std::vector<RemoteStorageBackendEntry> backends;
+};
+
+static std::string
+to_string(const RemoteStorageConfig& entry)
+{
+ std::string result = entry.params.url.str();
+ for (const auto& attr : entry.params.attributes) {
+ result += FMT("|{}={}", attr.key, attr.raw_value);
+ }
+ return result;
+}
+
+static RemoteStorageConfig
+parse_storage_config(const std::string_view entry)
+{
+ const auto parts =
+ Util::split_into_views(entry, "|", util::Tokenizer::Mode::include_empty);
+
+ if (parts.empty() || parts.front().empty()) {
+ throw core::Error(
+ FMT("remote storage config must provide a URL: {}", entry));
+ }
+
+ RemoteStorageConfig result;
+ const auto url_str = std::string(parts[0]);
+ result.params.url = url_str;
+
+ // The Url class is parsing the URL object lazily. Check if the URL is valid
+ // now to avoid exceptions later.
+ try {
+ std::ignore = result.params.url.str();
+ } catch (const std::exception& e) {
+ throw core::Error(FMT("Cannot parse URL {}: {}", url_str, e.what()));
+ }
+
+ if (result.params.url.scheme().empty()) {
+ throw core::Error(FMT("URL scheme must not be empty: {}", entry));
+ }
+
+ for (size_t i = 1; i < parts.size(); ++i) {
+ if (parts[i].empty()) {
+ continue;
+ }
+ const auto [key, right_hand_side] = util::split_once(parts[i], '=');
+ const auto& raw_value = right_hand_side.value_or("true");
+ const auto value =
+ util::value_or_throw<core::Error>(util::percent_decode(raw_value));
+ if (key == "read-only") {
+ result.read_only = (value == "true");
+ } else if (key == "shards") {
+ if (url_str.find('*') == std::string::npos) {
+ throw core::Error(
+ FMT(R"(Missing "*" in URL when using shards: "{}")", url_str));
+ }
+ for (const auto& shard : util::Tokenizer(value, ",")) {
+ double weight = 1.0;
+ std::string_view name;
+ const auto lp_pos = shard.find('(');
+ if (lp_pos != std::string_view::npos) {
+ if (shard.back() != ')') {
+ throw core::Error(FMT("Invalid shard name: \"{}\"", shard));
+ }
+ weight =
+ util::value_or_throw<core::Error>(util::parse_double(std::string(
+ shard.substr(lp_pos + 1, shard.length() - lp_pos - 2))));
+ if (weight < 0.0) {
+ throw core::Error(FMT("Invalid shard weight: \"{}\"", weight));
+ }
+ name = shard.substr(0, lp_pos);
+ } else {
+ name = shard;
+ }
+
+ result.shards.push_back({std::string(name), weight});
+ }
+ }
+
+ result.params.attributes.push_back(
+ {std::string(key), value, std::string(raw_value)});
+ }
+
+ return result;
+}
+
+static std::vector<RemoteStorageConfig>
+parse_storage_configs(const std::string_view& configs)
+{
+ std::vector<RemoteStorageConfig> result;
+ for (const auto& config : util::Tokenizer(configs, " ")) {
+ result.push_back(parse_storage_config(config));
+ }
+ return result;
+}
+
+static std::shared_ptr<remote::RemoteStorage>
+get_storage(const Url& url)
+{
+ const auto it = k_remote_storage_implementations.find(url.scheme());
+ if (it != k_remote_storage_implementations.end()) {
+ return it->second;
+ } else {
+ return {};
+ }
+}
+
+Storage::Storage(const Config& config) : local(config), m_config(config)
+{
+}
+
+// Define the destructor in the implementation file to avoid having to declare
+// RemoteStorageEntry and its constituents in the header file.
+// NOLINTNEXTLINE(modernize-use-equals-default)
+Storage::~Storage()
+{
+}
+
+void
+Storage::initialize()
+{
+ add_remote_storages();
+}
+
+void
+Storage::finalize()
+{
+ local.finalize();
+}
+
+void
+Storage::get(const Digest& key,
+ const core::CacheEntryType type,
+ const EntryReceiver& entry_receiver)
+{
+ MTR_SCOPE("storage", "get");
+
+ if (!m_config.remote_only()) {
+ auto value = local.get(key, type);
+ if (value) {
+ if (m_config.reshare()) {
+ put_in_remote_storage(key, *value, true);
+ }
+ if (entry_receiver(std::move(*value))) {
+ return;
+ }
+ }
+ }
+
+ get_from_remote_storage(key, type, [&](util::Bytes&& data) {
+ if (!m_config.remote_only()) {
+ local.put(key, type, data, true);
+ }
+ return entry_receiver(std::move(data));
+ });
+}
+
+void
+Storage::put(const Digest& key,
+ const core::CacheEntryType type,
+ nonstd::span<const uint8_t> value)
+{
+ MTR_SCOPE("storage", "put");
+
+ if (!m_config.remote_only()) {
+ local.put(key, type, value);
+ }
+ put_in_remote_storage(key, value, false);
+}
+
+void
+Storage::remove(const Digest& key, const core::CacheEntryType type)
+{
+ MTR_SCOPE("storage", "remove");
+
+ if (!m_config.remote_only()) {
+ local.remove(key, type);
+ }
+ remove_from_remote_storage(key);
+}
+
+bool
+Storage::has_remote_storage() const
+{
+ return !m_remote_storages.empty();
+}
+
+std::string
+Storage::get_remote_storage_config_for_logging() const
+{
+ auto configs = parse_storage_configs(m_config.remote_storage());
+ for (auto& config : configs) {
+ const auto storage = get_storage(config.params.url);
+ if (storage) {
+ storage->redact_secrets(config.params);
+ }
+ }
+ return util::join(configs, " ");
+}
+
+static void
+redact_url_for_logging(Url& url_for_logging)
+{
+ url_for_logging.user_info("");
+}
+
+void
+Storage::add_remote_storages()
+{
+ const auto configs = parse_storage_configs(m_config.remote_storage());
+ for (const auto& config : configs) {
+ auto url_for_logging = config.params.url;
+ redact_url_for_logging(url_for_logging);
+ const auto storage = get_storage(config.params.url);
+ if (!storage) {
+ throw core::Error(
+ FMT("unknown remote storage URL: {}", url_for_logging.str()));
+ }
+ m_remote_storages.push_back(std::make_unique<RemoteStorageEntry>(
+ RemoteStorageEntry{config, url_for_logging.str(), storage, {}}));
+ }
+}
+
+void
+Storage::mark_backend_as_failed(
+ RemoteStorageBackendEntry& backend_entry,
+ const remote::RemoteStorage::Backend::Failure failure)
+{
+ // The backend is expected to log details about the error.
+ backend_entry.failed = true;
+ local.increment_statistic(
+ failure == remote::RemoteStorage::Backend::Failure::timeout
+ ? core::Statistic::remote_storage_timeout
+ : core::Statistic::remote_storage_error);
+}
+
+static double
+to_half_open_unit_interval(uint64_t value)
+{
+ constexpr uint8_t double_significand_bits = 53;
+ constexpr uint64_t denominator = 1ULL << double_significand_bits;
+ constexpr uint64_t mask = denominator - 1;
+ return static_cast<double>(value & mask) / denominator;
+}
+
+static Url
+get_shard_url(const Digest& key,
+ const std::string& url,
+ const std::vector<RemoteStorageShardConfig>& shards)
+{
+ ASSERT(!shards.empty());
+
+ // This is the "weighted rendezvous hashing" algorithm.
+ double highest_score = -1.0;
+ std::string best_shard;
+ for (const auto& shard_config : shards) {
+ util::XXH3_64 hash;
+ hash.update(key.bytes(), key.size());
+ hash.update(shard_config.name.data(), shard_config.name.length());
+ const double score = to_half_open_unit_interval(hash.digest());
+ ASSERT(score >= 0.0 && score < 1.0);
+ const double weighted_score =
+ score == 0.0 ? 0.0 : shard_config.weight / -std::log(score);
+ if (weighted_score > highest_score) {
+ best_shard = shard_config.name;
+ highest_score = weighted_score;
+ }
+ }
+
+ return util::replace_first(url, "*", best_shard);
+}
+
+RemoteStorageBackendEntry*
+Storage::get_backend(RemoteStorageEntry& entry,
+ const Digest& key,
+ const std::string_view operation_description,
+ const bool for_writing)
+{
+ if (for_writing && entry.config.read_only) {
+ LOG("Not {} {} since it is read-only",
+ operation_description,
+ entry.url_for_logging);
+ return nullptr;
+ }
+
+ const auto shard_url =
+ entry.config.shards.empty()
+ ? entry.config.params.url
+ : get_shard_url(key, entry.config.params.url.str(), entry.config.shards);
+ auto backend =
+ std::find_if(entry.backends.begin(),
+ entry.backends.end(),
+ [&](const auto& x) { return x.url.str() == shard_url.str(); });
+
+ if (backend == entry.backends.end()) {
+ auto shard_url_for_logging = shard_url;
+ redact_url_for_logging(shard_url_for_logging);
+ entry.backends.push_back(
+ {shard_url, shard_url_for_logging.str(), {}, false});
+ auto shard_params = entry.config.params;
+ shard_params.url = shard_url;
+ try {
+ entry.backends.back().impl = entry.storage->create_backend(shard_params);
+ } catch (const remote::RemoteStorage::Backend::Failed& e) {
+ LOG("Failed to construct backend for {}{}",
+ entry.url_for_logging,
+ std::string_view(e.what()).empty() ? "" : FMT(": {}", e.what()));
+ mark_backend_as_failed(entry.backends.back(), e.failure());
+ return nullptr;
+ }
+ return &entry.backends.back();
+ } else if (backend->failed) {
+ LOG("Not {} {} since it failed earlier",
+ operation_description,
+ entry.url_for_logging);
+ return nullptr;
+ } else {
+ return &*backend;
+ }
+}
+
+void
+Storage::get_from_remote_storage(const Digest& key,
+ const core::CacheEntryType type,
+ const EntryReceiver& entry_receiver)
+{
+ MTR_SCOPE("remote_storage", "get");
+
+ for (const auto& entry : m_remote_storages) {
+ auto backend = get_backend(*entry, key, "getting from", false);
+ if (!backend) {
+ continue;
+ }
+
+ Timer timer;
+ auto result = backend->impl->get(key);
+ const auto ms = timer.measure_ms();
+ if (!result) {
+ mark_backend_as_failed(*backend, result.error());
+ continue;
+ }
+
+ auto& value = *result;
+ if (value) {
+ LOG("Retrieved {} from {} ({:.2f} ms)",
+ key.to_string(),
+ backend->url_for_logging,
+ ms);
+ local.increment_statistic(core::Statistic::remote_storage_read_hit);
+ if (type == core::CacheEntryType::result) {
+ local.increment_statistic(core::Statistic::remote_storage_hit);
+ }
+ if (entry_receiver(std::move(*value))) {
+ return;
+ }
+ } else {
+ LOG("No {} in {} ({:.2f} ms)",
+ key.to_string(),
+ backend->url_for_logging,
+ ms);
+ local.increment_statistic(core::Statistic::remote_storage_read_miss);
+ if (type == core::CacheEntryType::result) {
+ local.increment_statistic(core::Statistic::remote_storage_miss);
+ }
+ }
+ }
+}
+
+void
+Storage::put_in_remote_storage(const Digest& key,
+ nonstd::span<const uint8_t> value,
+ bool only_if_missing)
+{
+ MTR_SCOPE("remote_storage", "put");
+
+ if (!core::CacheEntry::Header(value).self_contained) {
+ LOG("Not putting {} in remote storage since it's not self-contained",
+ key.to_string());
+ return;
+ }
+
+ for (const auto& entry : m_remote_storages) {
+ auto backend = get_backend(*entry, key, "putting in", true);
+ if (!backend) {
+ continue;
+ }
+
+ Timer timer;
+ const auto result = backend->impl->put(key, value, only_if_missing);
+ const auto ms = timer.measure_ms();
+ if (!result) {
+ // The backend is expected to log details about the error.
+ mark_backend_as_failed(*backend, result.error());
+ continue;
+ }
+
+ const bool stored = *result;
+ LOG("{} {} in {} ({:.2f} ms)",
+ stored ? "Stored" : "Did not have to store",
+ key.to_string(),
+ backend->url_for_logging,
+ ms);
+ local.increment_statistic(core::Statistic::remote_storage_write);
+ }
+}
+
+void
+Storage::remove_from_remote_storage(const Digest& key)
+{
+ MTR_SCOPE("remote_storage", "remove");
+
+ for (const auto& entry : m_remote_storages) {
+ auto backend = get_backend(*entry, key, "removing from", true);
+ if (!backend) {
+ continue;
+ }
+
+ Timer timer;
+ const auto result = backend->impl->remove(key);
+ const auto ms = timer.measure_ms();
+ if (!result) {
+ mark_backend_as_failed(*backend, result.error());
+ continue;
+ }
+
+ const bool removed = *result;
+ if (removed) {
+ LOG("Removed {} from {} ({:.2f} ms)",
+ key.to_string(),
+ backend->url_for_logging,
+ ms);
+ } else {
+ LOG("No {} to remove from {} ({:.2f} ms)",
+ key.to_string(),
+ backend->url_for_logging,
+ ms);
+ }
+
+ local.increment_statistic(core::Statistic::remote_storage_write);
+ }
+}
+
+} // namespace storage
--- /dev/null
+// Copyright (C) 2021-2022 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 <core/types.hpp>
+#include <storage/local/LocalStorage.hpp>
+#include <storage/remote/RemoteStorage.hpp>
+#include <storage/types.hpp>
+#include <util/Bytes.hpp>
+
+#include <third_party/nonstd/span.hpp>
+
+#include <functional>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+class Digest;
+
+namespace storage {
+
+std::string get_features();
+
+struct RemoteStorageBackendEntry;
+struct RemoteStorageEntry;
+
+class Storage
+{
+public:
+ Storage(const Config& config);
+ ~Storage();
+
+ void initialize();
+ void finalize();
+
+ local::LocalStorage local;
+
+ using EntryReceiver = std::function<bool(util::Bytes&&)>;
+
+ void get(const Digest& key,
+ core::CacheEntryType type,
+ const EntryReceiver& entry_receiver);
+
+ void put(const Digest& key,
+ core::CacheEntryType type,
+ nonstd::span<const uint8_t> value);
+
+ void remove(const Digest& key, core::CacheEntryType type);
+
+ bool has_remote_storage() const;
+ std::string get_remote_storage_config_for_logging() const;
+
+private:
+ const Config& m_config;
+ std::vector<std::unique_ptr<RemoteStorageEntry>> m_remote_storages;
+
+ void add_remote_storages();
+
+ void mark_backend_as_failed(RemoteStorageBackendEntry& backend_entry,
+ remote::RemoteStorage::Backend::Failure failure);
+
+ RemoteStorageBackendEntry* get_backend(RemoteStorageEntry& entry,
+ const Digest& key,
+ std::string_view operation_description,
+ const bool for_writing);
+
+ void get_from_remote_storage(const Digest& key,
+ core::CacheEntryType type,
+ const EntryReceiver& entry_receiver);
+
+ void put_in_remote_storage(const Digest& key,
+ nonstd::span<const uint8_t> value,
+ bool only_if_missing);
+
+ void remove_from_remote_storage(const Digest& key);
+};
+
+} // namespace storage
--- /dev/null
+set(
+ sources
+ CacheFile.cpp
+ LocalStorage.cpp
+ LocalStorage_cleanup.cpp
+ LocalStorage_compress.cpp
+ LocalStorage_statistics.cpp
+ StatsFile.cpp
+ util.cpp
+)
+
+file(GLOB headers *.hpp)
+list(APPEND sources ${headers})
+
+target_sources(ccache_framework PRIVATE ${sources})
--- /dev/null
+// Copyright (C) 2019-2022 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 "CacheFile.hpp"
+
+#include <core/Manifest.hpp>
+#include <core/Result.hpp>
+#include <util/string.hpp>
+
+const Stat&
+CacheFile::lstat() const
+{
+ if (!m_stat) {
+ m_stat = Stat::lstat(m_path);
+ }
+
+ return *m_stat;
+}
+
+CacheFile::Type
+CacheFile::type() const
+{
+ if (util::ends_with(m_path, "M")) {
+ return Type::manifest;
+ } else if (util::ends_with(m_path, "R")) {
+ return Type::result;
+ } else if (util::ends_with(m_path, "W")) {
+ return Type::raw;
+ } else {
+ return Type::unknown;
+ }
+}
--- /dev/null
+// Copyright (C) 2019-2022 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 <Stat.hpp>
+
+#include <optional>
+#include <string>
+
+class CacheFile
+{
+public:
+ enum class Type { result, manifest, raw, unknown };
+
+ explicit CacheFile(const std::string& path);
+
+ const Stat& lstat() const;
+ const std::string& path() const;
+ Type type() const;
+
+private:
+ std::string m_path;
+ mutable std::optional<Stat> m_stat;
+};
+
+inline CacheFile::CacheFile(const std::string& path) : m_path(path)
+{
+}
+
+inline const std::string&
+CacheFile::path() const
+{
+ return m_path;
+}
--- /dev/null
+// Copyright (C) 2021-2022 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 "LocalStorage.hpp"
+
+#include <AtomicFile.hpp>
+#include <Config.hpp>
+#include <Logging.hpp>
+#include <MiniTrace.hpp>
+#include <Util.hpp>
+#include <assertions.hpp>
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+#include <storage/local/StatsFile.hpp>
+#include <util/Duration.hpp>
+#include <util/file.hpp>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+using core::Statistic;
+
+namespace storage::local {
+
+// How often (in seconds) to scan $CCACHE_DIR/tmp for left-over temporary
+// files.
+const util::Duration k_tempdir_cleanup_interval(2 * 24 * 60 * 60); // 2 days
+
+// Maximum files per cache directory. This constant is somewhat arbitrarily
+// chosen to be large enough to avoid unnecessary cache levels but small enough
+// not to make it too slow for legacy file systems with bad performance for
+// large directories. It could be made configurable, but hopefully there will be
+// no need to do that.
+const uint64_t k_max_cache_files_per_directory = 2000;
+
+// Minimum number of cache levels ($CCACHE_DIR/1/2/stored_file).
+const uint8_t k_min_cache_levels = 2;
+
+// Maximum number of cache levels ($CCACHE_DIR/1/2/3/stored_file).
+//
+// On a cache miss, (k_max_cache_levels - k_min_cache_levels + 1) cache lookups
+// (i.e. stat system calls) will be performed for a cache entry.
+//
+// An assumption made here is that if a cache is so large that it holds more
+// than 16^4 * k_max_cache_files_per_directory files then we can assume that the
+// file system is sane enough to handle more than
+// k_max_cache_files_per_directory.
+const uint8_t k_max_cache_levels = 4;
+
+static std::string
+suffix_from_type(const core::CacheEntryType type)
+{
+ switch (type) {
+ case core::CacheEntryType::manifest:
+ return "M";
+
+ case core::CacheEntryType::result:
+ return "R";
+ }
+
+ ASSERT(false);
+}
+
+static uint8_t
+calculate_wanted_cache_level(const uint64_t files_in_level_1)
+{
+ uint64_t files_per_directory = files_in_level_1 / 16;
+ for (uint8_t i = k_min_cache_levels; i <= k_max_cache_levels; ++i) {
+ if (files_per_directory < k_max_cache_files_per_directory) {
+ return i;
+ }
+ files_per_directory /= 16;
+ }
+ return k_max_cache_levels;
+}
+
+LocalStorage::LocalStorage(const Config& config) : m_config(config)
+{
+}
+
+void
+LocalStorage::finalize()
+{
+ if (m_config.temporary_dir() == m_config.default_temporary_dir()) {
+ clean_internal_tempdir();
+ }
+
+ if (!m_config.stats()) {
+ return;
+ }
+
+ if (m_manifest_key) {
+ // A manifest entry was written.
+ ASSERT(!m_manifest_path.empty());
+ update_stats_and_maybe_move_cache_file(*m_manifest_key,
+ m_manifest_path,
+ m_manifest_counter_updates,
+ core::CacheEntryType::manifest);
+ }
+
+ if (!m_result_key) {
+ // No result entry was written, so we just choose one of the stats files in
+ // the 256 level 2 directories.
+
+ ASSERT(m_result_counter_updates.get(Statistic::cache_size_kibibyte) == 0);
+ ASSERT(m_result_counter_updates.get(Statistic::files_in_cache) == 0);
+
+ const auto bucket = getpid() % 256;
+ const auto stats_file =
+ FMT("{}/{:x}/{:x}/stats", m_config.cache_dir(), bucket / 16, bucket % 16);
+ StatsFile(stats_file).update([&](auto& cs) {
+ cs.increment(m_result_counter_updates);
+ });
+ return;
+ }
+
+ ASSERT(!m_result_path.empty());
+
+ const auto counters =
+ update_stats_and_maybe_move_cache_file(*m_result_key,
+ m_result_path,
+ m_result_counter_updates,
+ core::CacheEntryType::result);
+ if (!counters) {
+ return;
+ }
+
+ const auto subdir =
+ FMT("{}/{:x}", m_config.cache_dir(), m_result_key->bytes()[0] >> 4);
+ bool need_cleanup = false;
+
+ if (m_config.max_files() != 0
+ && counters->get(Statistic::files_in_cache) > m_config.max_files() / 16) {
+ LOG("Need to clean up {} since it holds {} files (limit: {} files)",
+ subdir,
+ counters->get(Statistic::files_in_cache),
+ m_config.max_files() / 16);
+ need_cleanup = true;
+ }
+ if (m_config.max_size() != 0
+ && counters->get(Statistic::cache_size_kibibyte)
+ > m_config.max_size() / 1024 / 16) {
+ LOG("Need to clean up {} since it holds {} KiB (limit: {} KiB)",
+ subdir,
+ counters->get(Statistic::cache_size_kibibyte),
+ m_config.max_size() / 1024 / 16);
+ need_cleanup = true;
+ }
+
+ if (need_cleanup) {
+ const double factor = m_config.limit_multiple() / 16;
+ const uint64_t max_size = round(m_config.max_size() * factor);
+ const uint32_t max_files = round(m_config.max_files() * factor);
+ clean_dir(subdir,
+ max_size,
+ max_files,
+ std::nullopt,
+ std::nullopt,
+ [](double /*progress*/) {});
+ }
+}
+
+std::optional<util::Bytes>
+LocalStorage::get(const Digest& key, const core::CacheEntryType type)
+{
+ MTR_SCOPE("local_storage", "get");
+
+ std::optional<util::Bytes> return_value;
+
+ const auto cache_file = look_up_cache_file(key, type);
+ if (cache_file.stat) {
+ const auto value = util::read_file<util::Bytes>(cache_file.path);
+ if (value) {
+ LOG("Retrieved {} from local storage ({})",
+ key.to_string(),
+ cache_file.path);
+
+ // Update modification timestamp to save file from LRU cleanup.
+ util::set_timestamps(cache_file.path);
+
+ return_value = *value;
+ } else {
+ LOG("Failed to read {}: {}", cache_file.path, value.error());
+ }
+ } else {
+ LOG("No {} in local storage", key.to_string());
+ }
+
+ increment_statistic(return_value ? core::Statistic::local_storage_read_hit
+ : core::Statistic::local_storage_read_miss);
+ if (type == core::CacheEntryType::result) {
+ increment_statistic(return_value ? core::Statistic::local_storage_hit
+ : core::Statistic::local_storage_miss);
+ }
+
+ return return_value;
+}
+
+void
+LocalStorage::put(const Digest& key,
+ const core::CacheEntryType type,
+ nonstd::span<const uint8_t> value,
+ bool only_if_missing)
+{
+ MTR_SCOPE("local_storage", "put");
+
+ const auto cache_file = look_up_cache_file(key, type);
+ if (only_if_missing && cache_file.stat) {
+ LOG("Not storing {} in local storage since it already exists",
+ cache_file.path);
+ return;
+ }
+
+ switch (type) {
+ case core::CacheEntryType::manifest:
+ m_manifest_key = key;
+ m_manifest_path = cache_file.path;
+ break;
+
+ case core::CacheEntryType::result:
+ m_result_key = key;
+ m_result_path = cache_file.path;
+ break;
+ }
+
+ try {
+ increment_statistic(core::Statistic::local_storage_write);
+ AtomicFile result_file(cache_file.path, AtomicFile::Mode::binary);
+ result_file.write(value);
+ result_file.commit();
+ } catch (core::Error& e) {
+ LOG("Failed to write to {}: {}", cache_file.path, e.what());
+ return;
+ }
+
+ const auto new_stat = Stat::stat(cache_file.path, Stat::OnError::log);
+ if (!new_stat) {
+ LOG("Failed to stat {}: {}", cache_file.path, strerror(errno));
+ return;
+ }
+
+ LOG("Stored {} in local storage ({})", key.to_string(), cache_file.path);
+
+ auto& counter_updates = (type == core::CacheEntryType::manifest)
+ ? m_manifest_counter_updates
+ : m_result_counter_updates;
+ counter_updates.increment(
+ Statistic::cache_size_kibibyte,
+ Util::size_change_kibibyte(cache_file.stat, new_stat));
+ counter_updates.increment(Statistic::files_in_cache, cache_file.stat ? 0 : 1);
+
+ // Make sure we have a CACHEDIR.TAG in the cache part of cache_dir. This can
+ // be done almost anywhere, but we might as well do it near the end as we save
+ // the stat call if we exit early.
+ util::create_cachedir_tag(
+ FMT("{}/{}", m_config.cache_dir(), key.to_string()[0]));
+}
+
+void
+LocalStorage::remove(const Digest& key, const core::CacheEntryType type)
+{
+ MTR_SCOPE("local_storage", "remove");
+
+ const auto cache_file = look_up_cache_file(key, type);
+ if (cache_file.stat) {
+ increment_statistic(core::Statistic::local_storage_write);
+ Util::unlink_safe(cache_file.path);
+ LOG("Removed {} from local storage ({})", key.to_string(), cache_file.path);
+ } else {
+ LOG("No {} to remove from local storage", key.to_string());
+ }
+}
+
+std::string
+LocalStorage::get_raw_file_path(std::string_view result_path,
+ uint8_t file_number)
+{
+ if (file_number >= 10) {
+ // To support more entries in the future, encode to [0-9a-z]. Note that
+ // LocalStorage::evict currently assumes that the entry number is
+ // represented as one character.
+ throw core::Error(FMT("Too high raw file entry number: {}", file_number));
+ }
+
+ const auto prefix = result_path.substr(0, result_path.length() - 1);
+ return FMT("{}{}W", prefix, file_number);
+}
+
+std::string
+LocalStorage::get_raw_file_path(const Digest& result_key,
+ uint8_t file_number) const
+{
+ const auto cache_file =
+ look_up_cache_file(result_key, core::CacheEntryType::result);
+ return get_raw_file_path(cache_file.path, file_number);
+}
+
+void
+LocalStorage::put_raw_files(
+ const Digest& key,
+ const std::vector<core::Result::Serializer::RawFile> raw_files)
+{
+ const auto cache_file = look_up_cache_file(key, core::CacheEntryType::result);
+ Util::ensure_dir_exists(Util::dir_name(cache_file.path));
+
+ for (auto [file_number, source_path] : raw_files) {
+ const auto dest_path = get_raw_file_path(cache_file.path, file_number);
+ const auto old_stat = Stat::stat(dest_path);
+ try {
+ Util::clone_hard_link_or_copy_file(
+ m_config, source_path, dest_path, true);
+ m_added_raw_files.push_back(dest_path);
+ } catch (core::Error& e) {
+ LOG("Failed to store {} as raw file {}: {}",
+ source_path,
+ dest_path,
+ e.what());
+ throw;
+ }
+ const auto new_stat = Stat::stat(dest_path);
+ increment_statistic(Statistic::cache_size_kibibyte,
+ Util::size_change_kibibyte(old_stat, new_stat));
+ increment_statistic(Statistic::files_in_cache,
+ (new_stat ? 1 : 0) - (old_stat ? 1 : 0));
+ }
+}
+
+void
+LocalStorage::increment_statistic(const Statistic statistic,
+ const int64_t value)
+{
+ m_result_counter_updates.increment(statistic, value);
+}
+
+void
+LocalStorage::increment_statistics(const core::StatisticsCounters& statistics)
+{
+ m_result_counter_updates.increment(statistics);
+}
+
+// Private methods
+
+LocalStorage::LookUpCacheFileResult
+LocalStorage::look_up_cache_file(const Digest& key,
+ const core::CacheEntryType type) const
+{
+ const auto key_string = FMT("{}{}", key.to_string(), suffix_from_type(type));
+
+ for (uint8_t level = k_min_cache_levels; level <= k_max_cache_levels;
+ ++level) {
+ const auto path = get_path_in_cache(level, key_string);
+ const auto stat = Stat::stat(path);
+ if (stat) {
+ return {path, stat, level};
+ }
+ }
+
+ const auto shallowest_path =
+ get_path_in_cache(k_min_cache_levels, key_string);
+ return {shallowest_path, Stat(), k_min_cache_levels};
+}
+
+void
+LocalStorage::clean_internal_tempdir()
+{
+ MTR_SCOPE("local_storage", "clean_internal_tempdir");
+
+ const auto now = util::TimePoint::now();
+ const auto cleaned_stamp = FMT("{}/.cleaned", m_config.temporary_dir());
+ const auto cleaned_stat = Stat::stat(cleaned_stamp);
+ if (cleaned_stat
+ && cleaned_stat.mtime() + k_tempdir_cleanup_interval >= now) {
+ // No cleanup needed.
+ return;
+ }
+
+ LOG("Cleaning up {}", m_config.temporary_dir());
+ Util::ensure_dir_exists(m_config.temporary_dir());
+ Util::traverse(m_config.temporary_dir(),
+ [now](const std::string& path, bool is_dir) {
+ if (is_dir) {
+ return;
+ }
+ const auto st = Stat::lstat(path, Stat::OnError::log);
+ if (st && st.mtime() + k_tempdir_cleanup_interval < now) {
+ Util::unlink_tmp(path);
+ }
+ });
+
+ util::write_file(cleaned_stamp, "");
+}
+
+std::optional<core::StatisticsCounters>
+LocalStorage::update_stats_and_maybe_move_cache_file(
+ const Digest& key,
+ const std::string& current_path,
+ const core::StatisticsCounters& counter_updates,
+ const core::CacheEntryType type)
+{
+ if (counter_updates.all_zero()) {
+ return std::nullopt;
+ }
+
+ // Use stats file in the level one subdirectory for cache bookkeeping counters
+ // since cleanup is performed on level one. Use stats file in the level two
+ // subdirectory for other counters to reduce lock contention.
+ 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("{:x}", key.bytes()[0] >> 4);
+ if (!use_stats_on_level_1) {
+ level_string += FMT("/{:x}", key.bytes()[0] & 0xF);
+ }
+
+ const auto stats_file =
+ FMT("{}/{}/stats", m_config.cache_dir(), level_string);
+ auto counters = StatsFile(stats_file).update([&counter_updates](auto& cs) {
+ cs.increment(counter_updates);
+ });
+ if (!counters) {
+ return std::nullopt;
+ }
+
+ if (use_stats_on_level_1) {
+ // Only consider moving the cache file to another level when we have read
+ // the level 1 stats file since it's only then we know the proper
+ // files_in_cache value.
+ const auto wanted_level =
+ calculate_wanted_cache_level(counters->get(Statistic::files_in_cache));
+ const auto wanted_path =
+ get_path_in_cache(wanted_level, key.to_string() + suffix_from_type(type));
+ if (current_path != wanted_path) {
+ Util::ensure_dir_exists(Util::dir_name(wanted_path));
+ LOG("Moving {} to {}", current_path, wanted_path);
+ try {
+ Util::rename(current_path, wanted_path);
+ } catch (const core::Error&) {
+ // Two ccache processes may move the file at the same time, so failure
+ // to rename is OK.
+ }
+ for (const auto& raw_file : m_added_raw_files) {
+ try {
+ Util::rename(raw_file,
+ FMT("{}/{}",
+ Util::dir_name(wanted_path),
+ Util::base_name(raw_file)));
+ } catch (const core::Error&) {
+ // Two ccache processes may move the file at the same time, so failure
+ // to rename is OK.
+ }
+ }
+ }
+ }
+ return counters;
+}
+
+std::string
+LocalStorage::get_path_in_cache(const uint8_t level,
+ const std::string_view name) const
+{
+ ASSERT(level >= 1 && level <= 8);
+ ASSERT(name.length() >= level);
+
+ std::string path(m_config.cache_dir());
+ path.reserve(path.size() + level * 2 + 1 + name.length() - level);
+
+ for (uint8_t i = 0; i < level; ++i) {
+ path.push_back('/');
+ path.push_back(name.at(i));
+ }
+
+ path.push_back('/');
+ const std::string_view name_remaining = name.substr(level);
+ path.append(name_remaining.data(), name_remaining.length());
+
+ return path;
+}
+
+} // namespace storage::local
--- /dev/null
+// Copyright (C) 2021-2022 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 <Digest.hpp>
+#include <core/Result.hpp>
+#include <core/StatisticsCounters.hpp>
+#include <core/types.hpp>
+#include <storage/local/util.hpp>
+#include <storage/types.hpp>
+#include <util/Bytes.hpp>
+#include <util/TimePoint.hpp>
+
+#include <third_party/nonstd/span.hpp>
+
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+class Config;
+
+namespace storage {
+namespace local {
+
+struct CompressionStatistics
+{
+ uint64_t compr_size;
+ uint64_t content_size;
+ uint64_t incompr_size;
+ uint64_t on_disk_size;
+};
+
+class LocalStorage
+{
+public:
+ LocalStorage(const Config& config);
+
+ void finalize();
+
+ // --- Cache entry handling ---
+
+ std::optional<util::Bytes> get(const Digest& key, core::CacheEntryType type);
+
+ void put(const Digest& key,
+ core::CacheEntryType type,
+ nonstd::span<const uint8_t> value,
+ bool only_if_missing = false);
+
+ void remove(const Digest& key, core::CacheEntryType type);
+
+ static std::string get_raw_file_path(std::string_view result_path,
+ uint8_t file_number);
+ std::string get_raw_file_path(const Digest& result_key,
+ uint8_t file_number) const;
+
+ void
+ put_raw_files(const Digest& key,
+ const std::vector<core::Result::Serializer::RawFile> raw_files);
+
+ // --- Statistics ---
+
+ void increment_statistic(core::Statistic statistic, int64_t value = 1);
+ void increment_statistics(const core::StatisticsCounters& statistics);
+
+ const core::StatisticsCounters& get_statistics_updates() const;
+
+ // Zero all statistics counters except those tracking cache size and number of
+ // files in the cache.
+ void zero_all_statistics();
+
+ // Get statistics and last time of update for the whole local storage cache.
+ std::pair<core::StatisticsCounters, util::TimePoint>
+ get_all_statistics() const;
+
+ // --- Cleanup ---
+
+ void evict(const ProgressReceiver& progress_receiver,
+ std::optional<uint64_t> max_age,
+ std::optional<std::string> namespace_);
+
+ void clean_all(const ProgressReceiver& progress_receiver);
+
+ void wipe_all(const ProgressReceiver& progress_receiver);
+
+ // --- Compression ---
+
+ CompressionStatistics
+ get_compression_statistics(const ProgressReceiver& progress_receiver) const;
+
+ void recompress(std::optional<int8_t> level,
+ const ProgressReceiver& progress_receiver);
+
+private:
+ const Config& m_config;
+
+ // Main statistics updates (result statistics and size/count change for result
+ // file) which get written into the statistics file belonging to the result
+ // file.
+ core::StatisticsCounters m_result_counter_updates;
+
+ // Statistics updates (only for manifest size/count change) which get written
+ // into the statistics file belonging to the manifest.
+ core::StatisticsCounters m_manifest_counter_updates;
+
+ // The manifest and result keys and paths are stored by put() so that
+ // finalize() can use them to move the files in place.
+ std::optional<Digest> m_manifest_key;
+ std::optional<Digest> m_result_key;
+ std::string m_manifest_path;
+ std::string m_result_path;
+
+ std::vector<std::string> m_added_raw_files;
+
+ struct LookUpCacheFileResult
+ {
+ std::string path;
+ Stat stat;
+ uint8_t level;
+ };
+
+ LookUpCacheFileResult look_up_cache_file(const Digest& key,
+ core::CacheEntryType type) const;
+
+ void clean_internal_tempdir();
+
+ std::optional<core::StatisticsCounters>
+ update_stats_and_maybe_move_cache_file(
+ const Digest& key,
+ const std::string& current_path,
+ const core::StatisticsCounters& counter_updates,
+ core::CacheEntryType type);
+
+ // Join the cache directory, a '/' and `name` into a single path and return
+ // it. Additionally, `level` single-character, '/'-separated subpaths are
+ // split from the beginning of `name` before joining them all.
+ std::string get_path_in_cache(uint8_t level, std::string_view name) const;
+
+ static void clean_dir(const std::string& subdir,
+ uint64_t max_size,
+ uint64_t max_files,
+ std::optional<uint64_t> max_age,
+ std::optional<std::string> namespace_,
+ const ProgressReceiver& progress_receiver);
+};
+
+// --- Inline implementations ---
+
+inline const core::StatisticsCounters&
+LocalStorage::get_statistics_updates() const
+{
+ return m_result_counter_updates;
+}
+
+} // namespace local
+} // namespace storage
--- /dev/null
+// Copyright (C) 2002-2006 Andrew Tridgell
+// Copyright (C) 2009-2022 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 "LocalStorage.hpp"
+
+#include <Config.hpp>
+#include <Context.hpp>
+#include <File.hpp>
+#include <Logging.hpp>
+#include <TemporaryFile.hpp>
+#include <Util.hpp>
+#include <core/CacheEntry.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <storage/local/CacheFile.hpp>
+#include <storage/local/StatsFile.hpp>
+#include <storage/local/util.hpp>
+#include <util/file.hpp>
+#include <util/string.hpp>
+
+#ifdef INODE_CACHE_SUPPORTED
+# include <InodeCache.hpp>
+#endif
+
+#include <algorithm>
+
+using core::Statistic;
+
+namespace storage::local {
+
+static void
+delete_file(const std::string& path,
+ const uint64_t size,
+ uint64_t* cache_size,
+ uint64_t* files_in_cache)
+{
+ const bool deleted = Util::unlink_safe(path, Util::UnlinkLog::ignore_failure);
+ if (!deleted && errno != ENOENT && errno != ESTALE) {
+ 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
+ // aren't. (This can happen when there are several parallel ongoing
+ // cleanups of the same directory.)
+ *cache_size -= size;
+ --*files_in_cache;
+ }
+}
+
+static void
+update_counters(const std::string& dir,
+ const uint64_t files_in_cache,
+ const uint64_t cache_size,
+ const bool cleanup_performed)
+{
+ const std::string stats_file = dir + "/stats";
+ StatsFile(stats_file).update([=](auto& cs) {
+ if (cleanup_performed) {
+ cs.increment(Statistic::cleanups_performed);
+ }
+ cs.set(Statistic::files_in_cache, files_in_cache);
+ cs.set(Statistic::cache_size_kibibyte, cache_size / 1024);
+ });
+}
+
+void
+LocalStorage::evict(const ProgressReceiver& progress_receiver,
+ std::optional<uint64_t> max_age,
+ std::optional<std::string> namespace_)
+{
+ for_each_level_1_subdir(
+ m_config.cache_dir(),
+ [&](const std::string& subdir,
+ const ProgressReceiver& sub_progress_receiver) {
+ clean_dir(subdir, 0, 0, max_age, namespace_, sub_progress_receiver);
+ },
+ progress_receiver);
+}
+
+// Clean up one cache subdirectory.
+void
+LocalStorage::clean_dir(const std::string& subdir,
+ const uint64_t max_size,
+ const uint64_t max_files,
+ const std::optional<uint64_t> max_age,
+ const std::optional<std::string> namespace_,
+ const ProgressReceiver& progress_receiver)
+{
+ LOG("Cleaning up cache directory {}", subdir);
+
+ std::vector<CacheFile> files = get_level_1_files(
+ subdir, [&](double progress) { progress_receiver(progress / 3); });
+
+ uint64_t cache_size = 0;
+ uint64_t files_in_cache = 0;
+ auto current_time = util::TimePoint::now();
+ std::unordered_map<std::string /*result_file*/,
+ std::vector<std::string> /*associated_raw_files*/>
+ raw_files_map;
+
+ for (size_t i = 0; i < files.size();
+ ++i, progress_receiver(1.0 / 3 + 1.0 * i / files.size() / 3)) {
+ const auto& file = files[i];
+
+ if (!file.lstat().is_regular()) {
+ // Not a file or missing file.
+ continue;
+ }
+
+ // Delete any tmp files older than 1 hour right away.
+ if (file.lstat().mtime() + util::Duration(3600) < current_time
+ && TemporaryFile::is_tmp_file(file.path())) {
+ Util::unlink_tmp(file.path());
+ continue;
+ }
+
+ if (namespace_ && file.type() == CacheFile::Type::raw) {
+ const auto result_filename =
+ FMT("{}R", file.path().substr(0, file.path().length() - 2));
+ raw_files_map[result_filename].push_back(file.path());
+ }
+
+ cache_size += file.lstat().size_on_disk();
+ files_in_cache += 1;
+ }
+
+ // Sort according to modification time, oldest first.
+ std::sort(files.begin(), files.end(), [](const auto& f1, const auto& f2) {
+ return f1.lstat().mtime() < f2.lstat().mtime();
+ });
+
+ LOG("Before cleanup: {:.0f} KiB, {:.0f} files",
+ static_cast<double>(cache_size) / 1024,
+ static_cast<double>(files_in_cache));
+
+ bool cleaned = false;
+ for (size_t i = 0; i < files.size();
+ ++i, progress_receiver(2.0 / 3 + 1.0 * i / files.size() / 3)) {
+ const auto& file = files[i];
+
+ if (!file.lstat() || file.lstat().is_directory()) {
+ continue;
+ }
+
+ if ((max_size == 0 || cache_size <= max_size)
+ && (max_files == 0 || files_in_cache <= max_files)
+ && (!max_age
+ || file.lstat().mtime() > (current_time - util::Duration(*max_age)))
+ && (!namespace_ || max_age)) {
+ break;
+ }
+
+ if (namespace_) {
+ try {
+ core::CacheEntry::Header header(file.path());
+ if (header.namespace_ != *namespace_) {
+ continue;
+ }
+ } catch (core::Error&) {
+ // Failed to read header: ignore.
+ continue;
+ }
+
+ // For namespace eviction we need to remove raw files based on result
+ // filename since they don't have a header.
+ if (file.type() == CacheFile::Type::result) {
+ const auto entry = raw_files_map.find(file.path());
+ if (entry != raw_files_map.end()) {
+ for (const auto& raw_file : entry->second) {
+ delete_file(raw_file,
+ Stat::lstat(raw_file).size_on_disk(),
+ &cache_size,
+ &files_in_cache);
+ }
+ }
+ }
+ }
+
+ if (util::ends_with(file.path(), ".stderr")) {
+ // In order to be nice to legacy ccache versions, make sure that the .o
+ // file is deleted before .stderr, because if the ccache process gets
+ // killed after deleting the .stderr but before deleting the .o, the
+ // cached result will be inconsistent. (.stderr is the only file that is
+ // optional for legacy ccache versions; any other file missing from the
+ // cache will be detected.)
+ std::string o_file = file.path().substr(0, file.path().size() - 6) + "o";
+
+ // Don't subtract this extra deletion from the cache size; that
+ // bookkeeping will be done when the loop reaches the .o file. If the
+ // loop doesn't reach the .o file since the target limits have been
+ // reached, the bookkeeping won't happen, but that small counter
+ // discrepancy won't do much harm and it will correct itself in the next
+ // cleanup.
+ delete_file(o_file, 0, nullptr, nullptr);
+ }
+
+ delete_file(
+ file.path(), file.lstat().size_on_disk(), &cache_size, &files_in_cache);
+ cleaned = true;
+ }
+
+ 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);
+ }
+
+ update_counters(subdir, files_in_cache, cache_size, cleaned);
+}
+
+// Clean up all cache subdirectories.
+void
+LocalStorage::clean_all(const ProgressReceiver& progress_receiver)
+{
+ for_each_level_1_subdir(
+ m_config.cache_dir(),
+ [&](const std::string& subdir,
+ const ProgressReceiver& sub_progress_receiver) {
+ clean_dir(subdir,
+ m_config.max_size() / 16,
+ m_config.max_files() / 16,
+ std::nullopt,
+ std::nullopt,
+ sub_progress_receiver);
+ },
+ progress_receiver);
+}
+
+// Wipe one cache subdirectory.
+static void
+wipe_dir(const std::string& subdir, const ProgressReceiver& progress_receiver)
+{
+ LOG("Clearing out cache directory {}", subdir);
+
+ const std::vector<CacheFile> files = get_level_1_files(
+ subdir, [&](double progress) { progress_receiver(progress / 2); });
+
+ for (size_t i = 0; i < files.size(); ++i) {
+ Util::unlink_safe(files[i].path());
+ progress_receiver(0.5 + 0.5 * i / files.size());
+ }
+
+ const bool cleared = !files.empty();
+ if (cleared) {
+ LOG("Cleared out cache directory {}", subdir);
+ }
+ update_counters(subdir, 0, 0, cleared);
+}
+
+// Wipe all cached files in all subdirectories.
+void
+LocalStorage::wipe_all(const ProgressReceiver& progress_receiver)
+{
+ for_each_level_1_subdir(m_config.cache_dir(), wipe_dir, progress_receiver);
+}
+
+} // namespace storage::local
--- /dev/null
+// Copyright (C) 2019-2022 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 "LocalStorage.hpp"
+
+#include <AtomicFile.hpp>
+#include <Context.hpp>
+#include <File.hpp>
+#include <Logging.hpp>
+#include <TemporaryFile.hpp>
+#include <ThreadPool.hpp>
+#include <assertions.hpp>
+#include <core/CacheEntry.hpp>
+#include <core/Manifest.hpp>
+#include <core/Result.hpp>
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+#include <storage/local/StatsFile.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+#include <util/string.hpp>
+
+#include <third_party/fmt/core.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <memory>
+#include <string>
+#include <thread>
+
+namespace storage::local {
+
+namespace {
+
+class RecompressionStatistics
+{
+public:
+ void update(uint64_t content_size,
+ uint64_t old_size,
+ uint64_t new_size,
+ uint64_t incompressible_size);
+ uint64_t content_size() const;
+ uint64_t old_size() const;
+ uint64_t new_size() const;
+ uint64_t incompressible_size() const;
+
+private:
+ mutable std::mutex m_mutex;
+ uint64_t m_content_size = 0;
+ uint64_t m_old_size = 0;
+ uint64_t m_new_size = 0;
+ uint64_t m_incompressible_size = 0;
+};
+
+void
+RecompressionStatistics::update(const uint64_t content_size,
+ const uint64_t old_size,
+ const uint64_t new_size,
+ const uint64_t incompressible_size)
+{
+ std::unique_lock<std::mutex> lock(m_mutex);
+ m_incompressible_size += incompressible_size;
+ m_content_size += content_size;
+ m_old_size += old_size;
+ m_new_size += new_size;
+}
+
+uint64_t
+RecompressionStatistics::content_size() const
+{
+ std::unique_lock<std::mutex> lock(m_mutex);
+ return m_content_size;
+}
+
+uint64_t
+RecompressionStatistics::old_size() const
+{
+ std::unique_lock<std::mutex> lock(m_mutex);
+ return m_old_size;
+}
+
+uint64_t
+RecompressionStatistics::new_size() const
+{
+ std::unique_lock<std::mutex> lock(m_mutex);
+ return m_new_size;
+}
+
+uint64_t
+RecompressionStatistics::incompressible_size() const
+{
+ std::unique_lock<std::mutex> lock(m_mutex);
+ return m_incompressible_size;
+}
+
+} // namespace
+
+static void
+recompress_file(RecompressionStatistics& statistics,
+ const std::string& stats_file,
+ const CacheFile& cache_file,
+ const std::optional<int8_t> level)
+{
+ core::CacheEntry::Header header(cache_file.path());
+
+ const int8_t wanted_level =
+ level ? (*level == 0 ? core::CacheEntry::default_compression_level : *level)
+ : 0;
+ const auto old_stat = Stat::stat(cache_file.path(), Stat::OnError::log);
+
+ if (header.compression_level == wanted_level) {
+ statistics.update(header.entry_size, old_stat.size(), old_stat.size(), 0);
+ return;
+ }
+
+ const auto cache_file_data = util::value_or_throw<core::Error>(
+ util::read_file<util::Bytes>(cache_file.path()),
+ FMT("Failed to read {}: ", cache_file.path()));
+ core::CacheEntry cache_entry(cache_file_data);
+ cache_entry.verify_checksum();
+
+ header.entry_format_version = core::CacheEntry::k_format_version;
+ header.compression_type =
+ level ? core::CompressionType::zstd : core::CompressionType::none;
+ header.compression_level = wanted_level;
+
+ AtomicFile new_cache_file(cache_file.path(), AtomicFile::Mode::binary);
+ new_cache_file.write(
+ core::CacheEntry::serialize(header, cache_entry.payload()));
+ new_cache_file.commit();
+
+ // Restore mtime/atime to keep cache LRU cleanup working as expected:
+ util::set_timestamps(cache_file.path(), old_stat.mtime(), old_stat.atime());
+
+ const auto new_stat = Stat::stat(cache_file.path(), Stat::OnError::log);
+ StatsFile(stats_file).update([=](auto& cs) {
+ cs.increment(core::Statistic::cache_size_kibibyte,
+ Util::size_change_kibibyte(old_stat, new_stat));
+ });
+ statistics.update(header.entry_size, old_stat.size(), new_stat.size(), 0);
+}
+
+CompressionStatistics
+LocalStorage::get_compression_statistics(
+ const ProgressReceiver& progress_receiver) const
+{
+ CompressionStatistics cs{};
+
+ for_each_level_1_subdir(
+ m_config.cache_dir(),
+ [&](const auto& subdir, const auto& sub_progress_receiver) {
+ const std::vector<CacheFile> files = get_level_1_files(
+ subdir, [&](double progress) { sub_progress_receiver(progress / 2); });
+
+ for (size_t i = 0; i < files.size(); ++i) {
+ const auto& cache_file = files[i];
+ cs.on_disk_size += cache_file.lstat().size_on_disk();
+
+ try {
+ core::CacheEntry::Header header(cache_file.path());
+ cs.compr_size += cache_file.lstat().size();
+ cs.content_size += header.entry_size;
+ } catch (core::Error&) {
+ cs.incompr_size += cache_file.lstat().size();
+ }
+
+ sub_progress_receiver(1.0 / 2 + 1.0 * i / files.size() / 2);
+ }
+ },
+ progress_receiver);
+
+ return cs;
+}
+
+void
+LocalStorage::recompress(const std::optional<int8_t> level,
+ const ProgressReceiver& progress_receiver)
+{
+ const size_t threads = std::thread::hardware_concurrency();
+ const size_t read_ahead = 2 * threads;
+ ThreadPool thread_pool(threads, read_ahead);
+ RecompressionStatistics statistics;
+
+ for_each_level_1_subdir(
+ m_config.cache_dir(),
+ [&](const auto& subdir, const auto& sub_progress_receiver) {
+ std::vector<CacheFile> files =
+ get_level_1_files(subdir, [&](double progress) {
+ sub_progress_receiver(0.1 * progress);
+ });
+
+ auto stats_file = subdir + "/stats";
+
+ for (size_t i = 0; i < files.size(); ++i) {
+ const auto& file = files[i];
+
+ if (file.type() != CacheFile::Type::unknown) {
+ thread_pool.enqueue([&statistics, stats_file, file, level] {
+ try {
+ recompress_file(statistics, stats_file, file, level);
+ } catch (core::Error&) {
+ // Ignore for now.
+ }
+ });
+ } else if (!TemporaryFile::is_tmp_file(file.path())) {
+ statistics.update(0, 0, 0, file.lstat().size());
+ }
+
+ sub_progress_receiver(0.1 + 0.9 * i / files.size());
+ }
+
+ if (util::ends_with(subdir, "f")) {
+ // Wait here instead of after for_each_level_1_subdir to avoid
+ // updating the progress bar to 100% before all work is done.
+ thread_pool.shut_down();
+ }
+ },
+ progress_receiver);
+
+ // In case there was no f subdir, shut down the thread pool now.
+ thread_pool.shut_down();
+
+ if (isatty(STDOUT_FILENO)) {
+ PRINT_RAW(stdout, "\n\n");
+ }
+
+ const double old_ratio =
+ statistics.old_size() > 0
+ ? static_cast<double>(statistics.content_size()) / statistics.old_size()
+ : 0.0;
+ const double old_savings =
+ old_ratio > 0.0 ? 100.0 - (100.0 / old_ratio) : 0.0;
+ const double new_ratio =
+ statistics.new_size() > 0
+ ? static_cast<double>(statistics.content_size()) / statistics.new_size()
+ : 0.0;
+ const double new_savings =
+ new_ratio > 0.0 ? 100.0 - (100.0 / new_ratio) : 0.0;
+ const int64_t size_difference = static_cast<int64_t>(statistics.new_size())
+ - static_cast<int64_t>(statistics.old_size());
+
+ const std::string old_compr_size_str =
+ Util::format_human_readable_size(statistics.old_size());
+ const std::string new_compr_size_str =
+ Util::format_human_readable_size(statistics.new_size());
+ const std::string content_size_str =
+ Util::format_human_readable_size(statistics.content_size());
+ const std::string incompr_size_str =
+ Util::format_human_readable_size(statistics.incompressible_size());
+ const std::string 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);
+}
+
+} // namespace storage::local
--- /dev/null
+// Copyright (C) 2021-2022 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 "LocalStorage.hpp"
+
+#include <Config.hpp>
+#include <core/Statistics.hpp>
+#include <fmtmacros.hpp>
+#include <storage/local/StatsFile.hpp>
+
+#include <algorithm>
+
+namespace storage::local {
+
+static void
+for_each_level_1_and_2_stats_file(
+ const std::string& cache_dir,
+ const std::function<void(const std::string& path)> function)
+{
+ for (size_t level_1 = 0; level_1 <= 0xF; ++level_1) {
+ function(FMT("{}/{:x}/stats", cache_dir, level_1));
+ for (size_t level_2 = 0; level_2 <= 0xF; ++level_2) {
+ function(FMT("{}/{:x}/{:x}/stats", cache_dir, level_1, level_2));
+ }
+ }
+}
+
+// Zero all statistics counters except those tracking cache size and number of
+// files in the cache.
+void
+LocalStorage::zero_all_statistics()
+{
+ const auto now = util::TimePoint::now();
+ const auto zeroable_fields = core::Statistics::get_zeroable_fields();
+
+ for_each_level_1_and_2_stats_file(
+ m_config.cache_dir(), [=](const std::string& path) {
+ StatsFile(path).update([=](auto& cs) {
+ for (const auto statistic : zeroable_fields) {
+ cs.set(statistic, 0);
+ }
+ cs.set(core::Statistic::stats_zeroed_timestamp, now.sec());
+ });
+ });
+}
+
+// Get statistics and last time of update for the whole local storage cache.
+std::pair<core::StatisticsCounters, util::TimePoint>
+LocalStorage::get_all_statistics() const
+{
+ core::StatisticsCounters counters;
+ uint64_t zero_timestamp = 0;
+ util::TimePoint last_updated;
+
+ // Add up the stats in each directory.
+ for_each_level_1_and_2_stats_file(
+ m_config.cache_dir(), [&](const auto& path) {
+ counters.set(core::Statistic::stats_zeroed_timestamp, 0); // Don't add
+ counters.increment(StatsFile(path).read());
+ zero_timestamp = std::max(
+ counters.get(core::Statistic::stats_zeroed_timestamp), zero_timestamp);
+ last_updated = std::max(last_updated, Stat::stat(path).mtime());
+ });
+
+ counters.set(core::Statistic::stats_zeroed_timestamp, zero_timestamp);
+ return std::make_pair(counters, last_updated);
+}
+
+} // namespace storage::local
--- /dev/null
+// Copyright (C) 2021-2022 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 "StatsFile.hpp"
+
+#include <AtomicFile.hpp>
+#include <Logging.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <util/LockFile.hpp>
+#include <util/file.hpp>
+
+namespace storage::local {
+
+StatsFile::StatsFile(const std::string& path) : m_path(path)
+{
+}
+
+core::StatisticsCounters
+StatsFile::read() const
+{
+ core::StatisticsCounters counters;
+
+ const auto data = util::read_file<std::string>(m_path);
+ if (!data) {
+ // A nonexistent stats file is OK.
+ return counters;
+ }
+
+ size_t i = 0;
+ const char* str = data->c_str();
+ while (true) {
+ char* end;
+ const uint64_t value = std::strtoull(str, &end, 10);
+ if (end == str) {
+ break;
+ }
+ counters.set_raw(i, value);
+ ++i;
+ str = end;
+ }
+
+ return counters;
+}
+
+std::optional<core::StatisticsCounters>
+StatsFile::update(
+ std::function<void(core::StatisticsCounters& counters)> function) const
+{
+ util::ShortLivedLockFile lock_file(m_path);
+ util::LockFileGuard lock(lock_file);
+ if (!lock.acquired()) {
+ LOG("Failed to acquire lock for {}", m_path);
+ return std::nullopt;
+ }
+
+ auto counters = read();
+ function(counters);
+
+ AtomicFile file(m_path, AtomicFile::Mode::text);
+ for (size_t i = 0; i < counters.size(); ++i) {
+ file.write(FMT("{}\n", counters.get_raw(i)));
+ }
+ try {
+ file.commit();
+ } catch (const core::Error& e) {
+ // 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());
+ }
+
+ return counters;
+}
+
+} // namespace storage::local
--- /dev/null
+// Copyright (C) 2021-2022 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 <core/StatisticsCounters.hpp>
+
+#include <functional>
+#include <optional>
+#include <string>
+
+namespace storage::local {
+
+class StatsFile
+{
+public:
+ StatsFile(const std::string& path);
+
+ // Read counters. No lock is acquired. If the file doesn't exist all returned
+ // counters will be zero.
+ core::StatisticsCounters read() const;
+
+ // Acquire a lock, read counters, call `function` with the counters, write the
+ // counters and release the lock. Returns the resulting counters or nullopt on
+ // error (e.g. if the lock could not be acquired).
+ std::optional<core::StatisticsCounters>
+ update(std::function<void(core::StatisticsCounters& counters)>) const;
+
+private:
+ const std::string m_path;
+};
+
+} // namespace storage::local
--- /dev/null
+// Copyright (C) 2021-2022 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 "util.hpp"
+
+#include <Util.hpp>
+#include <fmtmacros.hpp>
+#include <util/string.hpp>
+
+namespace storage::local {
+
+void
+for_each_level_1_subdir(const std::string& cache_dir,
+ const SubdirVisitor& visitor,
+ const ProgressReceiver& progress_receiver)
+{
+ for (int i = 0; i <= 0xF; i++) {
+ double progress = 1.0 * i / 16;
+ progress_receiver(progress);
+ std::string subdir_path = FMT("{}/{:x}", cache_dir, i);
+ visitor(subdir_path, [&](double inner_progress) {
+ progress_receiver(progress + inner_progress / 16);
+ });
+ }
+ progress_receiver(1.0);
+}
+
+std::vector<CacheFile>
+get_level_1_files(const std::string& dir,
+ const ProgressReceiver& progress_receiver)
+{
+ std::vector<CacheFile> files;
+
+ if (!Stat::stat(dir)) {
+ return files;
+ }
+
+ size_t level_2_directories = 0;
+
+ Util::traverse(dir, [&](const std::string& path, bool is_dir) {
+ auto name = Util::base_name(path);
+ if (name == "CACHEDIR.TAG" || name == "stats"
+ || util::starts_with(name, ".nfs")) {
+ return;
+ }
+
+ if (!is_dir) {
+ files.emplace_back(path);
+ } else if (path != dir
+ && path.find('/', dir.size() + 1) == std::string::npos) {
+ ++level_2_directories;
+ progress_receiver(level_2_directories / 16.0);
+ }
+ });
+
+ progress_receiver(1.0);
+ return files;
+}
+
+} // namespace storage::local
--- /dev/null
+// Copyright (C) 2021-2022 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 <storage/local/CacheFile.hpp>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace storage::local {
+
+using ProgressReceiver = std::function<void(double progress)>;
+using SubdirVisitor = std::function<void(
+ const std::string& dir_path, const ProgressReceiver& progress_receiver)>;
+
+// Call a function for each subdir (0-9a-f) in the cache.
+//
+// Parameters:
+// - cache_dir: Path to the cache directory.
+// - visitor: Function to call with directory path and progress_receiver as
+// arguments.
+// - progress_receiver: Function that will be called for progress updates.
+void for_each_level_1_subdir(const std::string& cache_dir,
+ const SubdirVisitor& visitor,
+ const ProgressReceiver& progress_receiver);
+
+// Get a list of files in a level 1 subdirectory of the cache.
+//
+// The function works under the assumption that directory entries with one
+// character names (except ".") are subdirectories and that there are no other
+// subdirectories.
+//
+// Files ignored:
+// - CACHEDIR.TAG
+// - stats
+// - .nfs* (temporary NFS files that may be left for open but deleted files).
+//
+// Parameters:
+// - dir: The directory to traverse recursively.
+// - progress_receiver: Function that will be called for progress updates.
+std::vector<CacheFile>
+get_level_1_files(const std::string& dir,
+ const ProgressReceiver& progress_receiver);
+
+} // namespace storage::local
--- /dev/null
+set(
+ sources
+ FileStorage.cpp
+ HttpStorage.cpp
+ RemoteStorage.cpp
+)
+
+if(REDIS_STORAGE_BACKEND)
+ list(APPEND sources RedisStorage.cpp)
+endif()
+
+file(GLOB headers *.hpp)
+list(APPEND sources ${headers})
+
+target_sources(ccache_framework PRIVATE ${sources})
--- /dev/null
+// Copyright (C) 2021-2022 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 "FileStorage.hpp"
+
+#include <AtomicFile.hpp>
+#include <Digest.hpp>
+#include <Logging.hpp>
+#include <UmaskScope.hpp>
+#include <Util.hpp>
+#include <assertions.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <util/Bytes.hpp>
+#include <util/expected.hpp>
+#include <util/file.hpp>
+#include <util/string.hpp>
+
+#include <sys/stat.h> // for mode_t
+
+#include <string_view>
+
+namespace storage::remote {
+
+namespace {
+
+class FileStorageBackend : public RemoteStorage::Backend
+{
+public:
+ FileStorageBackend(const Params& params);
+
+ nonstd::expected<std::optional<util::Bytes>, Failure>
+ get(const Digest& key) override;
+
+ nonstd::expected<bool, Failure> put(const Digest& key,
+ nonstd::span<const uint8_t> value,
+ bool only_if_missing) override;
+
+ nonstd::expected<bool, Failure> remove(const Digest& key) override;
+
+private:
+ enum class Layout { flat, subdirs };
+
+ std::string m_dir;
+ std::optional<mode_t> m_umask;
+ bool m_update_mtime = false;
+ Layout m_layout = Layout::subdirs;
+
+ std::string get_entry_path(const Digest& key) const;
+};
+
+FileStorageBackend::FileStorageBackend(const Params& params)
+{
+ ASSERT(params.url.scheme() == "file");
+
+ const auto& host = params.url.host();
+#ifdef _WIN32
+ m_dir = util::replace_all(params.url.path(), "/", "\\");
+ if (!host.empty()) {
+ m_dir = FMT("\\\\{}{}", host, m_dir);
+ }
+#else
+ if (!host.empty() && host != "localhost") {
+ throw core::Fatal(
+ FMT("invalid file URL \"{}\": specifying a host other than localhost is"
+ " not supported",
+ params.url.str()));
+ }
+ m_dir = params.url.path();
+#endif
+
+ for (const auto& attr : params.attributes) {
+ if (attr.key == "layout") {
+ if (attr.value == "flat") {
+ m_layout = Layout::flat;
+ } else if (attr.value == "subdirs") {
+ m_layout = Layout::subdirs;
+ } else {
+ LOG("Unknown layout: {}", attr.value);
+ }
+ } else if (attr.key == "umask") {
+ m_umask =
+ util::value_or_throw<core::Fatal>(util::parse_umask(attr.value));
+ } else if (attr.key == "update-mtime") {
+ m_update_mtime = attr.value == "true";
+ } else if (!is_framework_attribute(attr.key)) {
+ LOG("Unknown attribute: {}", attr.key);
+ }
+ }
+}
+
+nonstd::expected<std::optional<util::Bytes>, RemoteStorage::Backend::Failure>
+FileStorageBackend::get(const Digest& key)
+{
+ const auto path = get_entry_path(key);
+ const bool exists = Stat::stat(path);
+
+ if (!exists) {
+ // Don't log failure if the entry doesn't exist.
+ return std::nullopt;
+ }
+
+ if (m_update_mtime) {
+ // Update modification timestamp for potential LRU cleanup by some external
+ // mechanism.
+ util::set_timestamps(path);
+ }
+
+ auto value = util::read_file<util::Bytes>(path);
+ if (!value) {
+ LOG("Failed to read {}: {}", path, value.error());
+ return nonstd::make_unexpected(Failure::error);
+ }
+ return std::move(*value);
+}
+
+nonstd::expected<bool, RemoteStorage::Backend::Failure>
+FileStorageBackend::put(const Digest& key,
+ const nonstd::span<const uint8_t> value,
+ const bool only_if_missing)
+{
+ const auto path = get_entry_path(key);
+
+ if (only_if_missing && Stat::stat(path)) {
+ LOG("{} already in cache", path);
+ return false;
+ }
+
+ {
+ UmaskScope umask_scope(m_umask);
+
+ const auto dir = Util::dir_name(path);
+ if (!Util::create_dir(dir)) {
+ LOG("Failed to create directory {}: {}", dir, strerror(errno));
+ return nonstd::make_unexpected(Failure::error);
+ }
+
+ util::create_cachedir_tag(m_dir);
+
+ LOG("Writing {}", path);
+ try {
+ AtomicFile file(path, AtomicFile::Mode::binary);
+ file.write(value);
+ file.commit();
+ return true;
+ } catch (const core::Error& e) {
+ LOG("Failed to write {}: {}", path, e.what());
+ return nonstd::make_unexpected(Failure::error);
+ }
+ }
+}
+
+nonstd::expected<bool, RemoteStorage::Backend::Failure>
+FileStorageBackend::remove(const Digest& key)
+{
+ return Util::unlink_safe(get_entry_path(key));
+}
+
+std::string
+FileStorageBackend::get_entry_path(const Digest& key) const
+{
+ switch (m_layout) {
+ case Layout::flat:
+ return FMT("{}/{}", m_dir, key.to_string());
+
+ case Layout::subdirs: {
+ const auto key_str = key.to_string();
+ const uint8_t digits = 2;
+ ASSERT(key_str.length() > digits);
+ return FMT("{}/{:.{}}/{}", m_dir, key_str, digits, &key_str[digits]);
+ }
+ }
+
+ ASSERT(false);
+}
+
+} // namespace
+
+std::unique_ptr<RemoteStorage::Backend>
+FileStorage::create_backend(const Backend::Params& params) const
+{
+ return std::make_unique<FileStorageBackend>(params);
+}
+
+} // namespace storage::remote
--- /dev/null
+// Copyright (C) 2021-2022 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 <storage/remote/RemoteStorage.hpp>
+
+namespace storage::remote {
+
+class FileStorage : public RemoteStorage
+{
+public:
+ std::unique_ptr<Backend>
+ create_backend(const Backend::Params& params) const override;
+};
+
+} // namespace storage::remote
--- /dev/null
+// Copyright (C) 2021-2022 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 "HttpStorage.hpp"
+
+#include <Digest.hpp>
+#include <Logging.hpp>
+#include <ccache.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <util/expected.hpp>
+#include <util/string.hpp>
+#include <util/types.hpp>
+
+#include <third_party/httplib.h>
+#include <third_party/url.hpp>
+
+#include <string_view>
+
+namespace storage::remote {
+
+namespace {
+
+class HttpStorageBackend : public RemoteStorage::Backend
+{
+public:
+ HttpStorageBackend(const Params& params);
+
+ nonstd::expected<std::optional<util::Bytes>, Failure>
+ get(const Digest& key) override;
+
+ nonstd::expected<bool, Failure> put(const Digest& key,
+ nonstd::span<const uint8_t> value,
+ bool only_if_missing) override;
+
+ nonstd::expected<bool, Failure> remove(const Digest& key) override;
+
+private:
+ enum class Layout { bazel, flat, subdirs };
+
+ const std::string m_url_path;
+ httplib::Client m_http_client;
+ Layout m_layout = Layout::subdirs;
+
+ std::string get_entry_path(const Digest& key) const;
+};
+
+std::string
+get_url_path(const Url& url)
+{
+ auto path = url.path();
+ if (path.empty() || path.back() != '/') {
+ path += '/';
+ }
+ return path;
+}
+
+Url
+get_partial_url(const Url& from_url)
+{
+ Url url;
+ url.scheme(from_url.scheme());
+ url.host(from_url.host(), from_url.ip_version());
+ if (!from_url.port().empty()) {
+ url.port(from_url.port());
+ }
+ return url;
+}
+
+std::string
+get_url(const Url& url)
+{
+ if (url.host().empty()) {
+ throw core::Fatal(
+ FMT("A host is required in HTTP storage URL \"{}\"", url.str()));
+ }
+
+ // httplib requires a partial URL with just scheme, host and port.
+ return get_partial_url(url).str();
+}
+
+RemoteStorage::Backend::Failure
+failure_from_httplib_error(httplib::Error error)
+{
+ return error == httplib::Error::ConnectionTimeout
+ ? RemoteStorage::Backend::Failure::timeout
+ : RemoteStorage::Backend::Failure::error;
+}
+
+HttpStorageBackend::HttpStorageBackend(const Params& params)
+ : m_url_path(get_url_path(params.url)),
+ m_http_client(get_url(params.url))
+{
+ if (!params.url.user_info().empty()) {
+ const auto [user, password] = util::split_once(params.url.user_info(), ':');
+ if (!password) {
+ throw core::Fatal(FMT("Expected username:password in URL but got \"{}\"",
+ params.url.user_info()));
+ }
+ m_http_client.set_basic_auth(std::string(user), std::string(*password));
+ }
+
+ m_http_client.set_default_headers({
+ {"User-Agent", FMT("ccache/{}", CCACHE_VERSION)},
+ });
+ m_http_client.set_keep_alive(true);
+
+ auto connect_timeout = k_default_connect_timeout;
+ auto operation_timeout = k_default_operation_timeout;
+
+ for (const auto& attr : params.attributes) {
+ if (attr.key == "bearer-token") {
+ m_http_client.set_bearer_token_auth(attr.value);
+ } else if (attr.key == "connect-timeout") {
+ connect_timeout = parse_timeout_attribute(attr.value);
+ } else if (attr.key == "keep-alive") {
+ m_http_client.set_keep_alive(attr.value == "true");
+ } else if (attr.key == "layout") {
+ if (attr.value == "bazel") {
+ m_layout = Layout::bazel;
+ } else if (attr.value == "flat") {
+ m_layout = Layout::flat;
+ } else if (attr.value == "subdirs") {
+ m_layout = Layout::subdirs;
+ } else {
+ LOG("Unknown layout: {}", attr.value);
+ }
+ } else if (attr.key == "operation-timeout") {
+ operation_timeout = parse_timeout_attribute(attr.value);
+ } else if (!is_framework_attribute(attr.key)) {
+ LOG("Unknown attribute: {}", attr.key);
+ }
+ }
+
+ m_http_client.set_connection_timeout(connect_timeout);
+ m_http_client.set_read_timeout(operation_timeout);
+ m_http_client.set_write_timeout(operation_timeout);
+}
+
+nonstd::expected<std::optional<util::Bytes>, RemoteStorage::Backend::Failure>
+HttpStorageBackend::get(const Digest& key)
+{
+ const auto url_path = get_entry_path(key);
+ const auto result = m_http_client.Get(url_path);
+
+ if (result.error() != httplib::Error::Success || !result) {
+ LOG("Failed to get {} from http storage: {} ({})",
+ url_path,
+ to_string(result.error()),
+ static_cast<int>(result.error()));
+ return nonstd::make_unexpected(failure_from_httplib_error(result.error()));
+ }
+
+ if (result->status < 200 || result->status >= 300) {
+ // Don't log failure if the entry doesn't exist.
+ return std::nullopt;
+ }
+
+ return util::Bytes(result->body.data(), result->body.size());
+}
+
+nonstd::expected<bool, RemoteStorage::Backend::Failure>
+HttpStorageBackend::put(const Digest& key,
+ const nonstd::span<const uint8_t> value,
+ const bool only_if_missing)
+{
+ const auto url_path = get_entry_path(key);
+
+ if (only_if_missing) {
+ const auto result = m_http_client.Head(url_path);
+
+ if (result.error() != httplib::Error::Success || !result) {
+ LOG("Failed to check for {} in http storage: {} ({})",
+ url_path,
+ to_string(result.error()),
+ static_cast<int>(result.error()));
+ return nonstd::make_unexpected(
+ failure_from_httplib_error(result.error()));
+ }
+
+ if (result->status >= 200 && result->status < 300) {
+ LOG("Found entry {} already within http storage: status code: {}",
+ url_path,
+ result->status);
+ return false;
+ }
+ }
+
+ static const auto content_type = "application/octet-stream";
+ const auto result =
+ m_http_client.Put(url_path,
+ reinterpret_cast<const char*>(value.data()),
+ value.size(),
+ content_type);
+
+ if (result.error() != httplib::Error::Success || !result) {
+ LOG("Failed to put {} to http storage: {} ({})",
+ url_path,
+ to_string(result.error()),
+ static_cast<int>(result.error()));
+ return nonstd::make_unexpected(failure_from_httplib_error(result.error()));
+ }
+
+ if (result->status < 200 || result->status >= 300) {
+ LOG("Failed to put {} to http storage: status code: {}",
+ url_path,
+ result->status);
+ return nonstd::make_unexpected(failure_from_httplib_error(result.error()));
+ }
+
+ return true;
+}
+
+nonstd::expected<bool, RemoteStorage::Backend::Failure>
+HttpStorageBackend::remove(const Digest& key)
+{
+ const auto url_path = get_entry_path(key);
+ const auto result = m_http_client.Delete(url_path);
+
+ if (result.error() != httplib::Error::Success || !result) {
+ LOG("Failed to delete {} from http storage: {} ({})",
+ url_path,
+ to_string(result.error()),
+ static_cast<int>(result.error()));
+ return nonstd::make_unexpected(failure_from_httplib_error(result.error()));
+ }
+
+ if (result->status < 200 || result->status >= 300) {
+ LOG("Failed to delete {} from http storage: status code: {}",
+ url_path,
+ result->status);
+ return nonstd::make_unexpected(failure_from_httplib_error(result.error()));
+ }
+
+ return true;
+}
+
+std::string
+HttpStorageBackend::get_entry_path(const Digest& key) const
+{
+ switch (m_layout) {
+ case Layout::bazel: {
+ // Mimic hex representation of a SHA256 hash value.
+ const auto sha256_hex_size = 64;
+ static_assert(Digest::size() == 20, "Update below if digest size changes");
+ std::string hex_digits = Util::format_base16(key.bytes(), key.size());
+ hex_digits.append(hex_digits.data(), sha256_hex_size - hex_digits.size());
+ LOG("Translated key {} to Bazel layout ac/{}", key.to_string(), hex_digits);
+ return FMT("{}ac/{}", m_url_path, hex_digits);
+ }
+
+ case Layout::flat:
+ return m_url_path + key.to_string();
+
+ case Layout::subdirs: {
+ const auto key_str = key.to_string();
+ const uint8_t digits = 2;
+ ASSERT(key_str.length() > digits);
+ return FMT("{}{:.{}}/{}", m_url_path, key_str, digits, &key_str[digits]);
+ }
+ }
+
+ ASSERT(false);
+}
+
+} // namespace
+
+std::unique_ptr<RemoteStorage::Backend>
+HttpStorage::create_backend(const Backend::Params& params) const
+{
+ return std::make_unique<HttpStorageBackend>(params);
+}
+
+void
+HttpStorage::redact_secrets(Backend::Params& params) const
+{
+ auto& url = params.url;
+ const auto [user, password] = util::split_once(url.user_info(), ':');
+ if (password) {
+ url.user_info(FMT("{}:{}", user, k_redacted_password));
+ }
+
+ auto bearer_token_attribute =
+ std::find_if(params.attributes.begin(),
+ params.attributes.end(),
+ [&](const auto& attr) { return attr.key == "bearer-token"; });
+ if (bearer_token_attribute != params.attributes.end()) {
+ bearer_token_attribute->value = k_redacted_password;
+ bearer_token_attribute->raw_value = k_redacted_password;
+ }
+}
+
+} // namespace storage::remote
--- /dev/null
+// Copyright (C) 2021-2022 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 <storage/remote/RemoteStorage.hpp>
+
+namespace storage::remote {
+
+class HttpStorage : public RemoteStorage
+{
+public:
+ std::unique_ptr<Backend>
+ create_backend(const Backend::Params& params) const override;
+
+ void redact_secrets(Backend::Params& params) const override;
+};
+
+} // namespace storage::remote
--- /dev/null
+// Copyright (C) 2021-2022 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 "RedisStorage.hpp"
+
+#include <Digest.hpp>
+#include <Logging.hpp>
+#include <core/exceptions.hpp>
+#include <fmtmacros.hpp>
+#include <util/expected.hpp>
+#include <util/string.hpp>
+
+// Ignore "ISO C++ forbids flexible array member ‘buf’" warning from -Wpedantic.
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable : 4200)
+#endif
+#include <hiredis/hiredis.h>
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
+#include <cstdarg>
+#include <map>
+#include <memory>
+
+namespace storage::remote {
+
+namespace {
+
+using RedisContext = std::unique_ptr<redisContext, decltype(&redisFree)>;
+using RedisReply = std::unique_ptr<redisReply, decltype(&freeReplyObject)>;
+
+const uint32_t DEFAULT_PORT = 6379;
+
+class RedisStorageBackend : public RemoteStorage::Backend
+{
+public:
+ RedisStorageBackend(const RemoteStorage::Backend::Params& params);
+
+ nonstd::expected<std::optional<util::Bytes>, Failure>
+ get(const Digest& key) override;
+
+ nonstd::expected<bool, Failure> put(const Digest& key,
+ nonstd::span<const uint8_t> value,
+ bool only_if_missing) override;
+
+ nonstd::expected<bool, Failure> remove(const Digest& key) override;
+
+private:
+ const std::string m_prefix;
+ RedisContext m_context;
+
+ void
+ connect(const Url& url, uint32_t connect_timeout, uint32_t operation_timeout);
+ void select_database(const Url& url);
+ void authenticate(const Url& url);
+ nonstd::expected<RedisReply, Failure> redis_command(const char* format, ...);
+ std::string get_key_string(const Digest& digest) const;
+};
+
+timeval
+to_timeval(const uint32_t ms)
+{
+ timeval tv;
+ tv.tv_sec = ms / 1000;
+ tv.tv_usec = (ms % 1000) * 1000;
+ return tv;
+}
+
+std::pair<std::optional<std::string>, std::optional<std::string>>
+split_user_info(const std::string& user_info)
+{
+ const auto [left, right] = util::split_once(user_info, ':');
+ if (left.empty()) {
+ // redis://HOST
+ return {std::nullopt, std::nullopt};
+ } else if (right) {
+ // redis://USERNAME:PASSWORD@HOST
+ return {std::string(left), std::string(*right)};
+ } else {
+ // redis://PASSWORD@HOST
+ return {std::nullopt, std::string(left)};
+ }
+}
+
+RedisStorageBackend::RedisStorageBackend(const Params& params)
+ : m_prefix("ccache"), // TODO: attribute
+ m_context(nullptr, redisFree)
+{
+ const auto& url = params.url;
+ ASSERT(url.scheme() == "redis" || url.scheme() == "redis+unix");
+ if (url.scheme() == "redis+unix" && !params.url.host().empty()
+ && params.url.host() != "localhost") {
+ throw core::Fatal(
+ FMT("invalid file path \"{}\": specifying a host other than localhost is"
+ " not supported",
+ params.url.str(),
+ params.url.host()));
+ }
+
+ auto connect_timeout = k_default_connect_timeout;
+ auto operation_timeout = k_default_operation_timeout;
+
+ for (const auto& attr : params.attributes) {
+ if (attr.key == "connect-timeout") {
+ connect_timeout = parse_timeout_attribute(attr.value);
+ } else if (attr.key == "operation-timeout") {
+ operation_timeout = parse_timeout_attribute(attr.value);
+ } else if (!is_framework_attribute(attr.key)) {
+ LOG("Unknown attribute: {}", attr.key);
+ }
+ }
+
+ connect(url, connect_timeout.count(), operation_timeout.count());
+ authenticate(url);
+ select_database(url);
+}
+
+inline bool
+is_error(int err)
+{
+ return err != REDIS_OK;
+}
+
+inline bool
+is_timeout(int err)
+{
+#ifdef REDIS_ERR_TIMEOUT
+ // Only returned for hiredis version 1.0.0 and above
+ return err == REDIS_ERR_TIMEOUT;
+#else
+ (void)err;
+ return false;
+#endif
+}
+
+nonstd::expected<std::optional<util::Bytes>, RemoteStorage::Backend::Failure>
+RedisStorageBackend::get(const Digest& key)
+{
+ const auto key_string = get_key_string(key);
+ LOG("Redis GET {}", key_string);
+ const auto reply = redis_command("GET %s", key_string.c_str());
+ if (!reply) {
+ return nonstd::make_unexpected(reply.error());
+ } else if ((*reply)->type == REDIS_REPLY_STRING) {
+ return util::Bytes((*reply)->str, (*reply)->len);
+ } else if ((*reply)->type == REDIS_REPLY_NIL) {
+ return std::nullopt;
+ } else {
+ LOG("Unknown reply type: {}", (*reply)->type);
+ return nonstd::make_unexpected(Failure::error);
+ }
+}
+
+nonstd::expected<bool, RemoteStorage::Backend::Failure>
+RedisStorageBackend::put(const Digest& key,
+ nonstd::span<const uint8_t> value,
+ bool only_if_missing)
+{
+ const auto key_string = get_key_string(key);
+
+ if (only_if_missing) {
+ LOG("Redis EXISTS {}", key_string);
+ const auto reply = redis_command("EXISTS %s", key_string.c_str());
+ if (!reply) {
+ return nonstd::make_unexpected(reply.error());
+ } else if ((*reply)->type != REDIS_REPLY_INTEGER) {
+ LOG("Unknown reply type: {}", (*reply)->type);
+ } else if ((*reply)->integer > 0) {
+ LOG("Entry {} already in Redis", key_string);
+ return false;
+ }
+ }
+
+ LOG("Redis SET {} [{} bytes]", key_string, value.size());
+ const auto reply =
+ redis_command("SET %s %b", key_string.c_str(), value.data(), value.size());
+ if (!reply) {
+ return nonstd::make_unexpected(reply.error());
+ } else if ((*reply)->type == REDIS_REPLY_STATUS) {
+ return true;
+ } else {
+ LOG("Unknown reply type: {}", (*reply)->type);
+ return nonstd::make_unexpected(Failure::error);
+ }
+}
+
+nonstd::expected<bool, RemoteStorage::Backend::Failure>
+RedisStorageBackend::remove(const Digest& key)
+{
+ const auto key_string = get_key_string(key);
+ LOG("Redis DEL {}", key_string);
+ const auto reply = redis_command("DEL %s", key_string.c_str());
+ if (!reply) {
+ return nonstd::make_unexpected(reply.error());
+ } else if ((*reply)->type == REDIS_REPLY_INTEGER) {
+ return (*reply)->integer > 0;
+ } else {
+ LOG("Unknown reply type: {}", (*reply)->type);
+ return nonstd::make_unexpected(Failure::error);
+ }
+}
+
+void
+RedisStorageBackend::connect(const Url& url,
+ const uint32_t connect_timeout,
+ const uint32_t operation_timeout)
+{
+ if (url.scheme() == "redis+unix") {
+ LOG("Redis connecting to {} (connect timeout {} ms)",
+ url.path(),
+ connect_timeout);
+ m_context.reset(redisConnectUnixWithTimeout(url.path().c_str(),
+ to_timeval(connect_timeout)));
+ } else {
+ const std::string host = url.host().empty() ? "localhost" : url.host();
+ const uint32_t port =
+ url.port().empty() ? DEFAULT_PORT
+ : util::value_or_throw<core::Fatal>(
+ util::parse_unsigned(url.port(), 1, 65535, "port"));
+ ASSERT(url.path().empty() || url.path()[0] == '/');
+
+ LOG("Redis connecting to {}:{} (connect timeout {} ms)",
+ host,
+ port,
+ connect_timeout);
+ m_context.reset(
+ redisConnectWithTimeout(host.c_str(), port, to_timeval(connect_timeout)));
+ }
+
+ if (!m_context) {
+ throw Failed("Redis context construction error");
+ }
+ if (is_timeout(m_context->err)) {
+ throw Failed(FMT("Redis connection timeout: {}", m_context->errstr),
+ Failure::timeout);
+ }
+ if (is_error(m_context->err)) {
+ throw Failed(FMT("Redis connection error: {}", m_context->errstr));
+ }
+
+ LOG("Redis operation timeout set to {} ms", operation_timeout);
+ if (redisSetTimeout(m_context.get(), to_timeval(operation_timeout))
+ != REDIS_OK) {
+ throw Failed("Failed to set operation timeout");
+ }
+
+ LOG_RAW("Redis connection OK");
+}
+
+void
+RedisStorageBackend::select_database(const Url& url)
+{
+ std::optional<std::string> db;
+ if (url.scheme() == "redis+unix") {
+ for (const auto& param : url.query()) {
+ if (param.key() == "db") {
+ db = param.val();
+ break;
+ }
+ }
+ } else if (!url.path().empty()) {
+ db = url.path().substr(1);
+ }
+ const uint32_t db_number =
+ !db ? 0
+ : util::value_or_throw<core::Fatal>(util::parse_unsigned(
+ *db, 0, std::numeric_limits<uint32_t>::max(), "db number"));
+
+ if (db_number != 0) {
+ LOG("Redis SELECT {}", db_number);
+ util::value_or_throw<Failed>(redis_command("SELECT %d", db_number));
+ }
+}
+
+void
+RedisStorageBackend::authenticate(const Url& url)
+{
+ const auto [user, password] = split_user_info(url.user_info());
+ if (password) {
+ if (user) {
+ // redis://user:password@host
+ LOG("Redis AUTH {} {}", *user, k_redacted_password);
+ util::value_or_throw<Failed>(
+ redis_command("AUTH %s %s", user->c_str(), password->c_str()));
+ } else {
+ // redis://password@host
+ LOG("Redis AUTH {}", k_redacted_password);
+ util::value_or_throw<Failed>(redis_command("AUTH %s", password->c_str()));
+ }
+ }
+}
+
+nonstd::expected<RedisReply, RemoteStorage::Backend::Failure>
+RedisStorageBackend::redis_command(const char* format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ auto reply =
+ static_cast<redisReply*>(redisvCommand(m_context.get(), format, ap));
+ va_end(ap);
+ if (!reply) {
+ LOG("Redis command failed: {}", m_context->errstr);
+ return nonstd::make_unexpected(is_timeout(m_context->err) ? Failure::timeout
+ : Failure::error);
+ } else if (reply->type == REDIS_REPLY_ERROR) {
+ LOG("Redis command failed: {}", reply->str);
+ return nonstd::make_unexpected(Failure::error);
+ } else {
+ return RedisReply(reply, freeReplyObject);
+ }
+}
+
+std::string
+RedisStorageBackend::get_key_string(const Digest& digest) const
+{
+ return FMT("{}:{}", m_prefix, digest.to_string());
+}
+
+} // namespace
+
+std::unique_ptr<RemoteStorage::Backend>
+RedisStorage::create_backend(const Backend::Params& params) const
+{
+ return std::make_unique<RedisStorageBackend>(params);
+}
+
+void
+RedisStorage::redact_secrets(Backend::Params& params) const
+{
+ auto& url = params.url;
+ const auto [user, password] = split_user_info(url.user_info());
+ if (password) {
+ if (user) {
+ // redis://user:password@host
+ url.user_info(FMT("{}:{}", *user, k_redacted_password));
+ } else {
+ // redis://password@host
+ url.user_info(k_redacted_password);
+ }
+ }
+}
+
+} // namespace storage::remote
--- /dev/null
+// Copyright (C) 2021-2022 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 <storage/remote/RemoteStorage.hpp>
+
+namespace storage::remote {
+
+class RedisStorage : public RemoteStorage
+{
+public:
+ std::unique_ptr<Backend>
+ create_backend(const Backend::Params& params) const override;
+
+ void redact_secrets(Backend::Params& params) const override;
+};
+
+} // namespace storage::remote
--- /dev/null
+// Copyright (C) 2021-2022 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 "RemoteStorage.hpp"
+
+#include <util/expected.hpp>
+#include <util/string.hpp>
+
+namespace storage::remote {
+
+bool
+RemoteStorage::Backend::is_framework_attribute(const std::string& name)
+{
+ return name == "read-only" || name == "shards";
+}
+
+std::chrono::milliseconds
+RemoteStorage::Backend::parse_timeout_attribute(const std::string& value)
+{
+ return std::chrono::milliseconds(util::value_or_throw<Failed>(
+ util::parse_unsigned(value, 1, 60 * 1000, "timeout")));
+}
+
+} // namespace storage::remote
--- /dev/null
+// Copyright (C) 2021-2022 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 <storage/types.hpp>
+#include <util/Bytes.hpp>
+
+#include <third_party/nonstd/expected.hpp>
+#include <third_party/nonstd/span.hpp>
+#include <third_party/url.hpp>
+
+#include <chrono>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+class Digest;
+
+namespace storage::remote {
+
+constexpr auto k_redacted_password = "********";
+const auto k_default_connect_timeout = std::chrono::milliseconds{100};
+const auto k_default_operation_timeout = std::chrono::milliseconds{10000};
+
+// This class defines the API that a remote storage must implement.
+class RemoteStorage
+{
+public:
+ class Backend
+ {
+ public:
+ struct Attribute
+ {
+ std::string key; // Key part.
+ std::string value; // Value part, percent-decoded.
+ std::string raw_value; // Value part, not percent-decoded.
+ };
+
+ struct Params
+ {
+ Url url;
+ std::vector<Attribute> attributes;
+ };
+
+ enum class Failure {
+ error, // Operation error, e.g. bad parameters or failed connection.
+ timeout, // Timeout, e.g. due to slow network or server.
+ };
+
+ class Failed : public std::runtime_error
+ {
+ public:
+ Failed(Failure failure);
+ Failed(const std::string& message, Failure failure = Failure::error);
+
+ Failure failure() const;
+
+ private:
+ Failure m_failure;
+ };
+
+ virtual ~Backend() = default;
+
+ // Get the value associated with `key`. Returns the value on success or
+ // std::nullopt if the entry is not present.
+ virtual nonstd::expected<std::optional<util::Bytes>, Failure>
+ get(const Digest& key) = 0;
+
+ // Put `value` associated to `key` in the storage. A true `only_if_missing`
+ // is a hint that the value does not have to be set if already present.
+ // Returns true if the entry was stored, otherwise false.
+ virtual nonstd::expected<bool, Failure>
+ put(const Digest& key,
+ nonstd::span<const uint8_t> value,
+ bool only_if_missing = false) = 0;
+
+ // Remove `key` and its associated value. Returns true if the entry was
+ // removed, otherwise false.
+ virtual nonstd::expected<bool, Failure> remove(const Digest& key) = 0;
+
+ // Determine whether an attribute is handled by the remote storage
+ // framework itself.
+ static bool is_framework_attribute(const std::string& name);
+
+ // Parse a timeout `value`, throwing `Failed` on error.
+ static std::chrono::milliseconds
+ parse_timeout_attribute(const std::string& value);
+ };
+
+ virtual ~RemoteStorage() = default;
+
+ // Create an instance of the backend. The instance is created just before the
+ // first call to a backend method, so the backend constructor can open a
+ // connection or similar right away if wanted. The method should throw
+ // `core::Fatal` on fatal configuration error or `Backend::Failed` on
+ // connection error or timeout.
+ virtual std::unique_ptr<Backend>
+ create_backend(const Backend::Params& parameters) const = 0;
+
+ // Redact secrets in backend parameters, if any.
+ virtual void redact_secrets(Backend::Params& parameters) const;
+};
+
+// --- Inline implementations ---
+
+inline void
+RemoteStorage::redact_secrets(RemoteStorage::Backend::Params& /*config*/) const
+{
+}
+
+inline RemoteStorage::Backend::Failed::Failed(Failure failure)
+ : Failed("", failure)
+{
+}
+
+inline RemoteStorage::Backend::Failed::Failed(const std::string& message,
+ Failure failure)
+ : std::runtime_error::runtime_error(message),
+ m_failure(failure)
+{
+}
+
+inline RemoteStorage::Backend::Failure
+RemoteStorage::Backend::Failed::failure() const
+{
+ return m_failure;
+}
+
+} // namespace storage::remote
--- /dev/null
+// Copyright (C) 2021 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 <functional>
+#include <string>
+
+namespace storage {
+
+using EntryWriter = std::function<bool(const std::string& path)>;
+
+} // namespace storage
+++ /dev/null
-// Copyright (C) 2010-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
-
-#ifdef __MINGW32__
-# define __USE_MINGW_ANSI_STDIO 1
-# define __STDC_FORMAT_MACROS 1
-#endif
-
-#include "config.h"
-
-#ifdef HAVE_SYS_FILE_H
-# include <sys/file.h>
-#endif
-
-#ifdef HAVE_SYS_MMAN_H
-# include <sys/mman.h>
-#endif
-#include <sys/stat.h>
-#include <sys/types.h>
-#ifdef HAVE_SYS_WAIT_H
-# include <sys/wait.h>
-#endif
-
-#include <cassert>
-#include <cctype>
-#include <cerrno>
-#include <cinttypes>
-#include <climits>
-#include <csignal>
-#include <cstdarg>
-#include <cstddef>
-#include <cstdint>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <ctime>
-
-#ifdef HAVE_DIRENT_H
-# include <dirent.h>
-#endif
-
-#include <fcntl.h>
-
-#ifdef HAVE_STRINGS_H
-# include <strings.h>
-#endif
-
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-#endif
-
-#ifdef HAVE_UTIME_H
-# include <utime.h>
-#elif defined(HAVE_SYS_UTIME_H)
-# include <sys/utime.h>
-#endif
-
-#ifdef HAVE_VARARGS_H
-# include <varargs.h>
-#endif
-
-// AIX/PASE does not properly define usleep within its headers. However, the
-// function is available in libc.a. This extern define ensures that it is
-// usable within the ccache code base.
-#ifdef _AIX
-extern "C" int usleep(useconds_t);
-#endif
-
-#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
-
-// Buffer size for I/O operations. Should be a multiple of 4 KiB.
-const size_t READ_BUFFER_SIZE = 65536;
-
-#ifndef ESTALE
-# define ESTALE -1
-#endif
-
-#ifdef _WIN32
-# ifndef _WIN32_WINNT
-// _WIN32_WINNT is set in the generated header config.h
-# error _WIN32_WINNT is undefined
-# endif
-
-# ifdef _MSC_VER
-typedef int mode_t;
-typedef int pid_t;
-# endif
-
-# ifndef __MINGW32__
-typedef int64_t ssize_t;
-# endif
-
-// Defined in Win32Util.cpp
-void usleep(int64_t usec);
-struct tm* localtime_r(time_t* _clock, struct tm* _result);
-
-# ifdef _MSC_VER
-int gettimeofday(struct timeval* tp, struct timezone* tzp);
-int asprintf(char** strp, const char* fmt, ...);
-# endif
-
-// From:
-// http://mesos.apache.org/api/latest/c++/3rdparty_2stout_2include_2stout_2windows_8hpp_source.html
-# ifdef _MSC_VER
-const mode_t S_IRUSR = mode_t(_S_IREAD);
-const mode_t S_IWUSR = mode_t(_S_IWRITE);
-# endif
-
-# ifndef S_IFIFO
-# define S_IFIFO 0x1000
-# endif
-
-# ifndef S_IFBLK
-# define S_IFBLK 0x6000
-# endif
-
-# ifndef S_IFLNK
-# define S_IFLNK 0xA000
-# endif
-
-# ifndef S_ISREG
-# define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
-# endif
-# ifndef S_ISDIR
-# define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
-# endif
-# ifndef S_ISFIFO
-# define S_ISFIFO(m) (((m)&S_IFMT) == S_IFIFO)
-# endif
-# ifndef S_ISCHR
-# define S_ISCHR(m) (((m)&S_IFMT) == S_IFCHR)
-# endif
-# ifndef S_ISLNK
-# define S_ISLNK(m) (((m)&S_IFMT) == S_IFLNK)
-# endif
-# ifndef S_ISBLK
-# define S_ISBLK(m) (((m)&S_IFMT) == S_IFBLK)
-# endif
-
-# include <direct.h>
-# include <io.h>
-# include <process.h>
-# define NOMINMAX 1
-# define WIN32_NO_STATUS
-# include <windows.h>
-# undef WIN32_NO_STATUS
-# include <ntstatus.h>
-# define mkdir(a, b) _mkdir(a)
-# define execv(a, b) \
- do_not_call_execv_on_windows // to protect against incidental use of MinGW
- // execv
-# define strncasecmp _strnicmp
-# define strcasecmp _stricmp
-
-# ifdef _MSC_VER
-# define PATH_MAX MAX_PATH
-# endif
-
-# ifdef _MSC_VER
-# define DLLIMPORT __declspec(dllimport)
-# else
-# define DLLIMPORT
-# endif
-
-# define STDIN_FILENO 0
-# define STDOUT_FILENO 1
-# define STDERR_FILENO 2
-# define DIR_DELIM_CH '\\'
-# define PATH_DELIM ";"
-#else
-# define DLLIMPORT
-# define DIR_DELIM_CH '/'
-# define PATH_DELIM ":"
-#endif
-
-DLLIMPORT extern char** environ;
-
-// Work with silly DOS binary open.
-#ifndef O_BINARY
-# define O_BINARY 0
-#endif
-
-#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_PTHREAD_MUTEXATTR_SETPSHARED)
-# define INODE_CACHE_SUPPORTED
-#endif
-
-// Workaround for missing std::is_trivially_copyable in GCC < 5.
-#if __GNUG__ && __GNUC__ < 5
-# define IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T)
-#else
-# define IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable<T>::value
-#endif
-
-// GCC version of a couple of standard C++ attributes
-#ifdef __GNUC__
-# define nodiscard gnu::warn_unused_result
-# define maybe_unused gnu::unused
-#endif
--- /dev/null
+// Copyright (C) 2022 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 <Config.hpp>
+#include <Logging.hpp>
+#include <fmtmacros.hpp>
+#include <util/LockFile.hpp>
+#include <util/string.hpp>
+
+#include <memory>
+#include <string>
+#include <thread>
+
+int
+main(int argc, char** argv)
+{
+ if (argc != 5) {
+ PRINT_RAW(stderr,
+ "Usage: test-lockfile PATH SECONDS <short|long>"
+ " <blocking|non-blocking>\n");
+ return 1;
+ }
+ Config config;
+ config.update_from_environment();
+ Logging::init(config);
+
+ const std::string path(argv[1]);
+ const auto seconds = util::parse_signed(argv[2]);
+ const bool long_lived = std::string(argv[3]) == "long";
+ const bool blocking = std::string(argv[4]) == "blocking";
+ if (!seconds) {
+ PRINT_RAW(stderr, "Error: Failed to parse seconds\n");
+ return 1;
+ }
+
+ using LockFilePtr = std::unique_ptr<util::LockFile>;
+ LockFilePtr lock_file;
+ lock_file = long_lived
+ ? LockFilePtr{std::make_unique<util::LongLivedLockFile>(path)}
+ : LockFilePtr{std::make_unique<util::ShortLivedLockFile>(path)};
+ const auto mode = blocking ? util::LockFileGuard::Mode::blocking
+ : util::LockFileGuard::Mode::non_blocking;
+
+ PRINT(stdout, "{}\n", blocking ? "Acquiring" : "Trying to acquire");
+ bool acquired = false;
+ {
+ util::LockFileGuard lock(*lock_file, mode);
+ acquired = lock.acquired();
+ if (acquired) {
+ PRINT_RAW(stdout, "Acquired\n");
+ PRINT(
+ stdout, "Sleeping {} second{}\n", *seconds, *seconds == 1 ? "" : "s");
+ std::this_thread::sleep_for(std::chrono::seconds{*seconds});
+ } else {
+ PRINT(stdout, "{} acquire\n", blocking ? "Failed to" : "Did not");
+ }
+ if (acquired) {
+ PRINT_RAW(stdout, "Releasing\n");
+ }
+ }
+ if (acquired) {
+ PRINT_RAW(stdout, "Released\n");
+ }
+}
# config file... So just pick a fast check that will never fail.
---
Checks: '-*,readability-function-size'
-CheckOptions:
+CheckOptions:
- key: readability-function-size.LineThreshold
value: 99999999
...
-
-add_library(third_party_lib STATIC base32hex.c format.cpp xxhash.c)
+add_library(third_party STATIC base32hex.c format.cpp httplib.cpp url.cpp xxhash.c)
if(NOT MSVC)
- target_sources(third_party_lib PRIVATE getopt_long.c)
+ target_sources(third_party PRIVATE getopt_long.c)
else()
- target_sources(third_party_lib PRIVATE win32/getopt.c)
- target_compile_definitions(third_party_lib PUBLIC -DSTATIC_GETOPT)
+ target_sources(third_party PRIVATE win32/getopt.c)
+ target_compile_definitions(third_party PUBLIC -DSTATIC_GETOPT)
endif()
if(WIN32)
- target_sources(third_party_lib PRIVATE win32/mktemp.c)
+ target_sources(third_party PRIVATE win32/mktemp.c)
endif ()
if(ENABLE_TRACING)
- target_sources(third_party_lib PRIVATE minitrace.c)
+ target_sources(third_party PRIVATE minitrace.c)
endif()
+file(GLOB headers *.h fmt/*.h nonstd/*.hpp win32/*.h)
+target_sources(third_party PRIVATE ${headers})
+
set(xxhdispatchtest [=[
#include "xxh_x86dispatch.c"
CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${CMAKE_CURRENT_SOURCE_DIR}"
COMPILE_DEFINITIONS "-DXXH_STATIC_LINKING_ONLY")
-target_compile_definitions(third_party_lib INTERFACE "-DXXH_STATIC_LINKING_ONLY")
+target_compile_definitions(third_party INTERFACE "-DXXH_STATIC_LINKING_ONLY")
if(USE_XXH_DISPATCH)
- target_sources(third_party_lib PRIVATE xxh_x86dispatch.c)
- target_compile_definitions(third_party_lib INTERFACE "-DUSE_XXH_DISPATCH")
+ target_sources(third_party PRIVATE xxh_x86dispatch.c)
+ target_compile_definitions(third_party INTERFACE "-DUSE_XXH_DISPATCH")
endif()
# Treat third party headers as system files (no warning for those headers).
target_include_directories(
- third_party_lib
- PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} SYSTEM)
-
-target_link_libraries(third_party_lib PRIVATE standard_settings)
-target_link_libraries(third_party_lib INTERFACE blake3)
+ third_party
+ PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} SYSTEM
+)
-# These warnings are enabled by default even without e.g. -Wall, but we don't
-# want them in third_party.
-if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|Clang$")
- target_compile_options(
- third_party_lib
- PRIVATE
- $<$<COMPILE_LANGUAGE:C>:-Wno-implicit-function-declaration
- -Wno-int-conversion>)
-endif()
+target_link_libraries(third_party PRIVATE standard_settings)
+target_link_libraries(third_party INTERFACE blake3)
-if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
- target_compile_options(
- third_party_lib
- PRIVATE $<$<COMPILE_LANGUAGE:C>:-Wno-attributes>)
+if(WIN32)
+ target_link_libraries(third_party PRIVATE ws2_32)
endif()
# Silence warning from winbase.h due to /Zc:preprocessor.
if(MSVC)
- target_compile_options(
- third_party_lib
- PRIVATE /wd5105)
-endif()
-
-# The headers are included from the rest of the project, so turn off warnings as
-# required.
-if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
- target_compile_options(third_party_lib INTERFACE -Wno-shadow)
+ target_compile_options(third_party PRIVATE /wd5105)
endif()
add_subdirectory(blake3)
set(compile_flags "${others_flags}")
endif()
+ if(MSVC)
+ set(suffix "_x86-64_windows_msvc.asm")
+ elseif(WIN32)
+ set(suffix "_x86-64_windows_gnu.S")
+ else()
+ set(suffix "_x86-64_unix.S")
+ endif()
+
# First check if it's possible to use the assembler variant for the feature.
string(TOUPPER "have_asm_${feature}" have_feature)
if(NOT DEFINED "${have_feature}" AND CMAKE_SIZEOF_VOID_P EQUAL 8)
- if(MSVC)
- set(suffix "_x86-64_windows_msvc.asm")
- elseif(WIN32)
- set(suffix "_x86-64_windows_gnu.S")
- else()
- set(suffix "_x86-64_unix.S")
- endif()
-
if(NOT CMAKE_REQUIRED_QUIET)
message(STATUS "Performing Test ${have_feature}")
endif()
target_compile_definitions(blake3 PRIVATE BLAKE3_USE_NEON)
endif()
endif()
+
+file(GLOB headers *.h)
+target_sources(blake3 PRIVATE ${headers})
#include "blake3.h"
#include "blake3_impl.h"
-const char * blake3_version(void) {
- return BLAKE3_VERSION_STRING;
-}
+const char *blake3_version(void) { return BLAKE3_VERSION_STRING; }
INLINE void chunk_state_init(blake3_chunk_state *self, const uint32_t key[8],
uint8_t flags) {
uint8_t cv_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN];
size_t num_cvs = blake3_compress_subtree_wide(input, input_len, key,
chunk_counter, flags, cv_array);
+ assert(num_cvs <= MAX_SIMD_DEGREE_OR_2);
// If MAX_SIMD_DEGREE is greater than 2 and there's enough input,
// compress_subtree_wide() returns more than 2 chaining values. Condense
// them into 2 by forming parent nodes repeatedly.
uint8_t out_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN / 2];
- while (num_cvs > 2) {
+ // The second half of this loop condition is always true, and we just
+ // asserted it above. But GCC can't tell that it's always true, and if NDEBUG
+ // is set on platforms where MAX_SIMD_DEGREE_OR_2 == 2, GCC emits spurious
+ // warnings here. GCC 8.5 is particularly sensitive, so if you're changing
+ // this code, test it against that version.
+ while (num_cvs > 2 && num_cvs <= MAX_SIMD_DEGREE_OR_2) {
num_cvs =
compress_parents_parallel(cv_array, num_cvs, key, flags, out_array);
memcpy(cv_array, out_array, num_cvs * BLAKE3_OUT_LEN);
}
output_root_bytes(&output, seek, out, out_len);
}
+
+void blake3_hasher_reset(blake3_hasher *self) {
+ chunk_state_reset(&self->chunk, self->key, 0);
+ self->cv_stack_len = 0;
+}
extern "C" {
#endif
-#define BLAKE3_VERSION_STRING "0.3.7"
+#define BLAKE3_VERSION_STRING "1.3.1"
#define BLAKE3_KEY_LEN 32
#define BLAKE3_OUT_LEN 32
#define BLAKE3_BLOCK_LEN 64
#define BLAKE3_CHUNK_LEN 1024
#define BLAKE3_MAX_DEPTH 54
-#define BLAKE3_MAX_SIMD_DEGREE 16
// This struct is a private implementation detail. It has to be here because
// it's part of blake3_hasher below.
uint8_t cv_stack[(BLAKE3_MAX_DEPTH + 1) * BLAKE3_OUT_LEN];
} blake3_hasher;
-const char * blake3_version(void);
+const char *blake3_version(void);
void blake3_hasher_init(blake3_hasher *self);
void blake3_hasher_init_keyed(blake3_hasher *self,
const uint8_t key[BLAKE3_KEY_LEN]);
void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context);
-void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,
+void blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,
size_t context_len);
void blake3_hasher_update(blake3_hasher *self, const void *input,
size_t input_len);
size_t out_len);
void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,
uint8_t *out, size_t out_len);
+void blake3_hasher_reset(blake3_hasher *self);
#ifdef __cplusplus
}
out[14] = loadu(&inputs[6][block_offset + 1 * sizeof(__m256i)]);
out[15] = loadu(&inputs[7][block_offset + 1 * sizeof(__m256i)]);
for (size_t i = 0; i < 8; ++i) {
- _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);
+ _mm_prefetch((const void *)&inputs[i][block_offset + 256], _MM_HINT_T0);
}
transpose_vecs(&out[0]);
transpose_vecs(&out[8]);
const __m256i mask = _mm256_set1_epi32(-(int32_t)increment_counter);
const __m256i add0 = _mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0);
const __m256i add1 = _mm256_and_si256(mask, add0);
- __m256i l = _mm256_add_epi32(_mm256_set1_epi32(counter), add1);
+ __m256i l = _mm256_add_epi32(_mm256_set1_epi32((int32_t)counter), add1);
__m256i carry = _mm256_cmpgt_epi32(_mm256_xor_si256(add1, _mm256_set1_epi32(0x80000000)),
_mm256_xor_si256( l, _mm256_set1_epi32(0x80000000)));
- __m256i h = _mm256_sub_epi32(_mm256_set1_epi32(counter >> 32), carry);
+ __m256i h = _mm256_sub_epi32(_mm256_set1_epi32((int32_t)(counter >> 32)), carry);
*out_lo = l;
*out_hi = h;
}
+static
void blake3_hash8_avx2(const uint8_t *const *inputs, size_t blocks,
const uint32_t key[8], uint64_t counter,
bool increment_counter, uint8_t flags,
out[14] = loadu_128(&inputs[2][block_offset + 3 * sizeof(__m128i)]);
out[15] = loadu_128(&inputs[3][block_offset + 3 * sizeof(__m128i)]);
for (size_t i = 0; i < 4; ++i) {
- _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);
+ _mm_prefetch((const void *)&inputs[i][block_offset + 256], _MM_HINT_T0);
}
transpose_vecs_128(&out[0]);
transpose_vecs_128(&out[4]);
*out_hi = _mm256_cvtepi64_epi32(_mm256_srli_epi64(counters, 32));
}
+static
void blake3_hash4_avx512(const uint8_t *const *inputs, size_t blocks,
const uint32_t key[8], uint64_t counter,
bool increment_counter, uint8_t flags,
out[14] = loadu_256(&inputs[6][block_offset + 1 * sizeof(__m256i)]);
out[15] = loadu_256(&inputs[7][block_offset + 1 * sizeof(__m256i)]);
for (size_t i = 0; i < 8; ++i) {
- _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);
+ _mm_prefetch((const void *)&inputs[i][block_offset + 256], _MM_HINT_T0);
}
transpose_vecs_256(&out[0]);
transpose_vecs_256(&out[8]);
*out_hi = _mm512_cvtepi64_epi32(_mm512_srli_epi64(counters, 32));
}
+static
void blake3_hash8_avx512(const uint8_t *const *inputs, size_t blocks,
const uint32_t key[8], uint64_t counter,
bool increment_counter, uint8_t flags,
out[14] = loadu_512(&inputs[14][block_offset]);
out[15] = loadu_512(&inputs[15][block_offset]);
for (size_t i = 0; i < 16; ++i) {
- _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);
+ _mm_prefetch((const void *)&inputs[i][block_offset + 256], _MM_HINT_T0);
}
transpose_vecs_512(out);
}
const __m512i mask = _mm512_set1_epi32(-(int32_t)increment_counter);
const __m512i add0 = _mm512_set_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
const __m512i add1 = _mm512_and_si512(mask, add0);
- __m512i l = _mm512_add_epi32(_mm512_set1_epi32(counter), add1);
+ __m512i l = _mm512_add_epi32(_mm512_set1_epi32((int32_t)counter), add1);
__mmask16 carry = _mm512_cmp_epu32_mask(l, add1, _MM_CMPINT_LT);
- __m512i h = _mm512_mask_add_epi32(_mm512_set1_epi32(counter >> 32), carry, _mm512_set1_epi32(counter >> 32), _mm512_set1_epi32(1));
+ __m512i h = _mm512_mask_add_epi32(_mm512_set1_epi32((int32_t)(counter >> 32)), carry, _mm512_set1_epi32((int32_t)(counter >> 32)), _mm512_set1_epi32(1));
*out_lo = l;
*out_hi = h;
}
+static
void blake3_hash16_avx512(const uint8_t *const *inputs, size_t blocks,
const uint32_t key[8], uint64_t counter,
bool increment_counter, uint8_t flags,
movzx r8d, r8b
shl rax, 32
add r8, rax
- vmovd xmm3, r9
- vmovd xmm4, r8
+ vmovq xmm3, r9
+ vmovq xmm4, r8
vpunpcklqdq xmm3, xmm3, xmm4
vmovaps xmm2, xmmword ptr [BLAKE3_IV]
vmovups xmm8, xmmword ptr [rdx]
mov r10, qword ptr [rsp+78H]
shl rax, 32
add r8, rax
- vmovd xmm3, r9
- vmovd xmm4, r8
+ vmovq xmm3, r9
+ vmovq xmm4, r8
vpunpcklqdq xmm3, xmm3, xmm4
vmovaps xmm2, xmmword ptr [BLAKE3_IV]
vmovups xmm8, xmmword ptr [rdx]
#endif
#endif
-#if defined(BLAKE3_USE_NEON)
+#if BLAKE3_USE_NEON == 1
blake3_hash_many_neon(inputs, num_inputs, blocks, key, counter,
increment_counter, flags, flags_start, flags_end, out);
return;
}
#endif
#endif
-#if defined(BLAKE3_USE_NEON)
+#if BLAKE3_USE_NEON == 1
return 4;
#endif
return 1;
#define IS_X86_32
#endif
+#if defined(__aarch64__) || defined(_M_ARM64)
+#define IS_AARCH64
+#endif
+
#if defined(IS_X86)
#if defined(_MSC_VER)
#include <intrin.h>
#include <immintrin.h>
#endif
+#if !defined(BLAKE3_USE_NEON)
+ // If BLAKE3_USE_NEON not manually set, autodetect based on AArch64ness
+ #if defined(IS_AARCH64)
+ #define BLAKE3_USE_NEON 1
+ #else
+ #define BLAKE3_USE_NEON 0
+ #endif
+#endif
+
#if defined(IS_X86)
#define MAX_SIMD_DEGREE 16
-#elif defined(BLAKE3_USE_NEON)
+#elif BLAKE3_USE_NEON == 1
#define MAX_SIMD_DEGREE 4
#else
#define MAX_SIMD_DEGREE 1
#elif defined(_MSC_VER) && defined(IS_X86_32)
if(x >> 32) {
unsigned long index;
- _BitScanReverse(&index, x >> 32);
+ _BitScanReverse(&index, (unsigned long)(x >> 32));
return 32 + index;
} else {
unsigned long index;
- _BitScanReverse(&index, x);
+ _BitScanReverse(&index, (unsigned long)x);
return index;
}
#else
#endif
#endif
-#if defined(BLAKE3_USE_NEON)
+#if BLAKE3_USE_NEON == 1
void blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs,
size_t blocks, const uint32_t key[8],
uint64_t counter, bool increment_counter,
#include <arm_neon.h>
-// TODO: This is probably incorrect for big-endian ARM. How should that work?
+#ifdef __ARM_BIG_ENDIAN
+#error "This implementation only supports little-endian ARM."
+// It might be that all we need for big-endian support here is to get the loads
+// and stores right, but step zero would be finding a way to test it in CI.
+#endif
+
INLINE uint32x4_t loadu_128(const uint8_t src[16]) {
// vld1q_u32 has alignment requirements. Don't use it.
uint32x4_t x;
*row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(2, 1, 0, 3));
}
-INLINE __m128i blend_epi16(__m128i a, __m128i b, const int imm8) {
+INLINE __m128i blend_epi16(__m128i a, __m128i b, const int16_t imm8) {
const __m128i bits = _mm_set_epi16(0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01);
__m128i mask = _mm_set1_epi16(imm8);
mask = _mm_and_si128(mask, bits);
out[14] = loadu(&inputs[2][block_offset + 3 * sizeof(__m128i)]);
out[15] = loadu(&inputs[3][block_offset + 3 * sizeof(__m128i)]);
for (size_t i = 0; i < 4; ++i) {
- _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);
+ _mm_prefetch((const void *)&inputs[i][block_offset + 256], _MM_HINT_T0);
}
transpose_vecs(&out[0]);
transpose_vecs(&out[4]);
const __m128i mask = _mm_set1_epi32(-(int32_t)increment_counter);
const __m128i add0 = _mm_set_epi32(3, 2, 1, 0);
const __m128i add1 = _mm_and_si128(mask, add0);
- __m128i l = _mm_add_epi32(_mm_set1_epi32(counter), add1);
+ __m128i l = _mm_add_epi32(_mm_set1_epi32((int32_t)counter), add1);
__m128i carry = _mm_cmpgt_epi32(_mm_xor_si128(add1, _mm_set1_epi32(0x80000000)),
_mm_xor_si128( l, _mm_set1_epi32(0x80000000)));
- __m128i h = _mm_sub_epi32(_mm_set1_epi32(counter >> 32), carry);
+ __m128i h = _mm_sub_epi32(_mm_set1_epi32((int32_t)(counter >> 32)), carry);
*out_lo = l;
*out_hi = h;
}
+static
void blake3_hash4_sse2(const uint8_t *const *inputs, size_t blocks,
const uint32_t key[8], uint64_t counter,
bool increment_counter, uint8_t flags,
pshufd xmm15, xmm11, 0x93
shl rax, 0x20
or rax, 0x40
- movd xmm3, rax
+ movq xmm3, rax
movdqa xmmword ptr [rsp+0x20], xmm3
movaps xmm3, xmmword ptr [rsp]
movaps xmm11, xmmword ptr [rsp+0x10]
movaps xmm2, xmmword ptr [BLAKE3_IV+rip]
shl rax, 32
or rax, 64
- movd xmm12, rax
+ movq xmm12, rax
movdqa xmm3, xmm13
punpcklqdq xmm3, xmm12
movups xmm4, xmmword ptr [r8+rdx-0x40]
pshufd xmm15, xmm11, 0x93
shl rax, 0x20
or rax, 0x40
- movd xmm3, rax
+ movq xmm3, rax
movdqa xmmword ptr [rsp+0x20], xmm3
movaps xmm3, xmmword ptr [rsp]
movaps xmm11, xmmword ptr [rsp+0x10]
movaps xmm2, xmmword ptr [BLAKE3_IV+rip]
shl rax, 32
or rax, 64
- movd xmm12, rax
+ movq xmm12, rax
movdqa xmm3, xmm13
punpcklqdq xmm3, xmm12
movups xmm4, xmmword ptr [r8+rdx-0x40]
por xmm9, xmm8
movdqa xmm8, xmm7
punpcklqdq xmm8, xmm5
- movdqa xmm10, xmm6
+ movdqa xmm14, xmm6
pand xmm8, xmmword ptr [PBLENDW_0x3F_MASK+rip]
- pand xmm10, xmmword ptr [PBLENDW_0xC0_MASK+rip]
- por xmm8, xmm10
+ pand xmm14, xmmword ptr [PBLENDW_0xC0_MASK+rip]
+ por xmm8, xmm14
pshufd xmm8, xmm8, 0x78
punpckhdq xmm5, xmm7
punpckldq xmm6, xmm5
por xmm9, xmm8
movdqa xmm8, xmm7
punpcklqdq xmm8, xmm5
- movdqa xmm10, xmm6
+ movdqa xmm14, xmm6
pand xmm8, xmmword ptr [PBLENDW_0x3F_MASK+rip]
- pand xmm10, xmmword ptr [PBLENDW_0xC0_MASK+rip]
- por xmm8, xmm10
+ pand xmm14, xmmword ptr [PBLENDW_0xC0_MASK+rip]
+ por xmm8, xmm14
pshufd xmm8, xmm8, 0x78
punpckhdq xmm5, xmm7
punpckldq xmm6, xmm5
por xmm9, xmm8
movdqa xmm8, xmm7
punpcklqdq xmm8, xmm5
- movdqa xmm10, xmm6
+ movdqa xmm14, xmm6
pand xmm8, xmmword ptr [PBLENDW_0x3F_MASK]
- pand xmm10, xmmword ptr [PBLENDW_0xC0_MASK]
- por xmm8, xmm10
+ pand xmm14, xmmword ptr [PBLENDW_0xC0_MASK]
+ por xmm8, xmm14
pshufd xmm8, xmm8, 78H
punpckhdq xmm5, xmm7
punpckldq xmm6, xmm5
por xmm9, xmm8
movdqa xmm8, xmm7
punpcklqdq xmm8, xmm5
- movdqa xmm10, xmm6
+ movdqa xmm14, xmm6
pand xmm8, xmmword ptr [PBLENDW_0x3F_MASK]
- pand xmm10, xmmword ptr [PBLENDW_0xC0_MASK]
- por xmm8, xmm10
+ pand xmm14, xmmword ptr [PBLENDW_0xC0_MASK]
+ por xmm8, xmm14
pshufd xmm8, xmm8, 78H
punpckhdq xmm5, xmm7
punpckldq xmm6, xmm5
out[14] = loadu(&inputs[2][block_offset + 3 * sizeof(__m128i)]);
out[15] = loadu(&inputs[3][block_offset + 3 * sizeof(__m128i)]);
for (size_t i = 0; i < 4; ++i) {
- _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);
+ _mm_prefetch((const void *)&inputs[i][block_offset + 256], _MM_HINT_T0);
}
transpose_vecs(&out[0]);
transpose_vecs(&out[4]);
const __m128i mask = _mm_set1_epi32(-(int32_t)increment_counter);
const __m128i add0 = _mm_set_epi32(3, 2, 1, 0);
const __m128i add1 = _mm_and_si128(mask, add0);
- __m128i l = _mm_add_epi32(_mm_set1_epi32(counter), add1);
+ __m128i l = _mm_add_epi32(_mm_set1_epi32((int32_t)counter), add1);
__m128i carry = _mm_cmpgt_epi32(_mm_xor_si128(add1, _mm_set1_epi32(0x80000000)),
_mm_xor_si128( l, _mm_set1_epi32(0x80000000)));
- __m128i h = _mm_sub_epi32(_mm_set1_epi32(counter >> 32), carry);
+ __m128i h = _mm_sub_epi32(_mm_set1_epi32((int32_t)(counter >> 32)), carry);
*out_lo = l;
*out_hi = h;
}
+static
void blake3_hash4_sse41(const uint8_t *const *inputs, size_t blocks,
const uint32_t key[8], uint64_t counter,
bool increment_counter, uint8_t flags,
// https://opensource.org/licenses/MIT
//
// The documentation can be found at the library's page:
-// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md
+// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md
//
// =================================================================================================
// =================================================================================================
#define DOCTEST_VERSION_MAJOR 2
#define DOCTEST_VERSION_MINOR 4
-#define DOCTEST_VERSION_PATCH 6
-#define DOCTEST_VERSION_STR "2.4.6"
+#define DOCTEST_VERSION_PATCH 9
+
+// util we need here
+#define DOCTEST_TOSTR_IMPL(x) #x
+#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x)
+
+#define DOCTEST_VERSION_STR \
+ DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "." \
+ DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "." \
+ DOCTEST_TOSTR(DOCTEST_VERSION_PATCH)
#define DOCTEST_VERSION \
(DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH)
// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect
+#ifdef _MSC_VER
+#define DOCTEST_CPLUSPLUS _MSVC_LANG
+#else
+#define DOCTEST_CPLUSPLUS __cplusplus
+#endif
+
#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH))
// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl...
// == COMPILER WARNINGS ============================================================================
// =================================================================================================
+// both the header and the implementation suppress all of these,
+// so it only makes sense to aggregrate them like so
+#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH \
+ DOCTEST_CLANG_SUPPRESS_WARNING_PUSH \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \
+ DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \
+ \
+ DOCTEST_GCC_SUPPRESS_WARNING_PUSH \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") \
+ DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") \
+ \
+ DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \
+ /* these 4 also disabled globally via cmake: */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \
+ /* */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \
+ /* static analysis */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */
+
+#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP \
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP \
+ DOCTEST_GCC_SUPPRESS_WARNING_POP \
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH
+
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
DOCTEST_GCC_SUPPRESS_WARNING_PUSH
-DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas")
-DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing")
DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations")
DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")
DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo")
DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
-DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning
-DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning
-DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration
-DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression
-DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated
-DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant
-DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding
-DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted
-DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted
-DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted
-DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted
DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted
-DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe
-// static analysis
-DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept'
-DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable
-DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ...
-DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr...
-DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
-
-// 4548 - expression before comma has no effect; expected expression with side - effect
-// 4265 - class has virtual functions, but destructor is not virtual
-// 4986 - exception specification does not match previous declaration
-// 4350 - behavior change: 'member1' called instead of 'member2'
-// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif'
-// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch
-// 4774 - format string expected in argument 'x' is not a string literal
-// 4820 - padding in structs
-
-// only 4 should be disabled globally:
-// - 4514 # unreferenced inline function has been removed
-// - 4571 # SEH related
-// - 4710 # function not inlined
-// - 4711 # function 'x' selected for automatic inline expansion
#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \
DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \
- DOCTEST_MSVC_SUPPRESS_WARNING(4548) \
- DOCTEST_MSVC_SUPPRESS_WARNING(4265) \
- DOCTEST_MSVC_SUPPRESS_WARNING(4986) \
- DOCTEST_MSVC_SUPPRESS_WARNING(4350) \
- DOCTEST_MSVC_SUPPRESS_WARNING(4668) \
- DOCTEST_MSVC_SUPPRESS_WARNING(4365) \
- DOCTEST_MSVC_SUPPRESS_WARNING(4774) \
- DOCTEST_MSVC_SUPPRESS_WARNING(4820) \
- DOCTEST_MSVC_SUPPRESS_WARNING(4625) \
- DOCTEST_MSVC_SUPPRESS_WARNING(4626) \
- DOCTEST_MSVC_SUPPRESS_WARNING(5027) \
- DOCTEST_MSVC_SUPPRESS_WARNING(5026) \
- DOCTEST_MSVC_SUPPRESS_WARNING(4623) \
- DOCTEST_MSVC_SUPPRESS_WARNING(5039) \
- DOCTEST_MSVC_SUPPRESS_WARNING(5045) \
- DOCTEST_MSVC_SUPPRESS_WARNING(5105)
+ DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */
#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP
// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html
// MSVC version table:
// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering
+// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022)
// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019)
// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017)
// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008)
// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005)
+// Universal Windows Platform support
+#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
+#define DOCTEST_CONFIG_NO_WINDOWS_SEH
+#endif // WINAPI_FAMILY
#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
#define DOCTEST_CONFIG_WINDOWS_SEH
#endif // MSVC
#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH
#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \
- !defined(__EMSCRIPTEN__)
+ !defined(__EMSCRIPTEN__) && !defined(__wasi__)
#define DOCTEST_CONFIG_POSIX_SIGNALS
#endif // _WIN32
#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS)
#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS
#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
-#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND)
+#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) \
+ || defined(__wasi__)
#define DOCTEST_CONFIG_NO_EXCEPTIONS
#endif // no exceptions
#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+#ifdef __wasi__
+#define DOCTEST_CONFIG_NO_MULTITHREADING
+#endif
+
#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT)
#define DOCTEST_CONFIG_IMPLEMENT
#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#define DOCTEST_INTERFACE
#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
+// needed for extern template instantiations
+// see https://github.com/fmtlib/fmt/issues/2228
+#if DOCTEST_MSVC
+#define DOCTEST_INTERFACE_DECL
+#define DOCTEST_INTERFACE_DEF DOCTEST_INTERFACE
+#else // DOCTEST_MSVC
+#define DOCTEST_INTERFACE_DECL DOCTEST_INTERFACE
+#define DOCTEST_INTERFACE_DEF
+#endif // DOCTEST_MSVC
+
#define DOCTEST_EMPTY
#if DOCTEST_MSVC
#endif
#ifndef DOCTEST_NORETURN
+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_NORETURN
+#else // DOCTEST_MSVC
#define DOCTEST_NORETURN [[noreturn]]
+#endif // DOCTEST_MSVC
#endif // DOCTEST_NORETURN
#ifndef DOCTEST_NOEXCEPT
+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_NOEXCEPT
+#else // DOCTEST_MSVC
#define DOCTEST_NOEXCEPT noexcept
+#endif // DOCTEST_MSVC
#endif // DOCTEST_NOEXCEPT
+#ifndef DOCTEST_CONSTEXPR
+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_CONSTEXPR const
+#define DOCTEST_CONSTEXPR_FUNC inline
+#else // DOCTEST_MSVC
+#define DOCTEST_CONSTEXPR constexpr
+#define DOCTEST_CONSTEXPR_FUNC constexpr
+#endif // DOCTEST_MSVC
+#endif // DOCTEST_CONSTEXPR
+
// =================================================================================================
// == FEATURE DETECTION END ========================================================================
// =================================================================================================
+#define DOCTEST_DECLARE_INTERFACE(name) \
+ virtual ~name(); \
+ name() = default; \
+ name(const name&) = delete; \
+ name(name&&) = delete; \
+ name& operator=(const name&) = delete; \
+ name& operator=(name&&) = delete;
+
+#define DOCTEST_DEFINE_INTERFACE(name) \
+ name::~name() = default;
+
// internal macros for string concatenation and anonymous variable name generation
#define DOCTEST_CAT_IMPL(s1, s2) s1##s2
#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2)
#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__)
#endif // __COUNTER__
-#define DOCTEST_TOSTR(x) #x
-
#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
#define DOCTEST_REF_WRAP(x) x&
#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
#define DOCTEST_PLATFORM_IPHONE
#elif defined(_WIN32)
#define DOCTEST_PLATFORM_WINDOWS
+#elif defined(__wasi__)
+#define DOCTEST_PLATFORM_WASI
#else // DOCTEST_PLATFORM
#define DOCTEST_PLATFORM_LINUX
#endif // DOCTEST_PLATFORM
-#define DOCTEST_GLOBAL_NO_WARNINGS(var) \
- DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \
- DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \
- static const int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp)
-#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP
+namespace doctest { namespace detail {
+ static DOCTEST_CONSTEXPR int consume(const int*, int) noexcept { return 0; }
+}}
+
+#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...) \
+ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \
+ static const int var = doctest::detail::consume(&var, __VA_ARGS__); \
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
#ifndef DOCTEST_BREAK_INTO_DEBUGGER
// should probably take a look at https://github.com/scottt/debugbreak
#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" : :) // NOLINT (hicpp-no-assembler)
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler)
#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__) || defined(__i386)
-#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler)
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler)
+#elif defined(__ppc__) || defined(__ppc64__)
+// https://www.cocoawithlove.com/2008/03/break-into-debugger.html
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n": : : "memory","r0","r3","r4") // NOLINT(hicpp-no-assembler)
#else
-#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT (hicpp-no-assembler)
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT(hicpp-no-assembler)
#endif
#elif DOCTEST_MSVC
#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak()
// this is kept here for backwards compatibility since the config option was changed
#ifdef DOCTEST_CONFIG_USE_IOSFWD
+#ifndef DOCTEST_CONFIG_USE_STD_HEADERS
#define DOCTEST_CONFIG_USE_STD_HEADERS
+#endif
#endif // DOCTEST_CONFIG_USE_IOSFWD
+// for clang - always include ciso646 (which drags some std stuff) because
+// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in
+// which case we don't want to forward declare stuff from std - for reference:
+// https://github.com/doctest/doctest/issues/126
+// https://github.com/doctest/doctest/issues/356
+#if DOCTEST_CLANG
+#include <ciso646>
+#ifdef _LIBCPP_VERSION
+#ifndef DOCTEST_CONFIG_USE_STD_HEADERS
+#define DOCTEST_CONFIG_USE_STD_HEADERS
+#endif
+#endif // _LIBCPP_VERSION
+#endif // clang
+
#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>
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <cstddef>
#include <ostream>
+#include <istream>
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
#else // DOCTEST_CONFIG_USE_STD_HEADERS
-#if DOCTEST_CLANG
-// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier)
-#include <ciso646>
-#endif // clang
-
-#ifdef _LIBCPP_VERSION
-#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD
-#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD
-#else // _LIBCPP_VERSION
-#define DOCTEST_STD_NAMESPACE_BEGIN namespace std {
-#define DOCTEST_STD_NAMESPACE_END }
-#endif // _LIBCPP_VERSION
-
// Forward declaring 'X' in namespace std is not permitted by the C++ Standard.
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643)
-DOCTEST_STD_NAMESPACE_BEGIN // NOLINT (cert-dcl58-cpp)
-typedef decltype(nullptr) nullptr_t;
+namespace std { // NOLINT(cert-dcl58-cpp)
+typedef decltype(nullptr) nullptr_t; // NOLINT(modernize-use-using)
+typedef decltype(sizeof(void*)) size_t; // NOLINT(modernize-use-using)
template <class charT>
struct char_traits;
template <>
struct char_traits<char>;
template <class charT, class traits>
-class basic_ostream;
-typedef basic_ostream<char, char_traits<char>> ostream;
+class basic_ostream; // NOLINT(fuchsia-virtual-inheritance)
+typedef basic_ostream<char, char_traits<char>> ostream; // NOLINT(modernize-use-using)
+template<class traits>
+// NOLINTNEXTLINE
+basic_ostream<char, traits>& operator<<(basic_ostream<char, traits>&, const char*);
+template <class charT, class traits>
+class basic_istream;
+typedef basic_istream<char, char_traits<char>> istream; // NOLINT(modernize-use-using)
template <class... Types>
class tuple;
#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
-// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183
-template <class _Ty>
+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183
+template <class Ty>
class allocator;
-template <class _Elem, class _Traits, class _Alloc>
+template <class Elem, class Traits, class Alloc>
class basic_string;
using string = basic_string<char, char_traits<char>, allocator<char>>;
#endif // VS 2019
-DOCTEST_STD_NAMESPACE_END
+} // namespace std
DOCTEST_MSVC_SUPPRESS_WARNING_POP
namespace doctest {
+using std::size_t;
+
DOCTEST_INTERFACE extern bool is_running_in_test;
+#ifndef DOCTEST_CONFIG_STRING_SIZE_TYPE
+#define DOCTEST_CONFIG_STRING_SIZE_TYPE unsigned
+#endif
+
// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length
// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for:
// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128)
// TODO:
// - optimizations - like not deleting memory unnecessarily in operator= and etc.
// - resize/reserve/clear
-// - substr
// - replace
// - back/front
// - iterator stuff
// - relational operators as free functions - taking const char* as one of the params
class DOCTEST_INTERFACE String
{
- static const unsigned len = 24; //!OCLINT avoid private static members
- static const unsigned last = len - 1; //!OCLINT avoid private static members
+public:
+ using size_type = DOCTEST_CONFIG_STRING_SIZE_TYPE;
+
+private:
+ static DOCTEST_CONSTEXPR size_type len = 24; //!OCLINT avoid private static members
+ static DOCTEST_CONSTEXPR size_type last = len - 1; //!OCLINT avoid private static members
struct view // len should be more than sizeof(view) - because of the final byte for flags
{
char* ptr;
- unsigned size;
- unsigned capacity;
+ size_type size;
+ size_type capacity;
};
union
{
- char buf[len];
+ char buf[len]; // NOLINT(*-avoid-c-arrays)
view data;
};
- bool isOnStack() const { return (buf[last] & 128) == 0; }
- void setOnHeap();
- void setLast(unsigned in = last);
+ char* allocate(size_type sz);
+
+ bool isOnStack() const noexcept { return (buf[last] & 128) == 0; }
+ void setOnHeap() noexcept;
+ void setLast(size_type in = last) noexcept;
+ void setSize(size_type sz) noexcept;
void copy(const String& other);
public:
- String();
+ static DOCTEST_CONSTEXPR size_type npos = static_cast<size_type>(-1);
+
+ String() noexcept;
~String();
// cppcheck-suppress noExplicitConstructor
String(const char* in);
- String(const char* in, unsigned in_size);
+ String(const char* in, size_type in_size);
+
+ String(std::istream& in, size_type in_size);
String(const String& other);
String& operator=(const String& other);
String& operator+=(const String& other);
- String operator+(const String& other) const;
- String(String&& other);
- String& operator=(String&& other);
+ String(String&& other) noexcept;
+ String& operator=(String&& other) noexcept;
- char operator[](unsigned i) const;
- char& operator[](unsigned i);
+ char operator[](size_type i) const;
+ char& operator[](size_type i);
// the only functions I'm willing to leave in the interface - available for inlining
const char* c_str() const { return const_cast<String*>(this)->c_str(); } // NOLINT
char* c_str() {
- if(isOnStack())
+ if (isOnStack()) {
return reinterpret_cast<char*>(buf);
+ }
return data.ptr;
}
- unsigned size() const;
- unsigned capacity() const;
+ size_type size() const;
+ size_type capacity() const;
+
+ String substr(size_type pos, size_type cnt = npos) &&;
+ String substr(size_type pos, size_type cnt = npos) const &;
+
+ size_type find(char ch, size_type pos = 0) const;
+ size_type rfind(char ch, size_type pos = npos) const;
int compare(const char* other, bool no_case = false) const;
int compare(const String& other, bool no_case = false) const;
+
+friend DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in);
};
+DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs);
+
DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs);
DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs);
DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs);
DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs);
DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs);
-DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in);
+class DOCTEST_INTERFACE Contains {
+public:
+ explicit Contains(const String& string);
+
+ bool checkWith(const String& other) const;
+
+ String string;
+};
+
+DOCTEST_INTERFACE String toString(const Contains& in);
+
+DOCTEST_INTERFACE bool operator==(const String& lhs, const Contains& rhs);
+DOCTEST_INTERFACE bool operator==(const Contains& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator!=(const String& lhs, const Contains& rhs);
+DOCTEST_INTERFACE bool operator!=(const Contains& lhs, const String& rhs);
namespace Color {
enum Enum
DT_WARN_THROWS_WITH = is_throws_with | is_warn,
DT_CHECK_THROWS_WITH = is_throws_with | is_check,
DT_REQUIRE_THROWS_WITH = is_throws_with | is_require,
-
+
DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn,
DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check,
DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require,
String m_decomp;
// for specific exception-related asserts
- bool m_threw_as;
- const char* m_exception_type;
- const char* m_exception_string;
+ bool m_threw_as;
+ const char* m_exception_type;
+
+ class DOCTEST_INTERFACE StringContains {
+ private:
+ Contains content;
+ bool isContains;
+
+ public:
+ StringContains(const String& str) : content(str), isContains(false) { }
+ StringContains(Contains cntn) : content(static_cast<Contains&&>(cntn)), isContains(true) { }
+
+ bool check(const String& str) { return isContains ? (content == str) : (content.string == str); }
+
+ operator const String&() const { return content.string; }
+
+ const char* c_str() const { return content.string.c_str(); }
+ } m_exception_string;
+
+ AssertData(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const StringContains& exception_string);
};
struct DOCTEST_INTERFACE MessageData
const char* m_file;
int m_line;
+ bool operator==(const SubcaseSignature& other) const;
bool operator<(const SubcaseSignature& other) const;
};
struct DOCTEST_INTERFACE IContextScope
{
- IContextScope();
- virtual ~IContextScope();
+ DOCTEST_DECLARE_INTERFACE(IContextScope)
virtual void stringify(std::ostream*) const = 0;
};
struct ContextOptions //!OCLINT too many fields
{
- std::ostream* cout; // stdout stream - std::cout by default
- std::ostream* cerr; // stderr stream - std::cerr by default
- String binary_name; // the test binary name
+ std::ostream* cout = nullptr; // stdout stream
+ String binary_name; // the test binary name
const detail::TestCase* currentTest = nullptr;
bool case_sensitive; // if filtering should be case sensitive
bool exit; // if the program should be exited after the tests are ran/whatever
bool duration; // print the time duration of each test case
+ bool minimal; // minimal console output (only test failures)
+ bool quiet; // no console output
bool no_throw; // to skip exceptions-related assertion macros
bool no_exitcode; // if the framework should return 0 as the exitcode
bool no_run; // to not run the tests at all (can be done with an "*" exclude)
+ bool no_intro; // to not print the intro of the framework
bool no_version; // to not print the version of the framework
bool no_colors; // if output to the console should be colorized
bool force_colors; // forces the use of colors even when a tty cannot be detected
};
namespace detail {
- template <bool CONDITION, typename TYPE = void>
- struct enable_if
- {};
+ namespace types {
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+ using namespace std;
+#else
+ template <bool COND, typename T = void>
+ struct enable_if { };
- template <typename TYPE>
- struct enable_if<true, TYPE>
- { typedef TYPE type; };
+ template <typename T>
+ struct enable_if<true, T> { using type = T; };
- // clang-format off
- template<class T> struct remove_reference { typedef T type; };
- template<class T> struct remove_reference<T&> { typedef T type; };
- template<class T> struct remove_reference<T&&> { typedef T type; };
+ struct true_type { static DOCTEST_CONSTEXPR bool value = true; };
+ struct false_type { static DOCTEST_CONSTEXPR bool value = false; };
+
+ template <typename T> struct remove_reference { using type = T; };
+ template <typename T> struct remove_reference<T&> { using type = T; };
+ template <typename T> struct remove_reference<T&&> { using type = T; };
+
+ template <typename T> struct is_rvalue_reference : false_type { };
+ template <typename T> struct is_rvalue_reference<T&&> : true_type { };
- template<typename T, typename U = T&&> U declval(int);
+ template<typename T> struct remove_const { using type = T; };
+ template <typename T> struct remove_const<const T> { using type = T; };
- template<typename T> T declval(long);
+ // Compiler intrinsics
+ template <typename T> struct is_enum { static DOCTEST_CONSTEXPR bool value = __is_enum(T); };
+ template <typename T> struct underlying_type { using type = __underlying_type(T); };
- template<typename T> auto declval() DOCTEST_NOEXCEPT -> decltype(declval<T>(0)) ;
+ template <typename T> struct is_pointer : false_type { };
+ template <typename T> struct is_pointer<T*> : true_type { };
- template<class T> struct is_lvalue_reference { const static bool value=false; };
- template<class T> struct is_lvalue_reference<T&> { const static bool value=true; };
+ template <typename T> struct is_array : false_type { };
+ // NOLINTNEXTLINE(*-avoid-c-arrays)
+ template <typename T, size_t SIZE> struct is_array<T[SIZE]> : true_type { };
+#endif
+ }
+
+ // <utility>
+ template <typename T>
+ T&& declval();
template <class T>
- inline T&& forward(typename remove_reference<T>::type& t) DOCTEST_NOEXCEPT
- {
+ DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference<T>::type& t) DOCTEST_NOEXCEPT {
return static_cast<T&&>(t);
}
template <class T>
- inline T&& forward(typename remove_reference<T>::type&& t) DOCTEST_NOEXCEPT
- {
- static_assert(!is_lvalue_reference<T>::value,
- "Can not forward an rvalue as an lvalue.");
+ DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference<T>::type&& t) DOCTEST_NOEXCEPT {
return static_cast<T&&>(t);
}
- 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>
+ struct deferred_false : types::false_type { };
+
+// MSVS 2015 :(
+#if defined(_MSC_VER) && _MSC_VER <= 1900
+ template <typename T, typename = void>
+ struct has_global_insertion_operator : types::false_type { };
template <typename T>
- struct deferred_false
- // cppcheck-suppress unusedStructMember
- { static const bool value = false; };
-
- namespace has_insertion_operator_impl {
- std::ostream &os();
- template<class T>
- DOCTEST_REF_WRAP(T) val();
-
- template<class, class = void>
- struct check {
- static constexpr bool value = false;
- };
+ struct has_global_insertion_operator<T, decltype(::operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { };
- template<class T>
- struct check<T, decltype(os() << val<T>(), void())> {
- static constexpr bool value = true;
- };
- } // namespace has_insertion_operator_impl
+ template <typename T, typename = void>
+ struct has_insertion_operator { static DOCTEST_CONSTEXPR bool value = has_global_insertion_operator<T>::value; };
- template<class T>
- using has_insertion_operator = has_insertion_operator_impl::check<const T>;
+ template <typename T, bool global>
+ struct insert_hack;
- DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num);
+ template <typename T>
+ struct insert_hack<T, true> {
+ static void insert(std::ostream& os, const T& t) { ::operator<<(os, t); }
+ };
+
+ template <typename T>
+ struct insert_hack<T, false> {
+ static void insert(std::ostream& os, const T& t) { operator<<(os, t); }
+ };
+
+ template <typename T>
+ using insert_hack_t = insert_hack<T, has_global_insertion_operator<T>::value>;
+#else
+ template <typename T, typename = void>
+ struct has_insertion_operator : types::false_type { };
+#endif
+
+template <typename T>
+struct has_insertion_operator<T, decltype(operator<<(declval<std::ostream&>(), declval<const T&>()), void())> : types::true_type { };
- DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream
- DOCTEST_INTERFACE String getTlsOssResult();
+ DOCTEST_INTERFACE std::ostream* tlssPush();
+ DOCTEST_INTERFACE String tlssPop();
template <bool C>
- struct StringMakerBase
- {
+ struct StringMakerBase {
template <typename T>
static String convert(const DOCTEST_REF_WRAP(T)) {
+#ifdef DOCTEST_CONFIG_REQUIRE_STRINGIFICATION_FOR_ALL_USED_TYPES
+ static_assert(deferred_false<T>::value, "No stringification detected for type T. See string conversion manual");
+#endif
return "{?}";
}
};
- template <>
- struct StringMakerBase<true>
- {
- template <typename T>
- static String convert(const DOCTEST_REF_WRAP(T) in) {
- *getTlsOss() << in;
- return getTlsOssResult();
- }
- };
-
- DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size);
+ template <typename T>
+ struct filldata;
template <typename T>
- String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) {
- return rawMemoryToString(&object, sizeof(object));
+ void filloss(std::ostream* stream, const T& in) {
+ filldata<T>::fill(stream, in);
+ }
+
+ template <typename T, size_t N>
+ void filloss(std::ostream* stream, const T (&in)[N]) { // NOLINT(*-avoid-c-arrays)
+ // T[N], T(&)[N], T(&&)[N] have same behaviour.
+ // Hence remove reference.
+ filloss<typename types::remove_reference<decltype(in)>::type>(stream, in);
}
template <typename T>
- const char* type_to_string() {
- return "<>";
+ String toStream(const T& in) {
+ std::ostream* stream = tlssPush();
+ filloss(stream, in);
+ return tlssPop();
}
+
+ template <>
+ struct StringMakerBase<true> {
+ template <typename T>
+ static String convert(const DOCTEST_REF_WRAP(T) in) {
+ return toStream(in);
+ }
+ };
} // namespace detail
template <typename T>
-struct StringMaker : public detail::StringMakerBase<detail::has_insertion_operator<T>::value>
+struct StringMaker : public detail::StringMakerBase<
+ detail::has_insertion_operator<T>::value || detail::types::is_pointer<T>::value || detail::types::is_array<T>::value>
{};
-template <typename T>
-struct StringMaker<T*>
-{
- template <typename U>
- static String convert(U* p) {
- if(p)
- return detail::rawMemoryToString(p);
- return "NULL";
- }
-};
+#ifndef DOCTEST_STRINGIFY
+#ifdef DOCTEST_CONFIG_DOUBLE_STRINGIFY
+#define DOCTEST_STRINGIFY(...) toString(toString(__VA_ARGS__))
+#else
+#define DOCTEST_STRINGIFY(...) toString(__VA_ARGS__)
+#endif
+#endif
-template <typename R, typename C>
-struct StringMaker<R C::*>
-{
- static String convert(R C::*p) {
- if(p)
- return detail::rawMemoryToString(p);
- return "NULL";
- }
-};
+template <typename T>
+String toString() {
+#if DOCTEST_MSVC >= 0 && DOCTEST_CLANG == 0 && DOCTEST_GCC == 0
+ String ret = __FUNCSIG__; // class doctest::String __cdecl doctest::toString<TYPE>(void)
+ String::size_type beginPos = ret.find('<');
+ return ret.substr(beginPos + 1, ret.size() - beginPos - static_cast<String::size_type>(sizeof(">(void)")));
+#else
+ String ret = __PRETTY_FUNCTION__; // doctest::String toString() [with T = TYPE]
+ String::size_type begin = ret.find('=') + 2;
+ return ret.substr(begin, ret.size() - begin - 1);
+#endif
+}
-template <typename T, typename detail::enable_if<!detail::is_enum<T>::value, bool>::type = true>
+template <typename T, typename detail::types::enable_if<!detail::types::is_enum<T>::value, bool>::type = true>
String toString(const DOCTEST_REF_WRAP(T) value) {
return StringMaker<T>::convert(value);
}
#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
-DOCTEST_INTERFACE String toString(char* in);
DOCTEST_INTERFACE String toString(const char* in);
#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+
+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183
+DOCTEST_INTERFACE String toString(const std::string& in);
+#endif // VS 2019
+
+DOCTEST_INTERFACE String toString(String in);
+
+DOCTEST_INTERFACE String toString(std::nullptr_t);
+
DOCTEST_INTERFACE String toString(bool in);
+
DOCTEST_INTERFACE String toString(float in);
DOCTEST_INTERFACE String toString(double in);
DOCTEST_INTERFACE String toString(double long in);
DOCTEST_INTERFACE String toString(char in);
DOCTEST_INTERFACE String toString(char signed in);
DOCTEST_INTERFACE String toString(char unsigned in);
-DOCTEST_INTERFACE String toString(int short in);
-DOCTEST_INTERFACE String toString(int short unsigned in);
-DOCTEST_INTERFACE String toString(int in);
-DOCTEST_INTERFACE String toString(int unsigned in);
-DOCTEST_INTERFACE String toString(int long in);
-DOCTEST_INTERFACE String toString(int long unsigned in);
-DOCTEST_INTERFACE String toString(int long long in);
-DOCTEST_INTERFACE String toString(int long long unsigned in);
-DOCTEST_INTERFACE String toString(std::nullptr_t in);
-
-template <typename T, typename detail::enable_if<detail::is_enum<T>::value, bool>::type = true>
+DOCTEST_INTERFACE String toString(short in);
+DOCTEST_INTERFACE String toString(short unsigned in);
+DOCTEST_INTERFACE String toString(signed in);
+DOCTEST_INTERFACE String toString(unsigned in);
+DOCTEST_INTERFACE String toString(long in);
+DOCTEST_INTERFACE String toString(long unsigned in);
+DOCTEST_INTERFACE String toString(long long in);
+DOCTEST_INTERFACE String toString(long long unsigned in);
+
+template <typename T, typename detail::types::enable_if<detail::types::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));
+ using UT = typename detail::types::underlying_type<T>::type;
+ return (DOCTEST_STRINGIFY(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);
-#endif // VS 2019
+namespace detail {
+ template <typename T>
+ struct filldata
+ {
+ static void fill(std::ostream* stream, const T& in) {
+#if defined(_MSC_VER) && _MSC_VER <= 1900
+ insert_hack_t<T>::insert(*stream, in);
+#else
+ operator<<(*stream, in);
+#endif
+ }
+ };
+
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866)
+// NOLINTBEGIN(*-avoid-c-arrays)
+ template <typename T, size_t N>
+ struct filldata<T[N]> {
+ static void fill(std::ostream* stream, const T(&in)[N]) {
+ *stream << "[";
+ for (size_t i = 0; i < N; i++) {
+ if (i != 0) { *stream << ", "; }
+ *stream << (DOCTEST_STRINGIFY(in[i]));
+ }
+ *stream << "]";
+ }
+ };
+// NOLINTEND(*-avoid-c-arrays)
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
-class DOCTEST_INTERFACE Approx
+ // Specialized since we don't want the terminating null byte!
+// NOLINTBEGIN(*-avoid-c-arrays)
+ template <size_t N>
+ struct filldata<const char[N]> {
+ static void fill(std::ostream* stream, const char (&in)[N]) {
+ *stream << String(in, in[N - 1] ? N : N - 1);
+ } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
+ };
+// NOLINTEND(*-avoid-c-arrays)
+
+ template <>
+ struct filldata<const void*> {
+ static void fill(std::ostream* stream, const void* in);
+ };
+
+ template <typename T>
+ struct filldata<T*> {
+ static void fill(std::ostream* stream, const T* in) {
+ filldata<const void*>::fill(stream, in);
+ }
+ };
+}
+
+struct DOCTEST_INTERFACE Approx
{
-public:
- explicit Approx(double value);
+ Approx(double value);
Approx operator()(double value) const;
#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
template <typename T>
explicit Approx(const T& value,
- typename detail::enable_if<std::is_constructible<double, T>::value>::type* =
+ typename detail::types::enable_if<std::is_constructible<double, T>::value>::type* =
static_cast<T*>(nullptr)) {
- *this = Approx(static_cast<double>(value));
+ *this = static_cast<double>(value);
}
#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
template <typename T>
- typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon(
+ typename std::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon(
const T& newEpsilon) {
m_epsilon = static_cast<double>(newEpsilon);
return *this;
#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
template <typename T>
- typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale(
+ typename std::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale(
const T& newScale) {
m_scale = static_cast<double>(newScale);
return *this;
DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs);
DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs);
- DOCTEST_INTERFACE friend String toString(const Approx& in);
-
#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#define DOCTEST_APPROX_PREFIX \
- template <typename T> friend typename detail::enable_if<std::is_constructible<double, T>::value, bool>::type
+ template <typename T> friend typename std::enable_if<std::is_constructible<double, T>::value, bool>::type
- DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); }
+ DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(static_cast<double>(lhs), rhs); }
DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); }
DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); }
DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); }
- DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; }
- DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; }
- DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; }
- DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; }
- DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; }
- DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; }
- DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; }
- DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; }
+ DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; }
+ DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) < rhs.m_value && lhs != rhs; }
+ DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast<double>(rhs) && lhs != rhs; }
+ DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return static_cast<double>(lhs) > rhs.m_value && lhs != rhs; }
+ DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast<double>(rhs) && lhs != rhs; }
#undef DOCTEST_APPROX_PREFIX
#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
// clang-format on
-private:
double m_epsilon;
double m_scale;
double m_value;
DOCTEST_INTERFACE const ContextOptions* getContextOptions();
-#if !defined(DOCTEST_CONFIG_DISABLE)
+template <typename F>
+struct DOCTEST_INTERFACE_DECL IsNaN
+{
+ F value; bool flipped;
+ IsNaN(F f, bool flip = false) : value(f), flipped(flip) { }
+ IsNaN<F> operator!() const { return { value, !flipped }; }
+ operator bool() const;
+};
+#ifndef __MINGW32__
+extern template struct DOCTEST_INTERFACE_DECL IsNaN<float>;
+extern template struct DOCTEST_INTERFACE_DECL IsNaN<double>;
+extern template struct DOCTEST_INTERFACE_DECL IsNaN<long double>;
+#endif
+DOCTEST_INTERFACE String toString(IsNaN<float> in);
+DOCTEST_INTERFACE String toString(IsNaN<double> in);
+DOCTEST_INTERFACE String toString(IsNaN<double long> in);
+
+#ifndef DOCTEST_CONFIG_DISABLE
namespace detail {
// clang-format off
#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
- template<class T> struct decay_array { typedef T type; };
- template<class T, unsigned N> struct decay_array<T[N]> { typedef T* type; };
- template<class T> struct decay_array<T[]> { typedef T* type; };
+ template<class T> struct decay_array { using type = T; };
+ template<class T, unsigned N> struct decay_array<T[N]> { using type = T*; };
+ template<class T> struct decay_array<T[]> { using type = T*; };
- template<class T> struct not_char_pointer { enum { value = 1 }; };
- template<> struct not_char_pointer<char*> { enum { value = 0 }; };
- template<> struct not_char_pointer<const char*> { enum { value = 0 }; };
+ template<class T> struct not_char_pointer { static DOCTEST_CONSTEXPR value = 1; };
+ template<> struct not_char_pointer<char*> { static DOCTEST_CONSTEXPR value = 0; };
+ template<> struct not_char_pointer<const char*> { static DOCTEST_CONSTEXPR value = 0; };
template<class T> struct can_use_op : public not_char_pointer<typename decay_array<T>::type> {};
#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
bool m_entered = false;
Subcase(const String& name, const char* file, int line);
+ Subcase(const Subcase&) = delete;
+ Subcase(Subcase&&) = delete;
+ Subcase& operator=(const Subcase&) = delete;
+ Subcase& operator=(Subcase&&) = delete;
~Subcase();
operator bool() const;
+
+ private:
+ bool checkFilters();
};
template <typename L, typename R>
String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op,
const DOCTEST_REF_WRAP(R) rhs) {
- // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
- return toString(lhs) + op + toString(rhs);
+ return (DOCTEST_STRINGIFY(lhs)) + op + (DOCTEST_STRINGIFY(rhs));
}
#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0)
// If not it doesn't find the operator or if the operator at global scope is defined after
// this template, the template won't be instantiated due to SFINAE. Once the template is not
// instantiated it can look for global operator using normal conversions.
-#define SFINAE_OP(ret,op) decltype(doctest::detail::declval<L>() op doctest::detail::declval<R>(),static_cast<ret>(0))
+#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval<L>() op doctest::detail::declval<R>()),ret{})
#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \
template <typename R> \
- DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \
- bool res = op_macro(doctest::detail::forward<L>(lhs), doctest::detail::forward<R>(rhs)); \
+ DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \
+ bool res = op_macro(doctest::detail::forward<const L>(lhs), doctest::detail::forward<R>(rhs)); \
if(m_at & assertType::is_false) \
res = !res; \
if(!res || doctest::getContextOptions()->success) \
return *this; \
}
- struct DOCTEST_INTERFACE Result
+ struct DOCTEST_INTERFACE Result // NOLINT(*-member-init)
{
bool m_passed;
String m_decomp;
+ Result() = default; // TODO: Why do we need this? (To remove NOLINT)
Result(bool passed, const String& decomposition = String());
// forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence
#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
#define DOCTEST_COMPARISON_RETURN_TYPE bool
#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
-#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type
- // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+#define DOCTEST_COMPARISON_RETURN_TYPE typename types::enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type
inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); }
inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); }
inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); }
assertType::Enum m_at;
explicit Expression_lhs(L&& in, assertType::Enum at)
- : lhs(doctest::detail::forward<L>(in))
+ : lhs(static_cast<L&&>(in))
, m_at(at) {}
DOCTEST_NOINLINE operator Result() {
-// this is needed only foc MSVC 2015:
-// https://ci.appveyor.com/project/onqtam/doctest/builds/38181202
+// this is needed only for MSVC 2015
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool
bool res = static_cast<bool>(lhs);
DOCTEST_MSVC_SUPPRESS_WARNING_POP
- if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
+ if(m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional
res = !res;
+ }
- if(!res || getContextOptions()->success)
- return Result(res, toString(lhs));
- return Result(res);
+ if(!res || getContextOptions()->success) {
+ return { res, (DOCTEST_STRINGIFY(lhs)) };
+ }
+ return { res };
}
- /* This is required for user-defined conversions from Expression_lhs to L */
- //operator L() const { return lhs; }
- operator L() const { return lhs; }
+ /* This is required for user-defined conversions from Expression_lhs to L */
+ operator L() const { return lhs; }
// clang-format off
DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional
// https://github.com/catchorg/Catch2/issues/870
// https://github.com/catchorg/Catch2/issues/565
template <typename L>
- Expression_lhs<L> operator<<(L &&operand) {
- return Expression_lhs<L>(doctest::detail::forward<L>(operand), m_at);
+ Expression_lhs<L> operator<<(L&& operand) {
+ return Expression_lhs<L>(static_cast<L&&>(operand), m_at);
+ }
+
+ template <typename L,typename types::enable_if<!doctest::detail::types::is_rvalue_reference<L>::value,void >::type* = nullptr>
+ Expression_lhs<const L&> operator<<(const L &operand) {
+ return Expression_lhs<const L&>(operand, m_at);
}
};
struct DOCTEST_INTERFACE TestSuite
{
- const char* m_test_suite;
- const char* m_description;
- bool m_skip;
- bool m_no_breaks;
- bool m_no_output;
- bool m_may_fail;
- bool m_should_fail;
- int m_expected_failures;
- double m_timeout;
+ const char* m_test_suite = nullptr;
+ const char* m_description = nullptr;
+ bool m_skip = false;
+ bool m_no_breaks = false;
+ bool m_no_output = false;
+ bool m_may_fail = false;
+ bool m_should_fail = false;
+ int m_expected_failures = 0;
+ double m_timeout = 0;
TestSuite& operator*(const char* in);
}
};
- typedef void (*funcType)();
+ using funcType = void (*)();
struct DOCTEST_INTERFACE TestCase : public TestCaseData
{
funcType m_test; // a function pointer to the test case
- const char* m_type; // for templated test cases - gets appended to the real name
+ String m_type; // for templated test cases - gets appended to the real name
int m_template_id; // an ID used to distinguish between the different versions of a templated test case
String m_full_name; // contains the name (only for templated test cases!) + the template type
TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
- const char* type = "", int template_id = -1);
+ const String& type = String(), int template_id = -1);
TestCase(const TestCase& other);
+ TestCase(TestCase&&) = delete;
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function
TestCase& operator=(const TestCase& other);
DOCTEST_MSVC_SUPPRESS_WARNING_POP
+ TestCase& operator=(TestCase&&) = delete;
+
TestCase& operator*(const char* in);
template <typename T>
}
bool operator<(const TestCase& other) const;
+
+ ~TestCase() = default;
};
// forward declarations of functions used by the macros
struct DOCTEST_INTERFACE ResultBuilder : public AssertData
{
ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
- const char* exception_type = "", const char* exception_string = "");
+ const char* exception_type = "", const String& exception_string = "");
+
+ ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const Contains& exception_string);
void setResult(const Result& res);
template <int comparison, typename L, typename R>
- DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs,
+ DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs,
const DOCTEST_REF_WRAP(R) rhs) {
m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
- if(m_failed || getContextOptions()->success)
+ if (m_failed || getContextOptions()->success) {
m_decomp = stringifyBinaryExpr(lhs, ", ", rhs);
+ }
+ return !m_failed;
}
template <typename L>
- DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) {
+ DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) {
m_failed = !val;
- if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
+ if (m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional
m_failed = !m_failed;
+ }
+
+ if (m_failed || getContextOptions()->success) {
+ m_decomp = (DOCTEST_STRINGIFY(val));
+ }
- if(m_failed || getContextOptions()->success)
- m_decomp = toString(val);
+ return !m_failed;
}
void translateException();
DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad);
- DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line,
- const char* expr, Result result);
+ DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line,
+ const char* expr, const Result& result);
#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \
do { \
if(checkIfShouldThrow(at)) \
throwException(); \
} \
- return; \
+ return !failed; \
} \
} while(false)
throwException()
template <int comparison, typename L, typename R>
- DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line,
+ DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line,
const char* expr, const DOCTEST_REF_WRAP(L) lhs,
const DOCTEST_REF_WRAP(R) rhs) {
bool failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
// ###################################################################################
DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs));
DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs));
+ return !failed;
}
template <typename L>
- DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line,
+ DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line,
const char* expr, const DOCTEST_REF_WRAP(L) val) {
bool failed = !val;
// IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
// THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
// ###################################################################################
- DOCTEST_ASSERT_OUT_OF_TESTS(toString(val));
- DOCTEST_ASSERT_IN_TESTS(toString(val));
+ DOCTEST_ASSERT_OUT_OF_TESTS((DOCTEST_STRINGIFY(val)));
+ DOCTEST_ASSERT_IN_TESTS((DOCTEST_STRINGIFY(val)));
+ return !failed;
}
struct DOCTEST_INTERFACE IExceptionTranslator
{
- IExceptionTranslator();
- virtual ~IExceptionTranslator();
+ DOCTEST_DECLARE_INTERFACE(IExceptionTranslator)
virtual bool translate(String&) const = 0;
};
try {
throw; // lgtm [cpp/rethrow-no-exception]
// cppcheck-suppress catchExceptionByValue
- } catch(T ex) { // NOLINT
+ } catch(const T& ex) {
res = m_translateFunction(ex); //!OCLINT parameter reassignment
return true;
} catch(...) {} //!OCLINT - empty catch statement
DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et);
- template <bool C>
- struct StringStreamBase
- {
- template <typename T>
- static void convert(std::ostream* s, const T& in) {
- *s << toString(in);
- }
-
- // always treat char* as a string in this context - no matter
- // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined
- static void convert(std::ostream* s, const char* in) { *s << String(in); }
- };
+ // ContextScope base class used to allow implementing methods of ContextScope
+ // that don't depend on the template parameter in doctest.cpp.
+ struct DOCTEST_INTERFACE ContextScopeBase : public IContextScope {
+ ContextScopeBase(const ContextScopeBase&) = delete;
- template <>
- struct StringStreamBase<true>
- {
- template <typename T>
- static void convert(std::ostream* s, const T& in) {
- *s << in;
- }
- };
+ ContextScopeBase& operator=(const ContextScopeBase&) = delete;
+ ContextScopeBase& operator=(ContextScopeBase&&) = delete;
- template <typename T>
- struct StringStream : public StringStreamBase<has_insertion_operator<T>::value>
- {};
+ ~ContextScopeBase() override = default;
- template <typename T>
- void toStream(std::ostream* s, const T& value) {
- StringStream<T>::convert(s, value);
- }
-
-#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
- DOCTEST_INTERFACE void toStream(std::ostream* s, char* in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in);
-#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
- DOCTEST_INTERFACE void toStream(std::ostream* s, bool in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, float in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, double in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, double long in);
-
- DOCTEST_INTERFACE void toStream(std::ostream* s, char in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, int short in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, int in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, int long in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in);
- DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in);
-
- // ContextScope base class used to allow implementing methods of ContextScope
- // that don't depend on the template parameter in doctest.cpp.
- class DOCTEST_INTERFACE ContextScopeBase : public IContextScope {
protected:
ContextScopeBase();
+ ContextScopeBase(ContextScopeBase&& other) noexcept;
void destroy();
+ bool need_to_destroy{true};
};
template <typename L> class ContextScope : public ContextScopeBase
{
- const L lambda_;
+ L lambda_;
public:
explicit ContextScope(const L &lambda) : lambda_(lambda) {}
+ explicit ContextScope(L&& lambda) : lambda_(static_cast<L&&>(lambda)) { }
+
+ ContextScope(const ContextScope&) = delete;
+ ContextScope(ContextScope&&) noexcept = default;
- ContextScope(ContextScope &&other) : lambda_(other.lambda_) {}
+ ContextScope& operator=(const ContextScope&) = delete;
+ ContextScope& operator=(ContextScope&&) = delete;
void stringify(std::ostream* s) const override { lambda_(s); }
- ~ContextScope() override { destroy(); }
+ ~ContextScope() override {
+ if (need_to_destroy) {
+ destroy();
+ }
+ }
};
struct DOCTEST_INTERFACE MessageBuilder : public MessageData
{
std::ostream* m_stream;
+ bool logged = false;
MessageBuilder(const char* file, int line, assertType::Enum severity);
- MessageBuilder() = delete;
+
+ MessageBuilder(const MessageBuilder&) = delete;
+ MessageBuilder(MessageBuilder&&) = delete;
+
+ MessageBuilder& operator=(const MessageBuilder&) = delete;
+ MessageBuilder& operator=(MessageBuilder&&) = delete;
+
~MessageBuilder();
// the preferred way of chaining parameters for stringification
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866)
template <typename T>
MessageBuilder& operator,(const T& in) {
- toStream(m_stream, in);
+ *m_stream << (DOCTEST_STRINGIFY(in));
return *this;
}
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
// kept here just for backwards-compatibility - the comma operator should be preferred now
template <typename T>
bool log();
void react();
};
-
+
template <typename L>
ContextScope<L> MakeContextScope(const L &lambda) {
return ContextScope<L>(lambda);
#endif // DOCTEST_CONFIG_DISABLE
namespace detail {
- typedef void (*assert_handler)(const AssertData&);
+ using assert_handler = void (*)(const AssertData&);
struct ContextState;
} // namespace detail
public:
explicit Context(int argc = 0, const char* const* argv = nullptr);
- ~Context();
+ Context(const Context&) = delete;
+ Context(Context&&) = delete;
+
+ Context& operator=(const Context&) = delete;
+ Context& operator=(Context&&) = delete;
+
+ ~Context(); // NOLINT(performance-trivially-destructible)
void applyCommandLine(int argc, const char* const* argv);
void addFilter(const char* filter, const char* value);
void clearFilters();
+ void setOption(const char* option, bool value);
void setOption(const char* option, int value);
void setOption(const char* option, const char* value);
void setAssertHandler(detail::assert_handler ah);
+ void setCout(std::ostream* out);
+
int run();
};
int numAssertsFailedCurrentTest;
double seconds;
int failure_flags; // use TestCaseFailureReason::Enum
+ bool testCaseSuccess;
};
struct DOCTEST_INTERFACE TestCaseException
// or isn't in the execution range (between first and last) (safe to cache a pointer to the input)
virtual void test_case_skipped(const TestCaseData&) = 0;
- // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have
- virtual ~IReporter();
+ DOCTEST_DECLARE_INTERFACE(IReporter)
// can obtain all currently active contexts and stringify them if one wishes to do so
static int get_num_active_contexts();
};
namespace detail {
- typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&);
+ using reporterCreatorFunc = IReporter* (*)(const ContextOptions&);
DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter);
}
} // namespace doctest
+#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES
+#define DOCTEST_FUNC_EMPTY [] { return false; }()
+#else
+#define DOCTEST_FUNC_EMPTY (void)0
+#endif
+
// if registering is not disabled
-#if !defined(DOCTEST_CONFIG_DISABLE)
+#ifndef DOCTEST_CONFIG_DISABLE
+
+#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES
+#define DOCTEST_FUNC_SCOPE_BEGIN [&]
+#define DOCTEST_FUNC_SCOPE_END ()
+#define DOCTEST_FUNC_SCOPE_RET(v) return v
+#else
+#define DOCTEST_FUNC_SCOPE_BEGIN do
+#define DOCTEST_FUNC_SCOPE_END while(false)
+#define DOCTEST_FUNC_SCOPE_RET(v) (void)0
+#endif
// common code in asserts - for convenience
-#define DOCTEST_ASSERT_LOG_AND_REACT(b) \
- if(b.log()) \
- DOCTEST_BREAK_INTO_DEBUGGER(); \
- b.react()
+#define DOCTEST_ASSERT_LOG_REACT_RETURN(b) \
+ if(b.log()) DOCTEST_BREAK_INTO_DEBUGGER(); \
+ b.react(); \
+ DOCTEST_FUNC_SCOPE_RET(!b.m_failed)
#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
#define DOCTEST_WRAP_IN_TRY(x) x;
#define DOCTEST_WRAP_IN_TRY(x) \
try { \
x; \
- } catch(...) { _DOCTEST_RB.translateException(); }
+ } catch(...) { DOCTEST_RB.translateException(); }
#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS
// registers the test by initializing a dummy var with a function
#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \
- global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \
+ global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT */ \
doctest::detail::regTest( \
doctest::detail::TestCase( \
f, __FILE__, __LINE__, \
doctest_detail_test_suite_ns::getCurrentTestSuite()) * \
- decorators); \
- DOCTEST_GLOBAL_NO_WARNINGS_END()
+ decorators))
#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \
- namespace { \
+ namespace { /* NOLINT */ \
struct der : public base \
{ \
void f(); \
}; \
- static void func() { \
+ static inline DOCTEST_NOINLINE void func() { \
der v; \
v.f(); \
} \
DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \
} \
- inline DOCTEST_NOINLINE void der::f()
+ inline DOCTEST_NOINLINE void der::f() // NOLINT(misc-definitions-in-headers)
#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \
static void f(); \
#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \
static doctest::detail::funcType proxy() { return f; } \
- DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators) \
+ DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators) \
static void f()
// for registering tests
#define DOCTEST_TEST_CASE(decorators) \
- DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators)
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators)
// for registering tests in classes - requires C++17 for inline variables!
-#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L)
+#if DOCTEST_CPLUSPLUS >= 201703L
#define DOCTEST_TEST_CASE_CLASS(decorators) \
- DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), \
- DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_), \
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), \
+ DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_), \
decorators)
#else // DOCTEST_TEST_CASE_CLASS
#define DOCTEST_TEST_CASE_CLASS(...) \
// for registering tests with a fixture
#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \
- DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \
- DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators)
+ DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c, \
+ DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators)
// for converting types to strings without the <typeinfo> header and demangling
-#define DOCTEST_TYPE_TO_STRING_IMPL(...) \
- template <> \
- inline const char* type_to_string<__VA_ARGS__>() { \
- return "<" #__VA_ARGS__ ">"; \
- }
-#define DOCTEST_TYPE_TO_STRING(...) \
- namespace doctest { namespace detail { \
- DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \
+#define DOCTEST_TYPE_TO_STRING_AS(str, ...) \
+ namespace doctest { \
+ template <> \
+ inline String toString<__VA_ARGS__>() { \
+ return str; \
} \
} \
- typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+ static_assert(true, "")
+
+#define DOCTEST_TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING_AS(#__VA_ARGS__, __VA_ARGS__)
#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \
template <typename T> \
static void func(); \
- namespace { \
+ namespace { /* NOLINT */ \
template <typename Tuple> \
struct iter; \
template <typename Type, typename... Rest> \
iter(const char* file, unsigned line, int index) { \
doctest::detail::regTest(doctest::detail::TestCase(func<Type>, file, line, \
doctest_detail_test_suite_ns::getCurrentTestSuite(), \
- doctest::detail::type_to_string<Type>(), \
+ doctest::toString<Type>(), \
int(line) * 1000 + index) \
* dec); \
iter<std::tuple<Rest...>>(file, line, index + 1); \
#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \
DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \
- DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_))
+ DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_))
#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \
- DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = \
- doctest::detail::instantiationHelper(DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0));\
- DOCTEST_GLOBAL_NO_WARNINGS_END()
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), /* NOLINT(cert-err58-cpp, fuchsia-statically-constructed-objects) */ \
+ doctest::detail::instantiationHelper( \
+ DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0)))
#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \
- DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \
- typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \
+ static_assert(true, "")
#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \
- DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \
- typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+ DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \
+ static_assert(true, "")
#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \
DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \
static void anon()
#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \
- DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__)
+ DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__)
// for subcases
#define DOCTEST_SUBCASE(name) \
- if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \
+ if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \
doctest::detail::Subcase(name, __FILE__, __LINE__))
// for grouping tests in test suites by using code blocks
#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \
namespace ns_name { namespace doctest_detail_test_suite_ns { \
- static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \
+ static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() noexcept { \
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \
DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \
namespace ns_name
#define DOCTEST_TEST_SUITE(decorators) \
- DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_))
+ DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_))
// for starting a testsuite block
#define DOCTEST_TEST_SUITE_BEGIN(decorators) \
- DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \
- doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \
- DOCTEST_GLOBAL_NO_WARNINGS_END() \
- typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \
+ doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators)) \
+ static_assert(true, "")
// for ending a testsuite block
#define DOCTEST_TEST_SUITE_END \
- DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \
- doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \
- DOCTEST_GLOBAL_NO_WARNINGS_END() \
- typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \
+ doctest::detail::setTestSuite(doctest::detail::TestSuite() * "")) \
+ using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int
// for registering exception translators
#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \
inline doctest::String translatorName(signature); \
- DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \
- doctest::registerExceptionTranslator(translatorName); \
- DOCTEST_GLOBAL_NO_WARNINGS_END() \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), /* NOLINT(cert-err58-cpp) */ \
+ doctest::registerExceptionTranslator(translatorName)) \
doctest::String translatorName(signature)
#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \
- DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \
+ DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \
signature)
// for registering reporters
#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \
- DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \
- doctest::registerReporter<reporter>(name, priority, true); \
- DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \
+ doctest::registerReporter<reporter>(name, priority, true)) \
+ static_assert(true, "")
// for registering listeners
#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \
- DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \
- doctest::registerReporter<reporter>(name, priority, false); \
- DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \
+ doctest::registerReporter<reporter>(name, priority, false)) \
+ static_assert(true, "")
-// for logging
+// clang-format off
+// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557
#define DOCTEST_INFO(...) \
- DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \
+ DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_), \
+ DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_), \
__VA_ARGS__)
+// clang-format on
#define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \
- auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \
+ auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \
[&](std::ostream* s_name) { \
doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \
mb_name.m_stream = s_name; \
#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x)
#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \
- do { \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \
mb * __VA_ARGS__; \
- DOCTEST_ASSERT_LOG_AND_REACT(mb); \
- } while(false)
+ if(mb.log()) \
+ DOCTEST_BREAK_INTO_DEBUGGER(); \
+ mb.react(); \
+ } DOCTEST_FUNC_SCOPE_END
// clang-format off
-#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__)
-#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__)
-#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__)
+#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)
+#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)
+#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)
// clang-format on
#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__)
#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
__LINE__, #__VA_ARGS__); \
- DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \
+ DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult( \
doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \
- << __VA_ARGS__)) \
- DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \
+ << __VA_ARGS__)) /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB) \
DOCTEST_CLANG_SUPPRESS_WARNING_POP
#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \
- do { \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \
- } while(false)
+ } DOCTEST_FUNC_SCOPE_END // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
+
+#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ DOCTEST_WRAP_IN_TRY( \
+ DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \
+ __VA_ARGS__)) \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ } DOCTEST_FUNC_SCOPE_END
+
+#define DOCTEST_UNARY_ASSERT(assert_type, ...) \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__)) \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ } DOCTEST_FUNC_SCOPE_END
#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \
<< __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP
+#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \
+ doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>( \
+ doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__)
+
+#define DOCTEST_UNARY_ASSERT(assert_type, ...) \
+ doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \
+ #__VA_ARGS__, __VA_ARGS__)
+
#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__)
#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__)
// clang-format off
-#define DOCTEST_WARN_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while(false)
-#define DOCTEST_CHECK_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while(false)
-#define DOCTEST_REQUIRE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while(false)
-#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while(false)
-#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while(false)
-#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while(false)
+#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } DOCTEST_FUNC_SCOPE_END
// clang-format on
+#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__)
+#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__)
+#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__)
+#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__)
+#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__)
+#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__)
+#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__)
+#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__)
+#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__)
+#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__)
+#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__)
+#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__)
+#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__)
+#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__)
+#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__)
+#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__)
+#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__)
+
+#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__)
+#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__)
+#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__)
+#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__)
+#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__)
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__)
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+
#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \
- do { \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
if(!doctest::getContextOptions()->no_throw) { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
__LINE__, #expr, #__VA_ARGS__, message); \
try { \
DOCTEST_CAST_TO_VOID(expr) \
- } catch(const typename doctest::detail::remove_const< \
- typename doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \
- _DOCTEST_RB.translateException(); \
- _DOCTEST_RB.m_threw_as = true; \
- } catch(...) { _DOCTEST_RB.translateException(); } \
- DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
+ } catch(const typename doctest::detail::types::remove_const< \
+ typename doctest::detail::types::remove_reference<__VA_ARGS__>::type>::type&) {\
+ DOCTEST_RB.translateException(); \
+ DOCTEST_RB.m_threw_as = true; \
+ } catch(...) { DOCTEST_RB.translateException(); } \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ } else { /* NOLINT(*-else-after-return) */ \
+ DOCTEST_FUNC_SCOPE_RET(false); \
} \
- } while(false)
+ } DOCTEST_FUNC_SCOPE_END
#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \
- do { \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
if(!doctest::getContextOptions()->no_throw) { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
__LINE__, expr_str, "", __VA_ARGS__); \
try { \
DOCTEST_CAST_TO_VOID(expr) \
- } catch(...) { _DOCTEST_RB.translateException(); } \
- DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
+ } catch(...) { DOCTEST_RB.translateException(); } \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ } else { /* NOLINT(*-else-after-return) */ \
+ DOCTEST_FUNC_SCOPE_RET(false); \
} \
- } while(false)
+ } DOCTEST_FUNC_SCOPE_END
#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \
- do { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ DOCTEST_FUNC_SCOPE_BEGIN { \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
__LINE__, #__VA_ARGS__); \
try { \
DOCTEST_CAST_TO_VOID(__VA_ARGS__) \
- } catch(...) { _DOCTEST_RB.translateException(); } \
- DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
- } while(false)
+ } catch(...) { DOCTEST_RB.translateException(); } \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ } DOCTEST_FUNC_SCOPE_END
// clang-format off
#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "")
#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__)
#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__)
-#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } while(false)
-#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } while(false)
-#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } while(false)
-#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } while(false)
-#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } while(false)
-#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while(false)
-#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } while(false)
-#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } while(false)
-#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while(false)
-#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while(false)
-#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while(false)
-#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while(false)
-#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } while(false)
-#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } while(false)
-#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } while(false)
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END
// clang-format on
-#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS
-
-#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \
- do { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
- __LINE__, #__VA_ARGS__); \
- DOCTEST_WRAP_IN_TRY( \
- _DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \
- __VA_ARGS__)) \
- DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
- } while(false)
-
-#define DOCTEST_UNARY_ASSERT(assert_type, ...) \
- do { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
- __LINE__, #__VA_ARGS__); \
- DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \
- DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
- } while(false)
-
-#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
-
-#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \
- doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>( \
- doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__)
-
-#define DOCTEST_UNARY_ASSERT(assert_type, ...) \
- doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \
- #__VA_ARGS__, __VA_ARGS__)
-
-#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
-
-#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__)
-#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__)
-#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__)
-#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__)
-#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__)
-#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__)
-#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__)
-#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__)
-#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__)
-#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__)
-#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__)
-#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__)
-#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__)
-#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__)
-#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__)
-#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__)
-#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__)
-#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__)
-
-#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__)
-#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__)
-#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__)
-#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__)
-#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__)
-#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__)
-
-#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS
-
-#undef DOCTEST_WARN_THROWS
-#undef DOCTEST_CHECK_THROWS
-#undef DOCTEST_REQUIRE_THROWS
-#undef DOCTEST_WARN_THROWS_AS
-#undef DOCTEST_CHECK_THROWS_AS
-#undef DOCTEST_REQUIRE_THROWS_AS
-#undef DOCTEST_WARN_THROWS_WITH
-#undef DOCTEST_CHECK_THROWS_WITH
-#undef DOCTEST_REQUIRE_THROWS_WITH
-#undef DOCTEST_WARN_THROWS_WITH_AS
-#undef DOCTEST_CHECK_THROWS_WITH_AS
-#undef DOCTEST_REQUIRE_THROWS_WITH_AS
-#undef DOCTEST_WARN_NOTHROW
-#undef DOCTEST_CHECK_NOTHROW
-#undef DOCTEST_REQUIRE_NOTHROW
-
-#undef DOCTEST_WARN_THROWS_MESSAGE
-#undef DOCTEST_CHECK_THROWS_MESSAGE
-#undef DOCTEST_REQUIRE_THROWS_MESSAGE
-#undef DOCTEST_WARN_THROWS_AS_MESSAGE
-#undef DOCTEST_CHECK_THROWS_AS_MESSAGE
-#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE
-#undef DOCTEST_WARN_THROWS_WITH_MESSAGE
-#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE
-#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE
-#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE
-#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE
-#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE
-#undef DOCTEST_WARN_NOTHROW_MESSAGE
-#undef DOCTEST_CHECK_NOTHROW_MESSAGE
-#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE
-
-#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
-
-#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, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) (static_cast<void>(0))
-#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0))
-#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0))
-#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0))
-#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0))
-
-#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
-
-#undef DOCTEST_REQUIRE
-#undef DOCTEST_REQUIRE_FALSE
-#undef DOCTEST_REQUIRE_MESSAGE
-#undef DOCTEST_REQUIRE_FALSE_MESSAGE
-#undef DOCTEST_REQUIRE_EQ
-#undef DOCTEST_REQUIRE_NE
-#undef DOCTEST_REQUIRE_GT
-#undef DOCTEST_REQUIRE_LT
-#undef DOCTEST_REQUIRE_GE
-#undef DOCTEST_REQUIRE_LE
-#undef DOCTEST_REQUIRE_UNARY
-#undef DOCTEST_REQUIRE_UNARY_FALSE
-
-#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
-
#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
// =================================================================================================
#else // DOCTEST_CONFIG_DISABLE
#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \
- namespace { \
+ namespace /* NOLINT */ { \
template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
struct der : public base \
{ void f(); }; \
// for registering tests
#define DOCTEST_TEST_CASE(name) \
- DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name)
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)
// for registering tests in classes
#define DOCTEST_TEST_CASE_CLASS(name) \
- DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name)
+ DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)
// for registering tests with a fixture
#define DOCTEST_TEST_CASE_FIXTURE(x, name) \
- DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \
- DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name)
+ DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x, \
+ DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)
// for converting types to strings without the <typeinfo> header and demangling
-#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
-#define DOCTEST_TYPE_TO_STRING_IMPL(...)
+#define DOCTEST_TYPE_TO_STRING_AS(str, ...) static_assert(true, "")
+#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "")
// for typed tests
#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \
template <typename type> \
- inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)()
+ inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)()
#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \
template <typename type> \
- inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)()
-
-#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \
- typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+ inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)()
-#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \
- typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "")
+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "")
// for subcases
#define DOCTEST_SUBCASE(name)
// for a testsuite block
-#define DOCTEST_TEST_SUITE(name) namespace
+#define DOCTEST_TEST_SUITE(name) namespace // NOLINT
// for starting a testsuite block
-#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "")
// for ending a testsuite block
-#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+#define DOCTEST_TEST_SUITE_END using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int
#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \
template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
- static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature)
+ static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature)
#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)
#define DOCTEST_REGISTER_LISTENER(name, priority, reporter)
#define DOCTEST_FAIL_CHECK(...) (static_cast<void>(0))
#define DOCTEST_FAIL(...) (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, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_MESSAGE(cond, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_MESSAGE(cond, ...) (static_cast<void>(0))
-#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) (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, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) (static_cast<void>(0))
-#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0))
-#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0))
-#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0))
-#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0))
-#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0))
-#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) (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))
+#if defined(DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED) \
+ && defined(DOCTEST_CONFIG_ASSERTS_RETURN_VALUES)
+
+#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }()
+
+#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }()
+#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }()
+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }()
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()
+
+namespace doctest {
+namespace detail {
+#define DOCTEST_RELATIONAL_OP(name, op) \
+ template <typename L, typename R> \
+ bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; }
+
+ DOCTEST_RELATIONAL_OP(eq, ==)
+ DOCTEST_RELATIONAL_OP(ne, !=)
+ DOCTEST_RELATIONAL_OP(lt, <)
+ DOCTEST_RELATIONAL_OP(gt, >)
+ DOCTEST_RELATIONAL_OP(le, <=)
+ DOCTEST_RELATIONAL_OP(ge, >=)
+} // namespace detail
+} // namespace doctest
+
+#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()
+#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()
+#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()
+#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()
+#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()
+#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()
+#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()
+#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()
+#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()
+#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()
+#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()
+#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()
+#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#define DOCTEST_WARN_THROWS_WITH(expr, with, ...) [] { static_assert(false, "Exception translation is not available when doctest is disabled."); return false; }()
+#define DOCTEST_CHECK_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,)
+
+#define DOCTEST_WARN_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_CHECK_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_REQUIRE_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_WARN_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_WARN_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+#define DOCTEST_CHECK_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+#define DOCTEST_REQUIRE_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }()
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }()
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }()
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
+
+#define DOCTEST_WARN(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_FALSE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_FALSE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_FUNC_EMPTY
+
+#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY
+
+#define DOCTEST_WARN_EQ(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_EQ(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_EQ(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_NE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_NE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_NE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_GT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_GT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_GT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_LT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_LT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_LT(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_GE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_GE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_GE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_LE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_LE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_LE(...) DOCTEST_FUNC_EMPTY
+
+#define DOCTEST_WARN_UNARY(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_UNARY(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#define DOCTEST_WARN_THROWS(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_NOTHROW(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_FUNC_EMPTY
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
#endif // DOCTEST_CONFIG_DISABLE
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+#define DOCTEST_EXCEPTION_EMPTY_FUNC DOCTEST_FUNC_EMPTY
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+#define DOCTEST_EXCEPTION_EMPTY_FUNC [] { static_assert(false, "Exceptions are disabled! " \
+ "Use DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS if you want to compile with exceptions disabled."); return false; }()
+
+#undef DOCTEST_REQUIRE
+#undef DOCTEST_REQUIRE_FALSE
+#undef DOCTEST_REQUIRE_MESSAGE
+#undef DOCTEST_REQUIRE_FALSE_MESSAGE
+#undef DOCTEST_REQUIRE_EQ
+#undef DOCTEST_REQUIRE_NE
+#undef DOCTEST_REQUIRE_GT
+#undef DOCTEST_REQUIRE_LT
+#undef DOCTEST_REQUIRE_GE
+#undef DOCTEST_REQUIRE_LE
+#undef DOCTEST_REQUIRE_UNARY
+#undef DOCTEST_REQUIRE_UNARY_FALSE
+
+#define DOCTEST_REQUIRE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_FALSE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_EQ DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_NE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_GT DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_LT DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_GE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_LE DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_UNARY DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_UNARY_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#define DOCTEST_WARN_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
// clang-format off
// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS
#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ
// clang-format on
// == SHORT VERSIONS OF THE MACROS
-#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES)
+#ifndef DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
#define TEST_CASE(name) DOCTEST_TEST_CASE(name)
#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name)
#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name)
+#define TYPE_TO_STRING_AS(str, ...) DOCTEST_TYPE_TO_STRING_AS(str, __VA_ARGS__)
#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__)
#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__)
#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id)
#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
-#if !defined(DOCTEST_CONFIG_DISABLE)
+#ifndef DOCTEST_CONFIG_DISABLE
// this is here to clear the 'current test suite' for the current translation unit - at the top
DOCTEST_TEST_SUITE_END();
-// add stringification for primitive/fundamental types
-namespace doctest { namespace detail {
- DOCTEST_TYPE_TO_STRING_IMPL(bool)
- DOCTEST_TYPE_TO_STRING_IMPL(float)
- DOCTEST_TYPE_TO_STRING_IMPL(double)
- DOCTEST_TYPE_TO_STRING_IMPL(long double)
- DOCTEST_TYPE_TO_STRING_IMPL(char)
- DOCTEST_TYPE_TO_STRING_IMPL(signed char)
- DOCTEST_TYPE_TO_STRING_IMPL(unsigned char)
-#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED)
- DOCTEST_TYPE_TO_STRING_IMPL(wchar_t)
-#endif // not MSVC or wchar_t support enabled
- DOCTEST_TYPE_TO_STRING_IMPL(short int)
- DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int)
- DOCTEST_TYPE_TO_STRING_IMPL(int)
- DOCTEST_TYPE_TO_STRING_IMPL(unsigned int)
- DOCTEST_TYPE_TO_STRING_IMPL(long int)
- DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int)
- DOCTEST_TYPE_TO_STRING_IMPL(long long int)
- DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int)
-}} // namespace doctest::detail
-
#endif // DOCTEST_CONFIG_DISABLE
DOCTEST_CLANG_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP
DOCTEST_GCC_SUPPRESS_WARNING_POP
+DOCTEST_SUPPRESS_COMMON_WARNINGS_POP
+
#endif // DOCTEST_LIBRARY_INCLUDED
#ifndef DOCTEST_SINGLE_HEADER
DOCTEST_CLANG_SUPPRESS_WARNING_POP
+DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH
+
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
-DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path")
DOCTEST_GCC_SUPPRESS_WARNING_PUSH
-DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas")
DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion")
-DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++")
DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing")
DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers")
DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations")
DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch")
DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum")
DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default")
DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations")
DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast")
DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function")
DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance")
-DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")
DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute")
DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
-DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning
-DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning
-DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration
DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data
-DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression
-DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated
-DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant
DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled
DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified
DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal
DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch
-DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs
-DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe
DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C
-DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff
-DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted
-DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted
-DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted
-DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted
DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning)
-// static analysis
-DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept'
-DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable
-DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ...
-DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor...
-DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
+DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <ctime>
#include <cmath>
#include <climits>
-// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37
+// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37
#ifdef __BORLANDC__
#include <math.h>
#endif // __BORLANDC__
#include <algorithm>
#include <iomanip>
#include <vector>
+#ifndef DOCTEST_CONFIG_NO_MULTITHREADING
#include <atomic>
#include <mutex>
+#define DOCTEST_DECLARE_MUTEX(name) std::mutex name;
+#define DOCTEST_DECLARE_STATIC_MUTEX(name) static DOCTEST_DECLARE_MUTEX(name)
+#define DOCTEST_LOCK_MUTEX(name) std::lock_guard<std::mutex> DOCTEST_ANONYMOUS(DOCTEST_ANON_LOCK_)(name);
+#else // DOCTEST_CONFIG_NO_MULTITHREADING
+#define DOCTEST_DECLARE_MUTEX(name)
+#define DOCTEST_DECLARE_STATIC_MUTEX(name)
+#define DOCTEST_LOCK_MUTEX(name)
+#endif // DOCTEST_CONFIG_NO_MULTITHREADING
#include <set>
#include <map>
+#include <unordered_set>
#include <exception>
#include <stdexcept>
#include <csignal>
#include <cfloat>
#include <cctype>
#include <cstdint>
+#include <string>
#ifdef DOCTEST_PLATFORM_MAC
#include <sys/types.h>
#endif // DOCTEST_PLATFORM_WINDOWS
-// this is a fix for https://github.com/onqtam/doctest/issues/348
+// this is a fix for https://github.com/doctest/doctest/issues/348
// https://mail.gnome.org/archives/xml/2012-January/msg00000.html
#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO)
#define STDOUT_FILENO fileno(stdout)
#endif
#ifndef DOCTEST_THREAD_LOCAL
+#if defined(DOCTEST_CONFIG_NO_MULTITHREADING) || DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_THREAD_LOCAL
+#else // DOCTEST_MSVC
#define DOCTEST_THREAD_LOCAL thread_local
-#endif
+#endif // DOCTEST_MSVC
+#endif // DOCTEST_THREAD_LOCAL
#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES
#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32
#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
#endif
+#ifndef DOCTEST_CDECL
+#define DOCTEST_CDECL __cdecl
+#endif
+
namespace doctest {
-bool is_running_in_test = false;
+bool is_running_in_test = false;
+
+namespace {
+ using namespace detail;
+
+ template <typename Ex>
+ DOCTEST_NORETURN void throw_exception(Ex const& e) {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ throw e;
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS
+ std::cerr << "doctest will terminate because it needed to throw an exception.\n"
+ << "The message was: " << e.what() << '\n';
+ std::terminate();
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+ }
+
+#ifndef DOCTEST_INTERNAL_ERROR
+#define DOCTEST_INTERNAL_ERROR(msg) \
+ throw_exception(std::logic_error( \
+ __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg))
+#endif // DOCTEST_INTERNAL_ERROR
-namespace {
- using namespace detail;
// case insensitive strcmp
int stricmp(const char* a, const char* b) {
for(;; a++, b++) {
}
}
- template <typename T>
- String fpToString(T value, int precision) {
- std::ostringstream oss;
- oss << std::setprecision(precision) << std::fixed << value;
- std::string d = oss.str();
- size_t i = d.find_last_not_of('0');
- if(i != std::string::npos && i != d.size() - 1) {
- if(d[i] == '.')
- i++;
- d = d.substr(0, i + 1);
- }
- return d.c_str();
- }
-
struct Endianness
{
enum Arch
} // namespace
namespace detail {
- void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); }
-
- String rawMemoryToString(const void* object, unsigned size) {
- // Reverse order for little endian architectures
- int i = 0, end = static_cast<int>(size), inc = 1;
- if(Endianness::which() == Endianness::Little) {
- i = end - 1;
- end = inc = -1;
- }
-
- unsigned const char* bytes = static_cast<unsigned const char*>(object);
- std::ostringstream oss;
- oss << "0x" << std::setfill('0') << std::hex;
- for(; i != end; i += inc)
- oss << std::setw(2) << static_cast<unsigned>(bytes[i]);
- return oss.str().c_str();
- }
+ DOCTEST_THREAD_LOCAL class
+ {
+ std::vector<std::streampos> stack;
+ std::stringstream ss;
+
+ public:
+ std::ostream* push() {
+ stack.push_back(ss.tellp());
+ return &ss;
+ }
+
+ String pop() {
+ if (stack.empty())
+ DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!");
- DOCTEST_THREAD_LOCAL std::ostringstream g_oss; // NOLINT(cert-err58-cpp)
+ std::streampos pos = stack.back();
+ stack.pop_back();
+ unsigned sz = static_cast<unsigned>(ss.tellp() - pos);
+ ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out);
+ return String(ss, sz);
+ }
+ } g_oss;
- std::ostream* getTlsOss() {
- g_oss.clear(); // there shouldn't be anything worth clearing in the flags
- g_oss.str(""); // the slow way of resetting a string stream
- //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383
- return &g_oss;
+ std::ostream* tlssPush() {
+ return g_oss.push();
}
- String getTlsOssResult() {
- //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383
- return g_oss.str().c_str();
+ String tlssPop() {
+ return g_oss.pop();
}
#ifndef DOCTEST_CONFIG_DISABLE
{
#if defined(DOCTEST_PLATFORM_WINDOWS)
- typedef ULONGLONG type;
+ using type = ULONGLONG;
#else // DOCTEST_PLATFORM_WINDOWS
- using namespace std;
- typedef uint64_t type;
+ using type = std::uint64_t;
#endif // DOCTEST_PLATFORM_WINDOWS
}
-typedef timer_large_integer::type ticks_t;
+using ticks_t = timer_large_integer::type;
#ifdef DOCTEST_CONFIG_GETCURRENTTICKS
ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); }
#elif defined(DOCTEST_PLATFORM_WINDOWS)
ticks_t getCurrentTicks() {
- static LARGE_INTEGER hz = {0}, hzo = {0};
+ static LARGE_INTEGER hz = { {0} }, hzo = { {0} };
if(!hz.QuadPart) {
QueryPerformanceFrequency(&hz);
QueryPerformanceCounter(&hzo);
ticks_t m_ticks = 0;
};
-#ifdef DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+#ifdef DOCTEST_CONFIG_NO_MULTITHREADING
+ template <typename T>
+ using Atomic = T;
+#else // DOCTEST_CONFIG_NO_MULTITHREADING
+ template <typename T>
+ using Atomic = std::atomic<T>;
+#endif // DOCTEST_CONFIG_NO_MULTITHREADING
+
+#if defined(DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS) || defined(DOCTEST_CONFIG_NO_MULTITHREADING)
template <typename T>
- using AtomicOrMultiLaneAtomic = std::atomic<T>;
+ using MultiLaneAtomic = Atomic<T>;
#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
// Provides a multilane implementation of an atomic variable that supports add, sub, load,
// store. Instead of using a single atomic variable, this splits up into multiple ones,
{
struct CacheLineAlignedAtomic
{
- std::atomic<T> atomic{};
- char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(std::atomic<T>)];
+ Atomic<T> atomic{};
+ char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(Atomic<T>)];
};
CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES];
return result;
}
- T operator=(T desired) DOCTEST_NOEXCEPT {
+ T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this]
store(desired);
return desired;
}
private:
// Each thread has a different atomic that it operates on. If more than NumLanes threads
- // use this, some will use the same atomic. So performance will degrate a bit, but still
+ // use this, some will use the same atomic. So performance will degrade a bit, but still
// everything will work.
//
// The logic here is a bit tricky. The call should be as fast as possible, so that there
// assigned in a round-robin fashion.
// 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with
// little overhead.
- std::atomic<T>& myAtomic() DOCTEST_NOEXCEPT {
- static std::atomic<size_t> laneCounter;
+ Atomic<T>& myAtomic() DOCTEST_NOEXCEPT {
+ static Atomic<size_t> laneCounter;
DOCTEST_THREAD_LOCAL size_t tlsLaneIdx =
laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES;
return m_atomics[tlsLaneIdx].atomic;
}
};
-
- template <typename T>
- using AtomicOrMultiLaneAtomic = MultiLaneAtomic<T>;
#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
// this holds both parameters from the command line and runtime data for tests
struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats
{
- AtomicOrMultiLaneAtomic<int> numAssertsCurrentTest_atomic;
- AtomicOrMultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic;
+ MultiLaneAtomic<int> numAssertsCurrentTest_atomic;
+ MultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic;
std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters
std::vector<String> stringifiedContexts; // logging from INFO() due to an exception
// stuff for subcases
- std::vector<SubcaseSignature> subcasesStack;
- std::set<decltype(subcasesStack)> subcasesPassed;
- int subcasesCurrentMaxLevel;
- bool should_reenter;
- std::atomic<bool> shouldLogCurrentException;
+ bool reachedLeaf;
+ std::vector<SubcaseSignature> subcaseStack;
+ std::vector<SubcaseSignature> nextSubcaseStack;
+ std::unordered_set<unsigned long long> fullyTraversedSubcases;
+ size_t currentSubcaseDepth;
+ Atomic<bool> shouldLogCurrentException;
void resetRunData() {
numTestCases = 0;
(TestCaseFailureReason::FailedExactlyNumTimes & failure_flags);
// if any subcase has failed - the whole test case has failed
- if(failure_flags && !ok_to_fail)
+ testCaseSuccess = !(failure_flags && !ok_to_fail);
+ if(!testCaseSuccess)
numTestCasesFailed++;
}
};
#endif // DOCTEST_CONFIG_DISABLE
} // namespace detail
-void String::setOnHeap() { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; }
-void String::setLast(unsigned in) { buf[last] = char(in); }
+char* String::allocate(size_type sz) {
+ if (sz <= last) {
+ buf[sz] = '\0';
+ setLast(last - sz);
+ return buf;
+ } else {
+ setOnHeap();
+ data.size = sz;
+ data.capacity = data.size + 1;
+ data.ptr = new char[data.capacity];
+ data.ptr[sz] = '\0';
+ return data.ptr;
+ }
+}
+
+void String::setOnHeap() noexcept { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; }
+void String::setLast(size_type in) noexcept { buf[last] = char(in); }
+void String::setSize(size_type sz) noexcept {
+ if (isOnStack()) { buf[sz] = '\0'; setLast(last - sz); }
+ else { data.ptr[sz] = '\0'; data.size = sz; }
+}
void String::copy(const String& other) {
- using namespace std;
if(other.isOnStack()) {
memcpy(buf, other.buf, len);
} else {
- setOnHeap();
- data.size = other.data.size;
- data.capacity = data.size + 1;
- data.ptr = new char[data.capacity];
- memcpy(data.ptr, other.data.ptr, data.size + 1);
+ memcpy(allocate(other.data.size), other.data.ptr, other.data.size);
}
}
-String::String() {
+String::String() noexcept {
buf[0] = '\0';
setLast();
}
String::~String() {
if(!isOnStack())
delete[] data.ptr;
- // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
-}
+} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
String::String(const char* in)
: String(in, strlen(in)) {}
-String::String(const char* in, unsigned in_size) {
- using namespace std;
- if(in_size <= last) {
- memcpy(buf, in, in_size);
- buf[in_size] = '\0';
- setLast(last - in_size);
- } else {
- setOnHeap();
- data.size = in_size;
- data.capacity = data.size + 1;
- data.ptr = new char[data.capacity];
- memcpy(data.ptr, in, in_size);
- data.ptr[in_size] = '\0';
- }
+String::String(const char* in, size_type in_size) {
+ memcpy(allocate(in_size), in, in_size);
+}
+
+String::String(std::istream& in, size_type in_size) {
+ in.read(allocate(in_size), in_size);
}
String::String(const String& other) { copy(other); }
}
String& String::operator+=(const String& other) {
- const unsigned my_old_size = size();
- const unsigned other_size = other.size();
- const unsigned total_size = my_old_size + other_size;
- using namespace std;
+ const size_type my_old_size = size();
+ const size_type other_size = other.size();
+ const size_type total_size = my_old_size + other_size;
if(isOnStack()) {
if(total_size < len) {
// append to the current stack space
return *this;
}
-// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
-String String::operator+(const String& other) const { return String(*this) += other; }
-
-String::String(String&& other) {
- using namespace std;
+String::String(String&& other) noexcept {
memcpy(buf, other.buf, len);
other.buf[0] = '\0';
other.setLast();
}
-String& String::operator=(String&& other) {
- using namespace std;
+String& String::operator=(String&& other) noexcept {
if(this != &other) {
if(!isOnStack())
delete[] data.ptr;
return *this;
}
-char String::operator[](unsigned i) const {
- return const_cast<String*>(this)->operator[](i); // NOLINT
+char String::operator[](size_type i) const {
+ return const_cast<String*>(this)->operator[](i);
}
-char& String::operator[](unsigned i) {
+char& String::operator[](size_type i) {
if(isOnStack())
return reinterpret_cast<char*>(buf)[i];
return data.ptr[i];
}
DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized")
-unsigned String::size() const {
+String::size_type String::size() const {
if(isOnStack())
- return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32
+ return last - (size_type(buf[last]) & 31); // using "last" would work only if "len" is 32
return data.size;
}
DOCTEST_GCC_SUPPRESS_WARNING_POP
-unsigned String::capacity() const {
+String::size_type String::capacity() const {
if(isOnStack())
return len;
return data.capacity;
}
+String String::substr(size_type pos, size_type cnt) && {
+ cnt = std::min(cnt, size() - 1 - pos);
+ char* cptr = c_str();
+ memmove(cptr, cptr + pos, cnt);
+ setSize(cnt);
+ return std::move(*this);
+}
+
+String String::substr(size_type pos, size_type cnt) const & {
+ cnt = std::min(cnt, size() - 1 - pos);
+ return String{ c_str() + pos, cnt };
+}
+
+String::size_type String::find(char ch, size_type pos) const {
+ const char* begin = c_str();
+ const char* end = begin + size();
+ const char* it = begin + pos;
+ for (; it < end && *it != ch; it++);
+ if (it < end) { return static_cast<size_type>(it - begin); }
+ else { return npos; }
+}
+
+String::size_type String::rfind(char ch, size_type pos) const {
+ const char* begin = c_str();
+ const char* it = begin + std::min(pos, size() - 1);
+ for (; it >= begin && *it != ch; it--);
+ if (it >= begin) { return static_cast<size_type>(it - begin); }
+ else { return npos; }
+}
+
int String::compare(const char* other, bool no_case) const {
if(no_case)
return doctest::stricmp(c_str(), other);
return compare(other.c_str(), no_case);
}
-// clang-format off
+String operator+(const String& lhs, const String& rhs) { return String(lhs) += rhs; }
+
bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; }
bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; }
bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; }
bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; }
bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; }
bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; }
-// clang-format on
std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); }
+Contains::Contains(const String& str) : string(str) { }
+
+bool Contains::checkWith(const String& other) const {
+ return strstr(other.c_str(), string.c_str()) != nullptr;
+}
+
+String toString(const Contains& in) {
+ return "Contains( " + in.string + " )";
+}
+
+bool operator==(const String& lhs, const Contains& rhs) { return rhs.checkWith(lhs); }
+bool operator==(const Contains& lhs, const String& rhs) { return lhs.checkWith(rhs); }
+bool operator!=(const String& lhs, const Contains& rhs) { return !rhs.checkWith(lhs); }
+bool operator!=(const Contains& lhs, const String& rhs) { return !lhs.checkWith(rhs); }
+
namespace {
void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;)
} // namespace
// clang-format off
const char* assertString(assertType::Enum at) {
- DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled
- switch(at) { //!OCLINT missing default in switch statements
- case assertType::DT_WARN : return "WARN";
- case assertType::DT_CHECK : return "CHECK";
- case assertType::DT_REQUIRE : return "REQUIRE";
-
- case assertType::DT_WARN_FALSE : return "WARN_FALSE";
- case assertType::DT_CHECK_FALSE : return "CHECK_FALSE";
- case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE";
-
- case assertType::DT_WARN_THROWS : return "WARN_THROWS";
- case assertType::DT_CHECK_THROWS : return "CHECK_THROWS";
- case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS";
-
- case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS";
- case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS";
- case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS";
-
- case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH";
- case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH";
- case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH";
-
- case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS";
- case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS";
- case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS";
-
- case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW";
- case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW";
- case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW";
-
- case assertType::DT_WARN_EQ : return "WARN_EQ";
- case assertType::DT_CHECK_EQ : return "CHECK_EQ";
- case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ";
- case assertType::DT_WARN_NE : return "WARN_NE";
- case assertType::DT_CHECK_NE : return "CHECK_NE";
- case assertType::DT_REQUIRE_NE : return "REQUIRE_NE";
- case assertType::DT_WARN_GT : return "WARN_GT";
- case assertType::DT_CHECK_GT : return "CHECK_GT";
- case assertType::DT_REQUIRE_GT : return "REQUIRE_GT";
- case assertType::DT_WARN_LT : return "WARN_LT";
- case assertType::DT_CHECK_LT : return "CHECK_LT";
- case assertType::DT_REQUIRE_LT : return "REQUIRE_LT";
- case assertType::DT_WARN_GE : return "WARN_GE";
- case assertType::DT_CHECK_GE : return "CHECK_GE";
- case assertType::DT_REQUIRE_GE : return "REQUIRE_GE";
- case assertType::DT_WARN_LE : return "WARN_LE";
- case assertType::DT_CHECK_LE : return "CHECK_LE";
- case assertType::DT_REQUIRE_LE : return "REQUIRE_LE";
-
- case assertType::DT_WARN_UNARY : return "WARN_UNARY";
- case assertType::DT_CHECK_UNARY : return "CHECK_UNARY";
- case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY";
- case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE";
- case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE";
- case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE";
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitely handled
+ #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type
+ #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK_ ## assert_type); \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE_ ## assert_type)
+ switch(at) {
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(FALSE);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_AS);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH_AS);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(NOTHROW);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(EQ);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(NE);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(GT);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(LT);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(GE);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(LE);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY_FALSE);
+
+ default: DOCTEST_INTERNAL_ERROR("Tried stringifying invalid assert type!");
}
DOCTEST_MSVC_SUPPRESS_WARNING_POP
- return "";
}
// clang-format on
DOCTEST_CLANG_SUPPRESS_WARNING_POP
DOCTEST_GCC_SUPPRESS_WARNING_POP
+bool SubcaseSignature::operator==(const SubcaseSignature& other) const {
+ return m_line == other.m_line
+ && std::strcmp(m_file, other.m_file) == 0
+ && m_name == other.m_name;
+}
+
bool SubcaseSignature::operator<(const SubcaseSignature& other) const {
if(m_line != other.m_line)
return m_line < other.m_line;
return m_name.compare(other.m_name) < 0;
}
-IContextScope::IContextScope() = default;
-IContextScope::~IContextScope() = default;
+DOCTEST_DEFINE_INTERFACE(IContextScope)
-#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
-String toString(char* in) { return toString(static_cast<const char*>(in)); }
-// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
-String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; }
-#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
-String toString(bool in) { return in ? "true" : "false"; }
-String toString(float in) { return fpToString(in, 5) + "f"; }
-String toString(double in) { return fpToString(in, 10); }
-String toString(double long in) { return fpToString(in, 15); }
-
-#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \
- String toString(type in) { \
- char buf[64]; \
- std::sprintf(buf, fmt, in); \
- return buf; \
+namespace detail {
+ void filldata<const void*>::fill(std::ostream* stream, const void* in) {
+ if (in) { *stream << in; }
+ else { *stream << "nullptr"; }
}
-DOCTEST_TO_STRING_OVERLOAD(char, "%d")
-DOCTEST_TO_STRING_OVERLOAD(char signed, "%d")
-DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u")
-DOCTEST_TO_STRING_OVERLOAD(int short, "%d")
-DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u")
-DOCTEST_TO_STRING_OVERLOAD(int, "%d")
-DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u")
-DOCTEST_TO_STRING_OVERLOAD(int long, "%ld")
-DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu")
-DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld")
-DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu")
+ template <typename T>
+ String toStreamLit(T t) {
+ std::ostream* os = tlssPush();
+ os->operator<<(t);
+ return tlssPop();
+ }
+}
-String toString(std::nullptr_t) { return "NULL"; }
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; }
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
-// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183
+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183
String toString(const std::string& in) { return in.c_str(); }
#endif // VS 2019
+String toString(String in) { return in; }
+
+String toString(std::nullptr_t) { return "nullptr"; }
+
+String toString(bool in) { return in ? "true" : "false"; }
+
+String toString(float in) { return toStreamLit(in); }
+String toString(double in) { return toStreamLit(in); }
+String toString(double long in) { return toStreamLit(in); }
+
+String toString(char in) { return toStreamLit(static_cast<signed>(in)); }
+String toString(char signed in) { return toStreamLit(static_cast<signed>(in)); }
+String toString(char unsigned in) { return toStreamLit(static_cast<unsigned>(in)); }
+String toString(short in) { return toStreamLit(in); }
+String toString(short unsigned in) { return toStreamLit(in); }
+String toString(signed in) { return toStreamLit(in); }
+String toString(unsigned in) { return toStreamLit(in); }
+String toString(long in) { return toStreamLit(in); }
+String toString(long unsigned in) { return toStreamLit(in); }
+String toString(long long in) { return toStreamLit(in); }
+String toString(long long unsigned in) { return toStreamLit(in); }
+
Approx::Approx(double value)
: m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100)
, m_scale(1.0)
bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; }
String toString(const Approx& in) {
- // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
- return String("Approx( ") + doctest::toString(in.m_value) + " )";
+ return "Approx( " + doctest::toString(in.m_value) + " )";
}
const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); }
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4738)
+template <typename F>
+IsNaN<F>::operator bool() const {
+ return std::isnan(value) ^ flipped;
+}
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+template struct DOCTEST_INTERFACE_DEF IsNaN<float>;
+template struct DOCTEST_INTERFACE_DEF IsNaN<double>;
+template struct DOCTEST_INTERFACE_DEF IsNaN<long double>;
+template <typename F>
+String toString(IsNaN<F> in) { return String(in.flipped ? "! " : "") + "IsNaN( " + doctest::toString(in.value) + " )"; }
+String toString(IsNaN<float> in) { return toString<float>(in); }
+String toString(IsNaN<double> in) { return toString<double>(in); }
+String toString(IsNaN<double long> in) { return toString<double long>(in); }
+
} // namespace doctest
#ifdef DOCTEST_CONFIG_DISABLE
void Context::applyCommandLine(int, const char* const*) {}
void Context::addFilter(const char*, const char*) {}
void Context::clearFilters() {}
+void Context::setOption(const char*, bool) {}
void Context::setOption(const char*, int) {}
void Context::setOption(const char*, const char*) {}
bool Context::shouldExit() { return false; }
void Context::setAsDefaultForAssertsOutOfTestCases() {}
void Context::setAssertHandler(detail::assert_handler) {}
+void Context::setCout(std::ostream*) {}
int Context::run() { return 0; }
-IReporter::~IReporter() = default;
-
int IReporter::get_num_active_contexts() { return 0; }
const IContextScope* const* IReporter::get_active_contexts() { return nullptr; }
int IReporter::get_num_stringified_contexts() { return 0; }
namespace {
// the int (priority) is part of the key for automatic sorting - sadly one can register a
// reporter with a duplicate name and a different priority but hopefully that won't happen often :|
- typedef std::map<std::pair<int, String>, reporterCreatorFunc> reporterMap;
+ using reporterMap = std::map<std::pair<int, String>, reporterCreatorFunc>;
reporterMap& getReporters() {
static reporterMap data;
#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
DOCTEST_NORETURN void throwException() {
g_cs->shouldLogCurrentException = false;
- throw TestFailureException();
- } // NOLINT(cert-err60-cpp)
+ throw TestFailureException(); // NOLINT(hicpp-exception-baseclass)
+ }
#else // DOCTEST_CONFIG_NO_EXCEPTIONS
void throwException() {}
#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
return !*wild;
}
- //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html
- //unsigned hashStr(unsigned const char* str) {
- // unsigned long hash = 5381;
- // char c;
- // while((c = *str++))
- // hash = ((hash << 5) + hash) + c; // hash * 33 + c
- // return hash;
- //}
-
// checks if the name matches any of the filters (and can be configured what to do when empty)
bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty,
- bool caseSensitive) {
- if(filters.empty() && matchEmpty)
+ bool caseSensitive) {
+ if (filters.empty() && matchEmpty)
return true;
- for(auto& curr : filters)
- if(wildcmp(name, curr.c_str(), caseSensitive))
+ for (auto& curr : filters)
+ if (wildcmp(name, curr.c_str(), caseSensitive))
return true;
return false;
}
-} // namespace
-namespace detail {
- Subcase::Subcase(const String& name, const char* file, int line)
- : m_signature({name, file, line}) {
- auto* s = g_cs;
+ unsigned long long hash(unsigned long long a, unsigned long long b) {
+ return (a << 5) + b;
+ }
- // check subcase filters
- if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) {
- if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive))
- return;
- if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive))
- return;
- }
-
- // if a Subcase on the same level has already been entered
- if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) {
- s->should_reenter = true;
- return;
- }
+ // C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html
+ unsigned long long hash(const char* str) {
+ unsigned long long hash = 5381;
+ char c;
+ while ((c = *str++))
+ hash = ((hash << 5) + hash) + c; // hash * 33 + c
+ return hash;
+ }
- // push the current signature to the stack so we can check if the
- // current stack + the current new subcase have been traversed
- s->subcasesStack.push_back(m_signature);
- if(s->subcasesPassed.count(s->subcasesStack) != 0) {
- // pop - revert to previous stack since we've already passed this
- s->subcasesStack.pop_back();
- return;
+ unsigned long long hash(const SubcaseSignature& sig) {
+ return hash(hash(hash(sig.m_file), hash(sig.m_name.c_str())), sig.m_line);
+ }
+
+ unsigned long long hash(const std::vector<SubcaseSignature>& sigs, size_t count) {
+ unsigned long long running = 0;
+ auto end = sigs.begin() + count;
+ for (auto it = sigs.begin(); it != end; it++) {
+ running = hash(running, hash(*it));
}
+ return running;
+ }
- s->subcasesCurrentMaxLevel = s->subcasesStack.size();
- m_entered = true;
+ unsigned long long hash(const std::vector<SubcaseSignature>& sigs) {
+ unsigned long long running = 0;
+ for (const SubcaseSignature& sig : sigs) {
+ running = hash(running, hash(sig));
+ }
+ return running;
+ }
+} // namespace
+namespace detail {
+ bool Subcase::checkFilters() {
+ if (g_cs->subcaseStack.size() < size_t(g_cs->subcase_filter_levels)) {
+ if (!matchesAny(m_signature.m_name.c_str(), g_cs->filters[6], true, g_cs->case_sensitive))
+ return true;
+ if (matchesAny(m_signature.m_name.c_str(), g_cs->filters[7], false, g_cs->case_sensitive))
+ return true;
+ }
+ return false;
+ }
- DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);
+ Subcase::Subcase(const String& name, const char* file, int line)
+ : m_signature({name, file, line}) {
+ if (!g_cs->reachedLeaf) {
+ if (g_cs->nextSubcaseStack.size() <= g_cs->subcaseStack.size()
+ || g_cs->nextSubcaseStack[g_cs->subcaseStack.size()] == m_signature) {
+ // Going down.
+ if (checkFilters()) { return; }
+
+ g_cs->subcaseStack.push_back(m_signature);
+ g_cs->currentSubcaseDepth++;
+ m_entered = true;
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);
+ }
+ } else {
+ if (g_cs->subcaseStack[g_cs->currentSubcaseDepth] == m_signature) {
+ // This subcase is reentered via control flow.
+ g_cs->currentSubcaseDepth++;
+ m_entered = true;
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);
+ } else if (g_cs->nextSubcaseStack.size() <= g_cs->currentSubcaseDepth
+ && g_cs->fullyTraversedSubcases.find(hash(hash(g_cs->subcaseStack, g_cs->currentSubcaseDepth), hash(m_signature)))
+ == g_cs->fullyTraversedSubcases.end()) {
+ if (checkFilters()) { return; }
+ // This subcase is part of the one to be executed next.
+ g_cs->nextSubcaseStack.clear();
+ g_cs->nextSubcaseStack.insert(g_cs->nextSubcaseStack.end(),
+ g_cs->subcaseStack.begin(), g_cs->subcaseStack.begin() + g_cs->currentSubcaseDepth);
+ g_cs->nextSubcaseStack.push_back(m_signature);
+ }
+ }
}
- DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
- DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
Subcase::~Subcase() {
- if(m_entered) {
- // only mark the subcase stack as passed if no subcases have been skipped
- if(g_cs->should_reenter == false)
- g_cs->subcasesPassed.insert(g_cs->subcasesStack);
- g_cs->subcasesStack.pop_back();
+ if (m_entered) {
+ g_cs->currentSubcaseDepth--;
+
+ if (!g_cs->reachedLeaf) {
+ // Leaf.
+ g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack));
+ g_cs->nextSubcaseStack.clear();
+ g_cs->reachedLeaf = true;
+ } else if (g_cs->nextSubcaseStack.empty()) {
+ // All children are finished.
+ g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack));
+ }
#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)
if(std::uncaught_exceptions() > 0
#else
if(std::uncaught_exception()
#endif
- && g_cs->shouldLogCurrentException) {
+ && g_cs->shouldLogCurrentException) {
DOCTEST_ITERATE_THROUGH_REPORTERS(
test_case_exception, {"exception thrown in subcase - will translate later "
- "when the whole test case has been exited (cannot "
- "translate while there is an active exception)",
- false});
+ "when the whole test case has been exited (cannot "
+ "translate while there is an active exception)",
+ false});
g_cs->shouldLogCurrentException = false;
}
+
DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);
}
}
- DOCTEST_CLANG_SUPPRESS_WARNING_POP
- DOCTEST_GCC_SUPPRESS_WARNING_POP
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP
Subcase::operator bool() const { return m_entered; }
TestSuite& TestSuite::operator*(const char* in) {
m_test_suite = in;
- // clear state
- m_description = nullptr;
- m_skip = false;
- m_no_breaks = false;
- m_no_output = false;
- m_may_fail = false;
- m_should_fail = false;
- m_expected_failures = 0;
- m_timeout = 0;
return *this;
}
TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
- const char* type, int template_id) {
+ const String& type, int template_id) {
m_file = file;
m_line = line;
m_name = nullptr; // will be later overridden in operator*
}
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function
- DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice
TestCase& TestCase::operator=(const TestCase& other) {
- static_cast<TestCaseData&>(*this) = static_cast<const TestCaseData&>(other);
-
+ TestCaseData::operator=(other);
m_test = other.m_test;
m_type = other.m_type;
m_template_id = other.m_template_id;
m_name = in;
// make a new name with an appended type for templated test case
if(m_template_id != -1) {
- m_full_name = String(m_name) + m_type;
+ m_full_name = String(m_name) + "<" + m_type + ">";
// redirect the name to point to the newly constructed full name
m_name = m_full_name.c_str();
}
return suiteOrderComparator(lhs, rhs);
}
-#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
- HANDLE g_stdoutHandle;
- WORD g_origFgAttrs;
- WORD g_origBgAttrs;
- bool g_attrsInitted = false;
-
- int colors_init() {
- if(!g_attrsInitted) {
- g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
- g_attrsInitted = true;
- CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
- GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo);
- g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED |
- BACKGROUND_BLUE | BACKGROUND_INTENSITY);
- g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED |
- FOREGROUND_BLUE | FOREGROUND_INTENSITY);
- }
- return 0;
- }
-
- int dumy_init_console_colors = colors_init();
-#endif // DOCTEST_CONFIG_COLORS_WINDOWS
-
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
void color_to_stream(std::ostream& s, Color::Enum code) {
static_cast<void>(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS
#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
if(g_no_colors ||
- (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false))
+ (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false))
return;
-#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs)
+ static struct ConsoleHelper {
+ HANDLE stdoutHandle;
+ WORD origFgAttrs;
+ WORD origBgAttrs;
+
+ ConsoleHelper() {
+ stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
+ CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+ GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo);
+ origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED |
+ BACKGROUND_BLUE | BACKGROUND_INTENSITY);
+ origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED |
+ FOREGROUND_BLUE | FOREGROUND_INTENSITY);
+ }
+ } ch;
+
+#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs)
// clang-format off
switch (code) {
case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
case Color::None:
case Color::Bright: // invalid
- default: DOCTEST_SET_ATTR(g_origFgAttrs);
+ default: DOCTEST_SET_ATTR(ch.origFgAttrs);
}
// clang-format on
#endif // DOCTEST_CONFIG_COLORS_WINDOWS
getExceptionTranslators().push_back(et);
}
-#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
- void toStream(std::ostream* s, char* in) { *s << in; }
- void toStream(std::ostream* s, const char* in) { *s << in; }
-#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
- void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; }
- void toStream(std::ostream* s, float in) { *s << in; }
- void toStream(std::ostream* s, double in) { *s << in; }
- void toStream(std::ostream* s, double long in) { *s << in; }
-
- void toStream(std::ostream* s, char in) { *s << in; }
- void toStream(std::ostream* s, char signed in) { *s << in; }
- void toStream(std::ostream* s, char unsigned in) { *s << in; }
- void toStream(std::ostream* s, int short in) { *s << in; }
- void toStream(std::ostream* s, int short unsigned in) { *s << in; }
- void toStream(std::ostream* s, int in) { *s << in; }
- void toStream(std::ostream* s, int unsigned in) { *s << in; }
- void toStream(std::ostream* s, int long in) { *s << in; }
- void toStream(std::ostream* s, int long unsigned in) { *s << in; }
- void toStream(std::ostream* s, int long long in) { *s << in; }
- void toStream(std::ostream* s, int long long unsigned in) { *s << in; }
-
DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO()
ContextScopeBase::ContextScopeBase() {
g_infoContexts.push_back(this);
}
- DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
- DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+ ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) noexcept {
+ if (other.need_to_destroy) {
+ other.destroy();
+ }
+ other.need_to_destroy = false;
+ g_infoContexts.push_back(this);
+ }
+
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
// destroy cannot be inlined into the destructor because that would mean calling stringify after
g_infoContexts.pop_back();
}
- DOCTEST_CLANG_SUPPRESS_WARNING_POP
- DOCTEST_GCC_SUPPRESS_WARNING_POP
+ DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ DOCTEST_GCC_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP
} // namespace detail
namespace {
static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) {
// Multiple threads may enter this filter/handler at once. We want the error message to be printed on the
// console just once no matter how many threads have crashed.
- static std::mutex mutex;
+ DOCTEST_DECLARE_STATIC_MUTEX(mutex)
static bool execute = true;
{
- std::lock_guard<std::mutex> lock(mutex);
+ DOCTEST_LOCK_MUTEX(mutex)
if(execute) {
bool reported = false;
for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
static unsigned int prev_abort_behavior;
static int prev_report_mode;
static _HFILE prev_report_file;
- static void (*prev_sigabrt_handler)(int);
+ static void (DOCTEST_CDECL *prev_sigabrt_handler)(int);
static std::terminate_handler original_terminate_handler;
static bool isSet;
static ULONG guaranteeSize;
unsigned int FatalConditionHandler::prev_abort_behavior;
int FatalConditionHandler::prev_report_mode;
_HFILE FatalConditionHandler::prev_report_file;
- void (*FatalConditionHandler::prev_sigabrt_handler)(int);
+ void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int);
std::terminate_handler FatalConditionHandler::original_terminate_handler;
bool FatalConditionHandler::isSet = false;
ULONG FatalConditionHandler::guaranteeSize = 0;
sigStack.ss_flags = 0;
sigaltstack(&sigStack, &oldSigStack);
struct sigaction sa = {};
- sa.sa_handler = handleSignal; // NOLINT
+ sa.sa_handler = handleSignal;
sa.sa_flags = SA_ONSTACK;
for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text)
#else
// TODO: integration with XCode and other IDEs
-#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros)
+#define DOCTEST_OUTPUT_DEBUG_STRING(text)
#endif // Platform
void addAssert(assertType::Enum at) {
DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true});
- while(g_cs->subcasesStack.size()) {
- g_cs->subcasesStack.pop_back();
+ while (g_cs->subcaseStack.size()) {
+ g_cs->subcaseStack.pop_back();
DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);
}
}
#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
} // namespace
-namespace detail {
- ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
- const char* exception_type, const char* exception_string) {
- m_test_case = g_cs->currentTest;
- m_at = at;
- m_file = file;
- m_line = line;
- m_expr = expr;
- m_failed = true;
- m_threw = false;
- m_threw_as = false;
- m_exception_type = exception_type;
- m_exception_string = exception_string;
+AssertData::AssertData(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const StringContains& exception_string)
+ : m_test_case(g_cs->currentTest), m_at(at), m_file(file), m_line(line), m_expr(expr),
+ m_failed(true), m_threw(false), m_threw_as(false), m_exception_type(exception_type),
+ m_exception_string(exception_string) {
#if DOCTEST_MSVC
- if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC
- ++m_expr;
+ if (m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC
+ ++m_expr;
#endif // MSVC
- }
+}
+
+namespace detail {
+ ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const String& exception_string)
+ : AssertData(at, file, line, expr, exception_type, exception_string) { }
+
+ ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+ const char* exception_type, const Contains& exception_string)
+ : AssertData(at, file, line, expr, exception_type, exception_string) { }
void ResultBuilder::setResult(const Result& res) {
m_decomp = res.m_decomp;
if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
m_failed = !m_threw;
} else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT
- m_failed = !m_threw_as || (m_exception != m_exception_string);
+ m_failed = !m_threw_as || !m_exception_string.check(m_exception);
} else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
m_failed = !m_threw_as;
} else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional
- m_failed = m_exception != m_exception_string;
+ m_failed = !m_exception_string.check(m_exception);
} else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
m_failed = m_threw;
}
if(m_exception.size())
- m_exception = String("\"") + m_exception + "\"";
+ m_exception = "\"" + m_exception + "\"";
if(is_running_in_test) {
addAssert(m_at);
std::abort();
}
- void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr,
- Result result) {
+ bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr,
+ const Result& result) {
bool failed = !result.m_passed;
// ###################################################################################
// ###################################################################################
DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp);
DOCTEST_ASSERT_IN_TESTS(result.m_decomp);
- // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+ return !failed;
}
MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) {
- m_stream = getTlsOss();
+ m_stream = tlssPush();
m_file = file;
m_line = line;
m_severity = severity;
}
- IExceptionTranslator::IExceptionTranslator() = default;
- IExceptionTranslator::~IExceptionTranslator() = default;
+ MessageBuilder::~MessageBuilder() {
+ if (!logged)
+ tlssPop();
+ }
+
+ DOCTEST_DEFINE_INTERFACE(IExceptionTranslator)
bool MessageBuilder::log() {
- m_string = getTlsOssResult();
+ if (!logged) {
+ m_string = tlssPop();
+ logged = true;
+ }
+
DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this);
const bool isWarn = m_severity & assertType::is_warn;
if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional
throwException();
}
-
- MessageBuilder::~MessageBuilder() = default;
} // namespace detail
namespace {
using namespace detail;
- template <typename Ex>
- DOCTEST_NORETURN void throw_exception(Ex const& e) {
-#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
- throw e;
-#else // DOCTEST_CONFIG_NO_EXCEPTIONS
- std::cerr << "doctest will terminate because it needed to throw an exception.\n"
- << "The message was: " << e.what() << '\n';
- std::terminate();
-#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
- }
-
-#ifndef DOCTEST_INTERNAL_ERROR
-#define DOCTEST_INTERNAL_ERROR(msg) \
- throw_exception(std::logic_error( \
- __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg))
-#endif // DOCTEST_INTERNAL_ERROR
-
// clang-format off
// =================================================================================================
void ensureTagClosed();
- private:
-
void writeDeclaration();
+ private:
+
void newlineIfNecessary();
bool m_tagIsOpen = false;
XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )
{
- writeDeclaration();
+ // writeDeclaration(); // called explicitly by the reporters that use the writer class - see issue #627
}
XmlWriter::~XmlWriter() {
struct XmlReporter : public IReporter
{
- XmlWriter xml;
- std::mutex mutex;
+ XmlWriter xml;
+ DOCTEST_DECLARE_MUTEX(mutex)
// caching pointers/references to objects of these types - safe to do
const ContextOptions& opt;
xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name)
.writeAttribute("testsuite", in.data[i]->m_test_suite)
.writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str()))
- .writeAttribute("line", line(in.data[i]->m_line));
+ .writeAttribute("line", line(in.data[i]->m_line))
+ .writeAttribute("skipped", in.data[i]->m_skip);
}
xml.scopedElement("OverallResultsTestCases")
.writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);
}
void test_run_start() override {
+ xml.writeDeclaration();
+
// remove .exe extension - mainly to have the same output on UNIX and Windows
std::string binary_name = skipPathFromFilename(opt.binary_name.c_str());
#ifdef DOCTEST_PLATFORM_WINDOWS
xml.startElement("OverallResultsAsserts")
.writeAttribute("successes",
st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest)
- .writeAttribute("failures", st.numAssertsFailedCurrentTest);
+ .writeAttribute("failures", st.numAssertsFailedCurrentTest)
+ .writeAttribute("test_case_success", st.testCaseSuccess);
if(opt.duration)
xml.writeAttribute("duration", st.seconds);
if(tc->m_expected_failures)
}
void test_case_exception(const TestCaseException& e) override {
- std::lock_guard<std::mutex> lock(mutex);
+ DOCTEST_LOCK_MUTEX(mutex)
xml.scopedElement("Exception")
.writeAttribute("crash", e.is_crash)
}
void subcase_start(const SubcaseSignature& in) override {
- std::lock_guard<std::mutex> lock(mutex);
-
xml.startElement("SubCase")
.writeAttribute("name", in.m_name)
.writeAttribute("filename", skipPathFromFilename(in.m_file))
if(!rb.m_failed && !opt.success)
return;
- std::lock_guard<std::mutex> lock(mutex);
+ DOCTEST_LOCK_MUTEX(mutex)
xml.startElement("Expression")
.writeAttribute("success", !rb.m_failed)
if(rb.m_at & assertType::is_throws_as)
xml.scopedElement("ExpectedException").writeText(rb.m_exception_type);
if(rb.m_at & assertType::is_throws_with)
- xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string);
+ xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string.c_str());
if((rb.m_at & assertType::is_normal) && !rb.m_threw)
xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str());
}
void log_message(const MessageData& mb) override {
- std::lock_guard<std::mutex> lock(mutex);
+ DOCTEST_LOCK_MUTEX(mutex)
xml.startElement("Message")
.writeAttribute("type", failureString(mb.m_severity))
} else if((rb.m_at & assertType::is_throws_as) &&
(rb.m_at & assertType::is_throws_with)) { //!OCLINT
s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""
- << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None;
+ << rb.m_exception_string.c_str()
+ << "\", " << rb.m_exception_type << " ) " << Color::None;
if(rb.m_threw) {
if(!rb.m_failed) {
s << "threw as expected!\n";
} else if(rb.m_at &
assertType::is_throws_with) { //!OCLINT bitwise operator in conditional
s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""
- << rb.m_exception_string << "\" ) " << Color::None
+ << rb.m_exception_string.c_str()
+ << "\" ) " << Color::None
<< (rb.m_threw ? (!rb.m_failed ? "threw as expected!" :
"threw a DIFFERENT exception: ") :
"did NOT throw at all!")
// - more attributes in tags
struct JUnitReporter : public IReporter
{
- XmlWriter xml;
- std::mutex mutex;
+ XmlWriter xml;
+ DOCTEST_DECLARE_MUTEX(mutex)
Timer timer;
std::vector<String> deepestSubcaseStackNames;
// WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
// =========================================================================================
- void report_query(const QueryData&) override {}
+ void report_query(const QueryData&) override {
+ xml.writeDeclaration();
+ }
- void test_run_start() override {}
+ void test_run_start() override {
+ xml.writeDeclaration();
+ }
void test_run_end(const TestRunStats& p) override {
// remove .exe extension - mainly to have the same output on UNIX and Windows
}
void test_case_exception(const TestCaseException& e) override {
- std::lock_guard<std::mutex> lock(mutex);
+ DOCTEST_LOCK_MUTEX(mutex)
testCaseData.addError("exception", e.error_string.c_str());
}
void subcase_start(const SubcaseSignature& in) override {
- std::lock_guard<std::mutex> lock(mutex);
deepestSubcaseStackNames.push_back(in.m_name);
}
if(!rb.m_failed) // report only failures & ignore the `success` option
return;
- std::lock_guard<std::mutex> lock(mutex);
+ DOCTEST_LOCK_MUTEX(mutex)
std::ostringstream os;
os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(")
bool hasLoggedCurrentTestStart;
std::vector<SubcaseSignature> subcasesStack;
size_t currentSubcaseLevel;
- std::mutex mutex;
+ DOCTEST_DECLARE_MUTEX(mutex)
// caching pointers/references to objects of these types - safe to do
const ContextOptions& opt;
}
void printIntro() {
- printVersion();
- s << Color::Cyan << "[doctest] " << Color::None
- << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n";
+ if(opt.no_intro == false) {
+ printVersion();
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n";
+ }
}
void printHelp() {
<< Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration=<bool> "
<< Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "no console output\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw=<bool> "
<< Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode=<bool> "
<< Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run=<bool> "
<< Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n";
+ s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro=<bool> "
+ << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version=<bool> "
<< Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors=<bool> "
printReporters(getReporters(), "reporters");
}
- void list_query_results() {
- separator_to_stream();
- if(opt.count || opt.list_test_cases) {
- s << Color::Cyan << "[doctest] " << Color::None
- << "unskipped test cases passing the current filters: "
- << g_cs->numTestCasesPassingFilters << "\n";
- } else if(opt.list_test_suites) {
- s << Color::Cyan << "[doctest] " << Color::None
- << "unskipped test cases passing the current filters: "
- << g_cs->numTestCasesPassingFilters << "\n";
- s << Color::Cyan << "[doctest] " << Color::None
- << "test suites with unskipped test cases passing the current filters: "
- << g_cs->numTestSuitesPassingFilters << "\n";
- }
- }
-
// =========================================================================================
// WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
// =========================================================================================
}
}
- void test_run_start() override { printIntro(); }
+ void test_run_start() override {
+ if(!opt.minimal)
+ printIntro();
+ }
void test_run_end(const TestRunStats& p) override {
+ if(opt.minimal && p.numTestCasesFailed == 0)
+ return;
+
separator_to_stream();
s << std::dec;
// log the preamble of the test case only if there is something
// else to print - something other than that an assert has failed
if(opt.duration ||
- (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure))
+ (st.failure_flags && st.failure_flags != static_cast<int>(TestCaseFailureReason::AssertFailure)))
logTestStart();
if(opt.duration)
}
void test_case_exception(const TestCaseException& e) override {
+ DOCTEST_LOCK_MUTEX(mutex)
if(tc->m_no_output)
return;
}
void subcase_start(const SubcaseSignature& subc) override {
- std::lock_guard<std::mutex> lock(mutex);
subcasesStack.push_back(subc);
++currentSubcaseLevel;
hasLoggedCurrentTestStart = false;
}
void subcase_end() override {
- std::lock_guard<std::mutex> lock(mutex);
--currentSubcaseLevel;
hasLoggedCurrentTestStart = false;
}
if((!rb.m_failed && !opt.success) || tc->m_no_output)
return;
- std::lock_guard<std::mutex> lock(mutex);
+ DOCTEST_LOCK_MUTEX(mutex)
logTestStart();
if(tc->m_no_output)
return;
- std::lock_guard<std::mutex> lock(mutex);
+ DOCTEST_LOCK_MUTEX(mutex)
logTestStart();
std::vector<String>& res) {
String filtersString;
if(parseOption(argc, argv, pattern, &filtersString)) {
- // tokenize with "," as a separator
- // cppcheck-suppress strtokCalled
- DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
- auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string
- while(pch != nullptr) {
- if(strlen(pch))
- res.push_back(pch);
- // uses the strtok() internal state to go to the next token
- // cppcheck-suppress strtokCalled
- pch = std::strtok(nullptr, ",");
+ // tokenize with "," as a separator, unless escaped with backslash
+ std::ostringstream s;
+ auto flush = [&s, &res]() {
+ auto string = s.str();
+ if(string.size() > 0) {
+ res.push_back(string.c_str());
+ }
+ s.str("");
+ };
+
+ bool seenBackslash = false;
+ const char* current = filtersString.c_str();
+ const char* end = current + strlen(current);
+ while(current != end) {
+ char character = *current++;
+ if(seenBackslash) {
+ seenBackslash = false;
+ if(character == ',' || character == '\\') {
+ s.put(character);
+ continue;
+ }
+ s.put('\\');
+ }
+ if(character == '\\') {
+ seenBackslash = true;
+ } else if(character == ',') {
+ flush();
+ } else {
+ s.put(character);
+ }
+ }
+
+ if(seenBackslash) {
+ s.put('\\');
}
- DOCTEST_CLANG_SUPPRESS_WARNING_POP
+ flush();
return true;
}
return false;
if(!parseOption(argc, argv, pattern, &parsedValue))
return false;
- if(type == 0) {
+ if(type) {
+ // integer
+ // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse...
+ int theInt = std::atoi(parsedValue.c_str());
+ if (theInt != 0) {
+ res = theInt; //!OCLINT parameter reassignment
+ return true;
+ }
+ } else {
// boolean
- const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1
- const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1
+ const char positive[][5] = { "1", "true", "on", "yes" }; // 5 - strlen("true") + 1
+ const char negative[][6] = { "0", "false", "off", "no" }; // 6 - strlen("false") + 1
// if the value matches any of the positive/negative possibilities
- for(unsigned i = 0; i < 4; i++) {
- if(parsedValue.compare(positive[i], true) == 0) {
+ for (unsigned i = 0; i < 4; i++) {
+ if (parsedValue.compare(positive[i], true) == 0) {
res = 1; //!OCLINT parameter reassignment
return true;
}
- if(parsedValue.compare(negative[i], true) == 0) {
+ if (parsedValue.compare(negative[i], true) == 0) {
res = 0; //!OCLINT parameter reassignment
return true;
}
}
- } else {
- // integer
- // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse...
- int theInt = std::atoi(parsedValue.c_str()); // NOLINT
- if(theInt != 0) {
- res = theInt; //!OCLINT parameter reassignment
- return true;
- }
}
return false;
}
DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false);
DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false);
DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false);
DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false);
DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false);
DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false);
+ DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false);
DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false);
DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false);
DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false);
curr.clear();
}
-// allows the user to override procedurally the int/bool options from the command line
+// allows the user to override procedurally the bool options from the command line
+void Context::setOption(const char* option, bool value) {
+ setOption(option, value ? "true" : "false");
+}
+
+// allows the user to override procedurally the int options from the command line
void Context::setOption(const char* option, int value) {
setOption(option, toString(value).c_str());
- // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
}
// allows the user to override procedurally the string options from the command line
void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; }
+void Context::setCout(std::ostream* out) { p->cout = out; }
+
+static class DiscardOStream : public std::ostream
+{
+private:
+ class : public std::streambuf
+ {
+ private:
+ // allowing some buffering decreases the amount of calls to overflow
+ char buf[1024];
+
+ protected:
+ std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; }
+
+ int_type overflow(int_type ch) override {
+ setp(std::begin(buf), std::end(buf));
+ return traits_type::not_eof(ch);
+ }
+ } discardBuf;
+
+public:
+ DiscardOStream()
+ : std::ostream(&discardBuf) {}
+} discardOut;
+
// the main function that does all the filtering and test running
int Context::run() {
using namespace detail;
g_no_colors = p->no_colors;
p->resetRunData();
- // stdout by default
- p->cout = &std::cout;
- p->cerr = &std::cerr;
-
- // or to a file if specified
std::fstream fstr;
- if(p->out.size()) {
- fstr.open(p->out.c_str(), std::fstream::out);
- p->cout = &fstr;
+ if(p->cout == nullptr) {
+ if(p->quiet) {
+ p->cout = &discardOut;
+ } else if(p->out.size()) {
+ // to a file if specified
+ fstr.open(p->out.c_str(), std::fstream::out);
+ p->cout = &fstr;
+ } else {
+ // stdout by default
+ p->cout = &std::cout;
+ }
}
FatalConditionHandler::allocateAltStackMem();
// random_shuffle implementation
const auto first = &testArray[0];
for(size_t i = testArray.size() - 1; i > 0; --i) {
- int idxToSwap = std::rand() % (i + 1); // NOLINT
+ int idxToSwap = std::rand() % (i + 1);
const auto temp = first[i];
p->numAssertsFailedCurrentTest_atomic = 0;
p->numAssertsCurrentTest_atomic = 0;
- p->subcasesPassed.clear();
+ p->fullyTraversedSubcases.clear();
DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);
do {
// reset some of the fields for subcases (except for the set of fully passed ones)
- p->should_reenter = false;
- p->subcasesCurrentMaxLevel = 0;
- p->subcasesStack.clear();
+ p->reachedLeaf = false;
+ // May not be empty if previous subcase exited via exception.
+ p->subcaseStack.clear();
+ p->currentSubcaseDepth = 0;
p->shouldLogCurrentException = true;
p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts;
}
- if(p->should_reenter && run_test)
+ if(!p->nextSubcaseStack.empty() && run_test)
DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc);
- if(!p->should_reenter)
+ if(p->nextSubcaseStack.empty())
run_test = false;
} while(run_test);
DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata);
}
- // see these issues on the reasoning for this:
- // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903
- // - https://github.com/onqtam/doctest/issues/126
- auto DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS = []() DOCTEST_NOINLINE
- { std::cout << std::string(); };
- DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS();
-
return cleanup_and_return();
}
-IReporter::~IReporter() = default;
+DOCTEST_DEFINE_INTERFACE(IReporter)
int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); }
const IContextScope* const* IReporter::get_active_contexts() {
DOCTEST_MSVC_SUPPRESS_WARNING_POP
DOCTEST_GCC_SUPPRESS_WARNING_POP
+DOCTEST_SUPPRESS_COMMON_WARNINGS_POP
+
#endif // DOCTEST_LIBRARY_IMPLEMENTATION
#endif // DOCTEST_CONFIG_IMPLEMENT
-// Formatting library for C++ - the core API
+// Formatting library for C++ - the core API for char/UTF-8
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
#ifndef FMT_CORE_H_
#define FMT_CORE_H_
-#include <cstdio> // std::FILE
+#include <cstddef> // std::byte
+#include <cstdio> // std::FILE
#include <cstring>
-#include <functional>
#include <iterator>
-#include <memory>
+#include <limits>
#include <string>
#include <type_traits>
-#include <vector>
// The fmt library version in the form major * 10000 + minor * 100 + patch.
-#define FMT_VERSION 70103
+#define FMT_VERSION 80101
-#ifdef __clang__
+#if defined(__clang__) && !defined(__ibmxl__)
# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
#else
# define FMT_CLANG_VERSION 0
#endif
-#if defined(__GNUC__) && !defined(__clang__)
+#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && \
+ !defined(__NVCOMPILER)
# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
#else
# define FMT_GCC_VERSION 0
#endif
-#if defined(__INTEL_COMPILER)
-# define FMT_ICC_VERSION __INTEL_COMPILER
-#else
-# define FMT_ICC_VERSION 0
+#ifndef FMT_GCC_PRAGMA
+// Workaround _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884.
+# if FMT_GCC_VERSION >= 504
+# define FMT_GCC_PRAGMA(arg) _Pragma(arg)
+# else
+# define FMT_GCC_PRAGMA(arg)
+# endif
#endif
-#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
-# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION
+#ifdef __ICL
+# define FMT_ICC_VERSION __ICL
+#elif defined(__INTEL_COMPILER)
+# define FMT_ICC_VERSION __INTEL_COMPILER
#else
-# define FMT_HAS_GXX_CXX11 0
+# define FMT_ICC_VERSION 0
#endif
#ifdef __NVCC__
#ifdef _MSC_VER
# define FMT_MSC_VER _MSC_VER
-# define FMT_SUPPRESS_MSC_WARNING(n) __pragma(warning(suppress : n))
+# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__))
#else
# define FMT_MSC_VER 0
-# define FMT_SUPPRESS_MSC_WARNING(n)
+# define FMT_MSC_WARNING(...)
#endif
#ifdef __has_feature
# define FMT_HAS_FEATURE(x) 0
#endif
-#if defined(__has_include) && !defined(__INTELLISENSE__) && \
+#if defined(__has_include) && \
+ (!defined(__INTELLISENSE__) || FMT_MSC_VER > 1900) && \
(!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600)
# define FMT_HAS_INCLUDE(x) __has_include(x)
#else
# define FMT_HAS_CPP_ATTRIBUTE(x) 0
#endif
+#ifdef _MSVC_LANG
+# define FMT_CPLUSPLUS _MSVC_LANG
+#else
+# define FMT_CPLUSPLUS __cplusplus
+#endif
+
#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \
- (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute))
+ (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute))
#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \
- (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute))
+ (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute))
// Check if relaxed C++14 constexpr is supported.
// GCC doesn't allow throw in constexpr until version 6 (bug 67371).
#ifndef FMT_USE_CONSTEXPR
# define FMT_USE_CONSTEXPR \
- (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \
+ (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1912 || \
(FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) && \
!FMT_NVCC && !FMT_ICC_VERSION
#endif
# define FMT_CONSTEXPR constexpr
# define FMT_CONSTEXPR_DECL constexpr
#else
-# define FMT_CONSTEXPR inline
+# define FMT_CONSTEXPR
# define FMT_CONSTEXPR_DECL
#endif
-#ifndef FMT_OVERRIDE
-# if FMT_HAS_FEATURE(cxx_override_control) || \
- (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
-# define FMT_OVERRIDE override
-# else
-# define FMT_OVERRIDE
+#if ((__cplusplus >= 202002L) && \
+ (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \
+ (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
+# define FMT_CONSTEXPR20 constexpr
+#else
+# define FMT_CONSTEXPR20
+#endif
+
+// Check if constexpr std::char_traits<>::compare,length is supported.
+#if defined(__GLIBCXX__)
+# if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \
+ _GLIBCXX_RELEASE >= 7 // GCC 7+ libstdc++ has _GLIBCXX_RELEASE.
+# define FMT_CONSTEXPR_CHAR_TRAITS constexpr
# endif
+#elif defined(_LIBCPP_VERSION) && __cplusplus >= 201703L && \
+ _LIBCPP_VERSION >= 4000
+# define FMT_CONSTEXPR_CHAR_TRAITS constexpr
+#elif FMT_MSC_VER >= 1914 && _MSVC_LANG >= 201703L
+# define FMT_CONSTEXPR_CHAR_TRAITS constexpr
+#endif
+#ifndef FMT_CONSTEXPR_CHAR_TRAITS
+# define FMT_CONSTEXPR_CHAR_TRAITS
#endif
// Check if exceptions are disabled.
#endif
#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \
- (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
+ FMT_GCC_VERSION >= 408 || FMT_MSC_VER >= 1900
# define FMT_DETECTED_NOEXCEPT noexcept
# define FMT_HAS_CXX11_NOEXCEPT 1
#else
# define FMT_NORETURN
#endif
-#ifndef FMT_DEPRECATED
-# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900
-# define FMT_DEPRECATED [[deprecated]]
+#if __cplusplus == 201103L || __cplusplus == 201402L
+# if defined(__INTEL_COMPILER) || defined(__PGI)
+# define FMT_FALLTHROUGH
+# elif defined(__clang__)
+# define FMT_FALLTHROUGH [[clang::fallthrough]]
+# elif FMT_GCC_VERSION >= 700 && \
+ (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520)
+# define FMT_FALLTHROUGH [[gnu::fallthrough]]
# else
-# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__)
-# define FMT_DEPRECATED __attribute__((deprecated))
-# elif FMT_MSC_VER
-# define FMT_DEPRECATED __declspec(deprecated)
-# else
-# define FMT_DEPRECATED /* deprecated */
-# endif
+# define FMT_FALLTHROUGH
# endif
+#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough)
+# define FMT_FALLTHROUGH [[fallthrough]]
+#else
+# define FMT_FALLTHROUGH
#endif
-// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers.
-#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC
-# define FMT_DEPRECATED_ALIAS
-#else
-# define FMT_DEPRECATED_ALIAS FMT_DEPRECATED
+#ifndef FMT_NODISCARD
+# if FMT_HAS_CPP17_ATTRIBUTE(nodiscard)
+# define FMT_NODISCARD [[nodiscard]]
+# else
+# define FMT_NODISCARD
+# endif
+#endif
+
+#ifndef FMT_USE_FLOAT
+# define FMT_USE_FLOAT 1
+#endif
+#ifndef FMT_USE_DOUBLE
+# define FMT_USE_DOUBLE 1
+#endif
+#ifndef FMT_USE_LONG_DOUBLE
+# define FMT_USE_LONG_DOUBLE 1
#endif
#ifndef FMT_INLINE
# endif
#endif
-#ifndef FMT_USE_INLINE_NAMESPACES
-# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
- (FMT_MSC_VER >= 1900 && !_MANAGED)
-# define FMT_USE_INLINE_NAMESPACES 1
+#ifndef FMT_DEPRECATED
+# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900
+# define FMT_DEPRECATED [[deprecated]]
# else
-# define FMT_USE_INLINE_NAMESPACES 0
+# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__)
+# define FMT_DEPRECATED __attribute__((deprecated))
+# elif FMT_MSC_VER
+# define FMT_DEPRECATED __declspec(deprecated)
+# else
+# define FMT_DEPRECATED /* deprecated */
+# endif
# endif
#endif
#ifndef FMT_BEGIN_NAMESPACE
-# if FMT_USE_INLINE_NAMESPACES
-# define FMT_INLINE_NAMESPACE inline namespace
-# define FMT_END_NAMESPACE \
- } \
- }
-# else
-# define FMT_INLINE_NAMESPACE namespace
-# define FMT_END_NAMESPACE \
- } \
- using namespace v7; \
- }
-# endif
# define FMT_BEGIN_NAMESPACE \
namespace fmt { \
- FMT_INLINE_NAMESPACE v7 {
+ inline namespace v8 {
+# define FMT_END_NAMESPACE \
+ } \
+ }
+#endif
+
+#ifndef FMT_MODULE_EXPORT
+# define FMT_MODULE_EXPORT
+# define FMT_MODULE_EXPORT_BEGIN
+# define FMT_MODULE_EXPORT_END
+# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
+# define FMT_END_DETAIL_NAMESPACE }
#endif
#if !defined(FMT_HEADER_ONLY) && defined(_WIN32)
-# define FMT_CLASS_API FMT_SUPPRESS_MSC_WARNING(4275)
+# define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275)
# ifdef FMT_EXPORT
# define FMT_API __declspec(dllexport)
-# define FMT_EXTERN_TEMPLATE_API FMT_API
-# define FMT_EXPORTED
# elif defined(FMT_SHARED)
# define FMT_API __declspec(dllimport)
-# define FMT_EXTERN_TEMPLATE_API FMT_API
# endif
#else
# define FMT_CLASS_API
+# if defined(FMT_EXPORT) || defined(FMT_SHARED)
+# if defined(__GNUC__) || defined(__clang__)
+# define FMT_API __attribute__((visibility("default")))
+# endif
+# endif
#endif
#ifndef FMT_API
# define FMT_API
#endif
-#ifndef FMT_EXTERN_TEMPLATE_API
-# define FMT_EXTERN_TEMPLATE_API
-#endif
-#ifndef FMT_INSTANTIATION_DEF_API
-# define FMT_INSTANTIATION_DEF_API FMT_API
-#endif
-
-#ifndef FMT_HEADER_ONLY
-# define FMT_EXTERN extern
-#else
-# define FMT_EXTERN
-#endif
// libc++ supports string_view in pre-c++17.
#if (FMT_HAS_INCLUDE(<string_view>) && \
#ifndef FMT_UNICODE
# define FMT_UNICODE !FMT_MSC_VER
#endif
-#if FMT_UNICODE && FMT_MSC_VER
-# pragma execution_character_set("utf-8")
+
+#ifndef FMT_CONSTEVAL
+# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \
+ __cplusplus > 201703L && !defined(__apple_build_version__)) || \
+ (defined(__cpp_consteval) && \
+ (!FMT_MSC_VER || _MSC_FULL_VER >= 193030704))
+// consteval is broken in MSVC before VS2022 and Apple clang 13.
+# define FMT_CONSTEVAL consteval
+# define FMT_HAS_CONSTEVAL
+# else
+# define FMT_CONSTEVAL
+# endif
+#endif
+
+#ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+# if defined(__cpp_nontype_template_args) && \
+ ((FMT_GCC_VERSION >= 903 && __cplusplus >= 201709L) || \
+ __cpp_nontype_template_args >= 201911L)
+# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 1
+# else
+# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0
+# endif
+#endif
+
+// Enable minimal optimizations for more compact code in debug mode.
+FMT_GCC_PRAGMA("GCC push_options")
+#ifndef __OPTIMIZE__
+FMT_GCC_PRAGMA("GCC optimize(\"Og\")")
#endif
FMT_BEGIN_NAMESPACE
+FMT_MODULE_EXPORT_BEGIN
// Implementations of enable_if_t and other metafunctions for older systems.
-template <bool B, class T = void>
+template <bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
-template <bool B, class T, class F>
+template <bool B, typename T, typename F>
using conditional_t = typename std::conditional<B, T, F>::type;
template <bool B> using bool_constant = std::integral_constant<bool, B>;
template <typename T>
template <typename T> struct type_identity { using type = T; };
template <typename T> using type_identity_t = typename type_identity<T>::type;
-struct monostate {};
+struct monostate {
+ constexpr monostate() {}
+};
// An enable_if helper to be used in template parameters which results in much
// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed
// to workaround a bug in MSVC 2019 (see #1140 and #1186).
-#define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0
+#ifdef FMT_DOC
+# define FMT_ENABLE_IF(...)
+#else
+# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0
+#endif
+
+FMT_BEGIN_DETAIL_NAMESPACE
-namespace detail {
+// Suppress "unused variable" warnings with the method described in
+// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/.
+// (void)var does not work on many Intel compilers.
+template <typename... T> FMT_CONSTEXPR void ignore_unused(const T&...) {}
+
+constexpr FMT_INLINE auto is_constant_evaluated(bool default_value = false)
+ FMT_NOEXCEPT -> bool {
+#ifdef __cpp_lib_is_constant_evaluated
+ ignore_unused(default_value);
+ return std::is_constant_evaluated();
+#else
+ return default_value;
+#endif
+}
-// A helper function to suppress "conditional expression is constant" warnings.
-template <typename T> constexpr T const_check(T value) { return value; }
+// A function to suppress "conditional expression is constant" warnings.
+template <typename T> constexpr FMT_INLINE auto const_check(T value) -> T {
+ return value;
+}
FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
const char* message);
#ifndef FMT_ASSERT
# ifdef NDEBUG
// FMT_ASSERT is not empty to avoid -Werror=empty-body.
-# define FMT_ASSERT(condition, message) ((void)0)
+# define FMT_ASSERT(condition, message) \
+ ::fmt::detail::ignore_unused((condition), (message))
# else
# define FMT_ASSERT(condition, message) \
((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \
# endif
#endif
+#ifdef __cpp_lib_byte
+using byte = std::byte;
+#else
+enum class byte : unsigned char {};
+#endif
+
#if defined(FMT_USE_STRING_VIEW)
template <typename Char> using std_string_view = std::basic_string_view<Char>;
#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW)
# define FMT_USE_INT128 1
using int128_t = __int128_t;
using uint128_t = __uint128_t;
+template <typename T> inline auto convert_for_visit(T value) -> T {
+ return value;
+}
#else
# define FMT_USE_INT128 0
#endif
#if !FMT_USE_INT128
-struct int128_t {};
-struct uint128_t {};
+enum class int128_t {};
+enum class uint128_t {};
+// Reduce template instantiations.
+template <typename T> inline auto convert_for_visit(T) -> monostate {
+ return {};
+}
#endif
// Casts a nonnegative integer to unsigned.
template <typename Int>
-FMT_CONSTEXPR typename std::make_unsigned<Int>::type to_unsigned(Int value) {
+FMT_CONSTEXPR auto to_unsigned(Int value) ->
+ typename std::make_unsigned<Int>::type {
FMT_ASSERT(value >= 0, "negative value");
return static_cast<typename std::make_unsigned<Int>::type>(value);
}
-FMT_SUPPRESS_MSC_WARNING(4566) constexpr unsigned char micro[] = "\u00B5";
+FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5";
-template <typename Char> constexpr bool is_unicode() {
- return FMT_UNICODE || sizeof(Char) != 1 ||
- (sizeof(micro) == 3 && micro[0] == 0xC2 && micro[1] == 0xB5);
+constexpr auto is_utf8() -> bool {
+ // Avoid buggy sign extensions in MSVC's constant evaluation mode.
+ // https://developercommunity.visualstudio.com/t/C-difference-in-behavior-for-unsigned/1233612
+ using uchar = unsigned char;
+ return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 &&
+ uchar(micro[1]) == 0xB5);
}
-
-#ifdef __cpp_char8_t
-using char8_type = char8_t;
-#else
-enum char8_type : unsigned char {};
-#endif
-} // namespace detail
-
-#ifdef FMT_USE_INTERNAL
-namespace internal = detail; // DEPRECATED
-#endif
+FMT_END_DETAIL_NAMESPACE
/**
An implementation of ``std::basic_string_view`` for pre-C++17. It provides a
the size with ``std::char_traits<Char>::length``.
\endrst
*/
-#if __cplusplus >= 201703L // C++17's char_traits::length() is constexpr.
- FMT_CONSTEXPR
-#endif
+ FMT_CONSTEXPR_CHAR_TRAITS
+ FMT_INLINE
basic_string_view(const Char* s)
- : data_(s), size_(std::char_traits<Char>::length(s)) {}
+ : data_(s),
+ size_(detail::const_check(std::is_same<Char, char>::value &&
+ !detail::is_constant_evaluated(true))
+ ? std::strlen(reinterpret_cast<const char*>(s))
+ : std::char_traits<Char>::length(s)) {}
/** Constructs a string reference from a ``std::basic_string`` object. */
template <typename Traits, typename Alloc>
size_(s.size()) {}
/** Returns a pointer to the string data. */
- constexpr const Char* data() const { return data_; }
+ constexpr auto data() const FMT_NOEXCEPT -> const Char* { return data_; }
/** Returns the string size. */
- constexpr size_t size() const { return size_; }
+ constexpr auto size() const FMT_NOEXCEPT -> size_t { return size_; }
- constexpr iterator begin() const { return data_; }
- constexpr iterator end() const { return data_ + size_; }
+ constexpr auto begin() const FMT_NOEXCEPT -> iterator { return data_; }
+ constexpr auto end() const FMT_NOEXCEPT -> iterator { return data_ + size_; }
- constexpr const Char& operator[](size_t pos) const { return data_[pos]; }
+ constexpr auto operator[](size_t pos) const FMT_NOEXCEPT -> const Char& {
+ return data_[pos];
+ }
- FMT_CONSTEXPR void remove_prefix(size_t n) {
+ FMT_CONSTEXPR void remove_prefix(size_t n) FMT_NOEXCEPT {
data_ += n;
size_ -= n;
}
// Lexicographically compare this string reference to other.
- int compare(basic_string_view other) const {
+ FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int {
size_t str_size = size_ < other.size_ ? size_ : other.size_;
int result = std::char_traits<Char>::compare(data_, other.data_, str_size);
if (result == 0)
return result;
}
- friend bool operator==(basic_string_view lhs, basic_string_view rhs) {
+ FMT_CONSTEXPR_CHAR_TRAITS friend auto operator==(basic_string_view lhs,
+ basic_string_view rhs)
+ -> bool {
return lhs.compare(rhs) == 0;
}
- friend bool operator!=(basic_string_view lhs, basic_string_view rhs) {
+ friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool {
return lhs.compare(rhs) != 0;
}
- friend bool operator<(basic_string_view lhs, basic_string_view rhs) {
+ friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool {
return lhs.compare(rhs) < 0;
}
- friend bool operator<=(basic_string_view lhs, basic_string_view rhs) {
+ friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool {
return lhs.compare(rhs) <= 0;
}
- friend bool operator>(basic_string_view lhs, basic_string_view rhs) {
+ friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool {
return lhs.compare(rhs) > 0;
}
- friend bool operator>=(basic_string_view lhs, basic_string_view rhs) {
+ friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool {
return lhs.compare(rhs) >= 0;
}
};
using string_view = basic_string_view<char>;
-using wstring_view = basic_string_view<wchar_t>;
/** Specifies if ``T`` is a character type. Can be specialized by users. */
template <typename T> struct is_char : std::false_type {};
template <> struct is_char<char> : std::true_type {};
-template <> struct is_char<wchar_t> : std::true_type {};
-template <> struct is_char<detail::char8_type> : std::true_type {};
-template <> struct is_char<char16_t> : std::true_type {};
-template <> struct is_char<char32_t> : std::true_type {};
-
-/**
- \rst
- Returns a string view of `s`. In order to add custom string type support to
- {fmt} provide an overload of `to_string_view` for it in the same namespace as
- the type for the argument-dependent lookup to work.
- **Example**::
-
- namespace my_ns {
- inline string_view to_string_view(const my_string& s) {
- return {s.data(), s.length()};
- }
- }
- std::string message = fmt::format(my_string("The answer is {}"), 42);
- \endrst
- */
+// Returns a string view of `s`.
template <typename Char, FMT_ENABLE_IF(is_char<Char>::value)>
-inline basic_string_view<Char> to_string_view(const Char* s) {
+FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view<Char> {
return s;
}
-
template <typename Char, typename Traits, typename Alloc>
-inline basic_string_view<Char> to_string_view(
- const std::basic_string<Char, Traits, Alloc>& s) {
+inline auto to_string_view(const std::basic_string<Char, Traits, Alloc>& s)
+ -> basic_string_view<Char> {
return s;
}
-
template <typename Char>
-inline basic_string_view<Char> to_string_view(basic_string_view<Char> s) {
+constexpr auto to_string_view(basic_string_view<Char> s)
+ -> basic_string_view<Char> {
return s;
}
-
template <typename Char,
FMT_ENABLE_IF(!std::is_empty<detail::std_string_view<Char>>::value)>
-inline basic_string_view<Char> to_string_view(detail::std_string_view<Char> s) {
+inline auto to_string_view(detail::std_string_view<Char> s)
+ -> basic_string_view<Char> {
return s;
}
struct is_compile_string : std::is_base_of<compile_string, S> {};
template <typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
-constexpr basic_string_view<typename S::char_type> to_string_view(const S& s) {
- return s;
+constexpr auto to_string_view(const S& s)
+ -> basic_string_view<typename S::char_type> {
+ return basic_string_view<typename S::char_type>(s);
}
-namespace detail {
+FMT_BEGIN_DETAIL_NAMESPACE
+
void to_string_view(...);
-using fmt::v7::to_string_view;
+using fmt::to_string_view;
// Specifies whether S is a string type convertible to fmt::basic_string_view.
// It should be a constexpr function but MSVC 2017 fails to compile it in
template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
void check_format_string(S);
+FMT_NORETURN FMT_API void throw_format_error(const char* message);
+
struct error_handler {
constexpr error_handler() = default;
constexpr error_handler(const error_handler&) = default;
// This function is intentionally not constexpr to give a compile-time error.
FMT_NORETURN FMT_API void on_error(const char* message);
};
-} // namespace detail
+FMT_END_DETAIL_NAMESPACE
/** String's character type. */
template <typename S> using char_t = typename detail::char_t_impl<S>::type;
\rst
Parsing context consisting of a format string range being parsed and an
argument counter for automatic indexing.
-
- You can use one of the following type aliases for common character types:
-
- +-----------------------+-------------------------------------+
- | Type | Definition |
- +=======================+=====================================+
- | format_parse_context | basic_format_parse_context<char> |
- +-----------------------+-------------------------------------+
- | wformat_parse_context | basic_format_parse_context<wchar_t> |
- +-----------------------+-------------------------------------+
+ You can use the ``format_parse_context`` type alias for ``char`` instead.
\endrst
*/
template <typename Char, typename ErrorHandler = detail::error_handler>
Returns an iterator to the beginning of the format string range being
parsed.
*/
- constexpr iterator begin() const FMT_NOEXCEPT { return format_str_.begin(); }
+ constexpr auto begin() const FMT_NOEXCEPT -> iterator {
+ return format_str_.begin();
+ }
/**
Returns an iterator past the end of the format string range being parsed.
*/
- constexpr iterator end() const FMT_NOEXCEPT { return format_str_.end(); }
+ constexpr auto end() const FMT_NOEXCEPT -> iterator {
+ return format_str_.end();
+ }
/** Advances the begin iterator to ``it``. */
FMT_CONSTEXPR void advance_to(iterator it) {
Reports an error if using the manual argument indexing; otherwise returns
the next argument index and switches to the automatic indexing.
*/
- FMT_CONSTEXPR int next_arg_id() {
+ FMT_CONSTEXPR auto next_arg_id() -> int {
// Don't check if the argument id is valid to avoid overhead and because it
// will be checked during formatting anyway.
if (next_arg_id_ >= 0) return next_arg_id_++;
ErrorHandler::on_error(message);
}
- constexpr ErrorHandler error_handler() const { return *this; }
+ constexpr auto error_handler() const -> ErrorHandler { return *this; }
};
using format_parse_context = basic_format_parse_context<char>;
-using wformat_parse_context = basic_format_parse_context<wchar_t>;
template <typename Context> class basic_format_arg;
template <typename Context> class basic_format_args;
template <typename Char>
struct is_contiguous<std::basic_string<Char>> : std::true_type {};
-namespace detail {
+class appender;
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+template <typename Context, typename T>
+constexpr auto has_const_formatter_impl(T*)
+ -> decltype(typename Context::template formatter_type<T>().format(
+ std::declval<const T&>(), std::declval<Context&>()),
+ true) {
+ return true;
+}
+template <typename Context>
+constexpr auto has_const_formatter_impl(...) -> bool {
+ return false;
+}
+template <typename T, typename Context>
+constexpr auto has_const_formatter() -> bool {
+ return has_const_formatter_impl<Context>(static_cast<T*>(nullptr));
+}
// Extracts a reference to the container from back_insert_iterator.
template <typename Container>
-inline Container& get_container(std::back_insert_iterator<Container> it) {
+inline auto get_container(std::back_insert_iterator<Container> it)
+ -> Container& {
using bi_iterator = std::back_insert_iterator<Container>;
struct accessor : bi_iterator {
accessor(bi_iterator iter) : bi_iterator(iter) {}
return *accessor(it).container;
}
+template <typename Char, typename InputIt, typename OutputIt>
+FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out)
+ -> OutputIt {
+ while (begin != end) *out++ = static_cast<Char>(*begin++);
+ return out;
+}
+
+template <typename Char, typename T, typename U,
+ FMT_ENABLE_IF(
+ std::is_same<remove_const_t<T>, U>::value&& is_char<U>::value)>
+FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* {
+ if (is_constant_evaluated()) return copy_str<Char, T*, U*>(begin, end, out);
+ auto size = to_unsigned(end - begin);
+ memcpy(out, begin, size * sizeof(U));
+ return out + size;
+}
+
/**
\rst
A contiguous memory buffer with an optional growing ability. It is an internal
protected:
// Don't initialize ptr_ since it is not accessed to save a few cycles.
- FMT_SUPPRESS_MSC_WARNING(26495)
+ FMT_MSC_WARNING(suppress : 26495)
buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {}
- buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT
- : ptr_(p),
- size_(sz),
- capacity_(cap) {}
+ FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0,
+ size_t cap = 0) FMT_NOEXCEPT : ptr_(p),
+ size_(sz),
+ capacity_(cap) {}
- ~buffer() = default;
+ FMT_CONSTEXPR20 ~buffer() = default;
+ buffer(buffer&&) = default;
/** Sets the buffer data and capacity. */
- void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT {
+ FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT {
ptr_ = buf_data;
capacity_ = buf_capacity;
}
/** Increases the buffer capacity to hold at least *capacity* elements. */
- virtual void grow(size_t capacity) = 0;
+ virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0;
public:
using value_type = T;
buffer(const buffer&) = delete;
void operator=(const buffer&) = delete;
- T* begin() FMT_NOEXCEPT { return ptr_; }
- T* end() FMT_NOEXCEPT { return ptr_ + size_; }
+ auto begin() FMT_NOEXCEPT -> T* { return ptr_; }
+ auto end() FMT_NOEXCEPT -> T* { return ptr_ + size_; }
- const T* begin() const FMT_NOEXCEPT { return ptr_; }
- const T* end() const FMT_NOEXCEPT { return ptr_ + size_; }
+ auto begin() const FMT_NOEXCEPT -> const T* { return ptr_; }
+ auto end() const FMT_NOEXCEPT -> const T* { return ptr_ + size_; }
/** Returns the size of this buffer. */
- size_t size() const FMT_NOEXCEPT { return size_; }
+ constexpr auto size() const FMT_NOEXCEPT -> size_t { return size_; }
/** Returns the capacity of this buffer. */
- size_t capacity() const FMT_NOEXCEPT { return capacity_; }
+ constexpr auto capacity() const FMT_NOEXCEPT -> size_t { return capacity_; }
/** Returns a pointer to the buffer data. */
- T* data() FMT_NOEXCEPT { return ptr_; }
+ FMT_CONSTEXPR auto data() FMT_NOEXCEPT -> T* { return ptr_; }
/** Returns a pointer to the buffer data. */
- const T* data() const FMT_NOEXCEPT { return ptr_; }
+ FMT_CONSTEXPR auto data() const FMT_NOEXCEPT -> const T* { return ptr_; }
/** Clears this buffer. */
void clear() { size_ = 0; }
// Tries resizing the buffer to contain *count* elements. If T is a POD type
// the new elements may not be initialized.
- void try_resize(size_t count) {
+ FMT_CONSTEXPR20 void try_resize(size_t count) {
try_reserve(count);
size_ = count <= capacity_ ? count : capacity_;
}
// capacity by a smaller amount than requested but guarantees there is space
// for at least one additional element either by increasing the capacity or by
// flushing the buffer if it is full.
- void try_reserve(size_t new_capacity) {
+ FMT_CONSTEXPR20 void try_reserve(size_t new_capacity) {
if (new_capacity > capacity_) grow(new_capacity);
}
- void push_back(const T& value) {
+ FMT_CONSTEXPR20 void push_back(const T& value) {
try_reserve(size_ + 1);
ptr_[size_++] = value;
}
/** Appends data to the end of the buffer. */
template <typename U> void append(const U* begin, const U* end);
- template <typename I> T& operator[](I index) { return ptr_[index]; }
- template <typename I> const T& operator[](I index) const {
+ template <typename I> FMT_CONSTEXPR auto operator[](I index) -> T& {
+ return ptr_[index];
+ }
+ template <typename I>
+ FMT_CONSTEXPR auto operator[](I index) const -> const T& {
return ptr_[index];
}
};
struct buffer_traits {
explicit buffer_traits(size_t) {}
- size_t count() const { return 0; }
- size_t limit(size_t size) { return size; }
+ auto count() const -> size_t { return 0; }
+ auto limit(size_t size) -> size_t { return size; }
};
class fixed_buffer_traits {
public:
explicit fixed_buffer_traits(size_t limit) : limit_(limit) {}
- size_t count() const { return count_; }
- size_t limit(size_t size) {
+ auto count() const -> size_t { return count_; }
+ auto limit(size_t size) -> size_t {
size_t n = limit_ > count_ ? limit_ - count_ : 0;
count_ += size;
return size < n ? size : n;
T data_[buffer_size];
protected:
- void grow(size_t) final FMT_OVERRIDE {
+ FMT_CONSTEXPR20 void grow(size_t) override {
if (this->size() == buffer_size) flush();
}
- void flush();
+
+ void flush() {
+ auto size = this->size();
+ this->clear();
+ out_ = copy_str<T>(data_, data_ + this->limit(size), out_);
+ }
public:
explicit iterator_buffer(OutputIt out, size_t n = buffer_size)
- : Traits(n),
- buffer<T>(data_, 0, buffer_size),
- out_(out) {}
+ : Traits(n), buffer<T>(data_, 0, buffer_size), out_(out) {}
+ iterator_buffer(iterator_buffer&& other)
+ : Traits(other), buffer<T>(data_, 0, buffer_size), out_(other.out_) {}
+ ~iterator_buffer() { flush(); }
+
+ auto out() -> OutputIt {
+ flush();
+ return out_;
+ }
+ auto count() const -> size_t { return Traits::count() + this->size(); }
+};
+
+template <typename T>
+class iterator_buffer<T*, T, fixed_buffer_traits> final
+ : public fixed_buffer_traits,
+ public buffer<T> {
+ private:
+ T* out_;
+ enum { buffer_size = 256 };
+ T data_[buffer_size];
+
+ protected:
+ FMT_CONSTEXPR20 void grow(size_t) override {
+ if (this->size() == this->capacity()) flush();
+ }
+
+ void flush() {
+ size_t n = this->limit(this->size());
+ if (this->data() == out_) {
+ out_ += n;
+ this->set(data_, buffer_size);
+ }
+ this->clear();
+ }
+
+ public:
+ explicit iterator_buffer(T* out, size_t n = buffer_size)
+ : fixed_buffer_traits(n), buffer<T>(out, 0, n), out_(out) {}
+ iterator_buffer(iterator_buffer&& other)
+ : fixed_buffer_traits(other),
+ buffer<T>(std::move(other)),
+ out_(other.out_) {
+ if (this->data() != out_) {
+ this->set(data_, buffer_size);
+ this->clear();
+ }
+ }
~iterator_buffer() { flush(); }
- OutputIt out() {
+ auto out() -> T* {
flush();
return out_;
}
- size_t count() const { return Traits::count() + this->size(); }
+ auto count() const -> size_t {
+ return fixed_buffer_traits::count() + this->size();
+ }
};
template <typename T> class iterator_buffer<T*, T> final : public buffer<T> {
protected:
- void grow(size_t) final FMT_OVERRIDE {}
+ FMT_CONSTEXPR20 void grow(size_t) override {}
public:
explicit iterator_buffer(T* out, size_t = 0) : buffer<T>(out, 0, ~size_t()) {}
- T* out() { return &*this->end(); }
+ auto out() -> T* { return &*this->end(); }
};
// A buffer that writes to a container with the contiguous storage.
Container& container_;
protected:
- void grow(size_t capacity) final FMT_OVERRIDE {
+ FMT_CONSTEXPR20 void grow(size_t capacity) override {
container_.resize(capacity);
this->set(&container_[0], capacity);
}
: buffer<typename Container::value_type>(c.size()), container_(c) {}
explicit iterator_buffer(std::back_insert_iterator<Container> out, size_t = 0)
: iterator_buffer(get_container(out)) {}
- std::back_insert_iterator<Container> out() {
+ auto out() -> std::back_insert_iterator<Container> {
return std::back_inserter(container_);
}
};
size_t count_ = 0;
protected:
- void grow(size_t) final FMT_OVERRIDE {
+ FMT_CONSTEXPR20 void grow(size_t) override {
if (this->size() != buffer_size) return;
count_ += this->size();
this->clear();
public:
counting_buffer() : buffer<T>(data_, 0, buffer_size) {}
- size_t count() { return count_ + this->size(); }
+ auto count() -> size_t { return count_ + this->size(); }
};
-// An output iterator that appends to the buffer.
-// It is used to reduce symbol sizes for the common case.
template <typename T>
-class buffer_appender : public std::back_insert_iterator<buffer<T>> {
- using base = std::back_insert_iterator<buffer<T>>;
-
- public:
- explicit buffer_appender(buffer<T>& buf) : base(buf) {}
- buffer_appender(base it) : base(it) {}
+using buffer_appender = conditional_t<std::is_same<T, char>::value, appender,
+ std::back_insert_iterator<buffer<T>>>;
- buffer_appender& operator++() {
- base::operator++();
- return *this;
- }
-
- buffer_appender operator++(int) {
- buffer_appender tmp = *this;
- ++*this;
- return tmp;
- }
-};
-
-// Maps an output iterator into a buffer.
+// Maps an output iterator to a buffer.
template <typename T, typename OutputIt>
-iterator_buffer<OutputIt, T> get_buffer(OutputIt);
-template <typename T> buffer<T>& get_buffer(buffer_appender<T>);
-
-template <typename OutputIt> OutputIt get_buffer_init(OutputIt out) {
- return out;
-}
-template <typename T> buffer<T>& get_buffer_init(buffer_appender<T> out) {
- return get_container(out);
+auto get_buffer(OutputIt out) -> iterator_buffer<OutputIt, T> {
+ return iterator_buffer<OutputIt, T>(out);
}
template <typename Buffer>
auto get_iterator(Buffer& buf) -> decltype(buf.out()) {
return buf.out();
}
-template <typename T> buffer_appender<T> get_iterator(buffer<T>& buf) {
+template <typename T> auto get_iterator(buffer<T>& buf) -> buffer_appender<T> {
return buffer_appender<T>(buf);
}
};
// Specifies if T has an enabled fallback_formatter specialization.
-template <typename T, typename Context>
+template <typename T, typename Char>
using has_fallback_formatter =
- std::is_constructible<fallback_formatter<T, typename Context::char_type>>;
+ std::is_constructible<fallback_formatter<T, Char>>;
struct view {};
template <typename... U>
arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {}
arg_data(const arg_data& other) = delete;
- const T* args() const { return args_ + 1; }
- named_arg_info<Char>* named_args() { return named_args_; }
+ auto args() const -> const T* { return args_ + 1; }
+ auto named_args() -> named_arg_info<Char>* { return named_args_; }
};
template <typename T, typename Char, size_t NUM_ARGS>
T args_[NUM_ARGS != 0 ? NUM_ARGS : +1];
template <typename... U>
- FMT_INLINE arg_data(const U&... init) : args_{init...} {}
- FMT_INLINE const T* args() const { return args_; }
- FMT_INLINE std::nullptr_t named_args() { return nullptr; }
+ FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {}
+ FMT_CONSTEXPR FMT_INLINE auto args() const -> const T* { return args_; }
+ FMT_CONSTEXPR FMT_INLINE auto named_args() -> std::nullptr_t {
+ return nullptr;
+ }
};
template <typename Char>
inline void init_named_args(named_arg_info<Char>*, int, int) {}
-template <typename Char, typename T, typename... Tail>
+template <typename T> struct is_named_arg : std::false_type {};
+template <typename T> struct is_statically_named_arg : std::false_type {};
+
+template <typename T, typename Char>
+struct is_named_arg<named_arg<Char, T>> : std::true_type {};
+
+template <typename Char, typename T, typename... Tail,
+ FMT_ENABLE_IF(!is_named_arg<T>::value)>
void init_named_args(named_arg_info<Char>* named_args, int arg_count,
int named_arg_count, const T&, const Tail&... args) {
init_named_args(named_args, arg_count + 1, named_arg_count, args...);
}
-template <typename Char, typename T, typename... Tail>
+template <typename Char, typename T, typename... Tail,
+ FMT_ENABLE_IF(is_named_arg<T>::value)>
void init_named_args(named_arg_info<Char>* named_args, int arg_count,
- int named_arg_count, const named_arg<Char, T>& arg,
- const Tail&... args) {
+ int named_arg_count, const T& arg, const Tail&... args) {
named_args[named_arg_count++] = {arg.name, arg_count};
init_named_args(named_args, arg_count + 1, named_arg_count, args...);
}
template <typename... Args>
-FMT_INLINE void init_named_args(std::nullptr_t, int, int, const Args&...) {}
-
-template <typename T> struct is_named_arg : std::false_type {};
-
-template <typename T, typename Char>
-struct is_named_arg<named_arg<Char, T>> : std::true_type {};
+FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int,
+ const Args&...) {}
-template <bool B = false> constexpr size_t count() { return B ? 1 : 0; }
-template <bool B1, bool B2, bool... Tail> constexpr size_t count() {
+template <bool B = false> constexpr auto count() -> size_t { return B ? 1 : 0; }
+template <bool B1, bool B2, bool... Tail> constexpr auto count() -> size_t {
return (B1 ? 1 : 0) + count<B2, Tail...>();
}
-template <typename... Args> constexpr size_t count_named_args() {
+template <typename... Args> constexpr auto count_named_args() -> size_t {
return count<is_named_arg<Args>::value...>();
}
+template <typename... Args>
+constexpr auto count_statically_named_args() -> size_t {
+ return count<is_statically_named_arg<Args>::value...>();
+}
+
enum class type {
none_type,
// Integer types should go first,
return t > type::none_type && t <= type::last_numeric_type;
}
+struct unformattable {};
+struct unformattable_char : unformattable {};
+struct unformattable_const : unformattable {};
+struct unformattable_pointer : unformattable {};
+
template <typename Char> struct string_value {
const Char* data;
size_t size;
template <typename Context> struct custom_value {
using parse_context = typename Context::parse_context_type;
- const void* value;
- void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx);
+ void* value;
+ void (*format)(void* arg, parse_context& parse_ctx, Context& ctx);
};
// A formatting argument value.
using char_type = typename Context::char_type;
union {
+ monostate no_value;
int int_value;
unsigned uint_value;
long long long_long_value;
named_arg_value<char_type> named_args;
};
- constexpr FMT_INLINE value(int val = 0) : int_value(val) {}
+ constexpr FMT_INLINE value() : no_value() {}
+ constexpr FMT_INLINE value(int val) : int_value(val) {}
constexpr FMT_INLINE value(unsigned val) : uint_value(val) {}
- FMT_INLINE value(long long val) : long_long_value(val) {}
- FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {}
+ constexpr FMT_INLINE value(long long val) : long_long_value(val) {}
+ constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {}
FMT_INLINE value(int128_t val) : int128_value(val) {}
FMT_INLINE value(uint128_t val) : uint128_value(val) {}
- FMT_INLINE value(float val) : float_value(val) {}
- FMT_INLINE value(double val) : double_value(val) {}
+ constexpr FMT_INLINE value(float val) : float_value(val) {}
+ constexpr FMT_INLINE value(double val) : double_value(val) {}
FMT_INLINE value(long double val) : long_double_value(val) {}
- FMT_INLINE value(bool val) : bool_value(val) {}
- FMT_INLINE value(char_type val) : char_value(val) {}
- FMT_INLINE value(const char_type* val) { string.data = val; }
- FMT_INLINE value(basic_string_view<char_type> val) {
+ constexpr FMT_INLINE value(bool val) : bool_value(val) {}
+ constexpr FMT_INLINE value(char_type val) : char_value(val) {}
+ FMT_CONSTEXPR FMT_INLINE value(const char_type* val) {
+ string.data = val;
+ if (is_constant_evaluated()) string.size = {};
+ }
+ FMT_CONSTEXPR FMT_INLINE value(basic_string_view<char_type> val) {
string.data = val.data();
string.size = val.size();
}
FMT_INLINE value(const named_arg_info<char_type>* args, size_t size)
: named_args{args, size} {}
- template <typename T> FMT_INLINE value(const T& val) {
- custom.value = &val;
+ template <typename T> FMT_CONSTEXPR FMT_INLINE value(T& val) {
+ using value_type = remove_cvref_t<T>;
+ custom.value = const_cast<value_type*>(&val);
// Get the formatter type through the context to allow different contexts
// have different extension points, e.g. `formatter<T>` for `format` and
// `printf_formatter<T>` for `printf`.
custom.format = format_custom_arg<
- T, conditional_t<has_formatter<T, Context>::value,
- typename Context::template formatter_type<T>,
- fallback_formatter<T, char_type>>>;
+ value_type,
+ conditional_t<has_formatter<value_type, Context>::value,
+ typename Context::template formatter_type<value_type>,
+ fallback_formatter<value_type, char_type>>>;
}
+ value(unformattable);
+ value(unformattable_char);
+ value(unformattable_const);
+ value(unformattable_pointer);
private:
// Formats an argument of a custom type, such as a user-defined class.
template <typename T, typename Formatter>
- static void format_custom_arg(const void* arg,
+ static void format_custom_arg(void* arg,
typename Context::parse_context_type& parse_ctx,
Context& ctx) {
- Formatter f;
+ auto f = Formatter();
parse_ctx.advance_to(f.parse(parse_ctx));
- ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx));
+ using qualified_type =
+ conditional_t<has_const_formatter<T, Context>(), const T, T>;
+ ctx.advance_to(f.format(*static_cast<qualified_type*>(arg), ctx));
}
};
template <typename Context, typename T>
-FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value);
+FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg<Context>;
// To minimize the number of types we need to deal with, long is translated
// either to int or to long long depending on its size.
using long_type = conditional_t<long_short, int, long long>;
using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
-struct unformattable {};
-
// Maps formatting arguments to core types.
+// arg_mapper reports errors by returning unformattable instead of using
+// static_assert because it's used in the is_formattable trait.
template <typename Context> struct arg_mapper {
using char_type = typename Context::char_type;
- FMT_CONSTEXPR int map(signed char val) { return val; }
- FMT_CONSTEXPR unsigned map(unsigned char val) { return val; }
- FMT_CONSTEXPR int map(short val) { return val; }
- FMT_CONSTEXPR unsigned map(unsigned short val) { return val; }
- FMT_CONSTEXPR int map(int val) { return val; }
- FMT_CONSTEXPR unsigned map(unsigned val) { return val; }
- FMT_CONSTEXPR long_type map(long val) { return val; }
- FMT_CONSTEXPR ulong_type map(unsigned long val) { return val; }
- FMT_CONSTEXPR long long map(long long val) { return val; }
- FMT_CONSTEXPR unsigned long long map(unsigned long long val) { return val; }
- FMT_CONSTEXPR int128_t map(int128_t val) { return val; }
- FMT_CONSTEXPR uint128_t map(uint128_t val) { return val; }
- FMT_CONSTEXPR bool map(bool val) { return val; }
-
- template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
- FMT_CONSTEXPR char_type map(T val) {
- static_assert(
- std::is_same<T, char>::value || std::is_same<T, char_type>::value,
- "mixing character types is disallowed");
+ FMT_CONSTEXPR FMT_INLINE auto map(signed char val) -> int { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(unsigned char val) -> unsigned {
+ return val;
+ }
+ FMT_CONSTEXPR FMT_INLINE auto map(short val) -> int { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(unsigned short val) -> unsigned {
+ return val;
+ }
+ FMT_CONSTEXPR FMT_INLINE auto map(int val) -> int { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(unsigned val) -> unsigned { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(long val) -> long_type { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(unsigned long val) -> ulong_type {
+ return val;
+ }
+ FMT_CONSTEXPR FMT_INLINE auto map(long long val) -> long long { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(unsigned long long val)
+ -> unsigned long long {
+ return val;
+ }
+ FMT_CONSTEXPR FMT_INLINE auto map(int128_t val) -> int128_t { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(uint128_t val) -> uint128_t { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(bool val) -> bool { return val; }
+
+ template <typename T, FMT_ENABLE_IF(std::is_same<T, char>::value ||
+ std::is_same<T, char_type>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto map(T val) -> char_type {
return val;
}
+ template <typename T, enable_if_t<(std::is_same<T, wchar_t>::value ||
+#ifdef __cpp_char8_t
+ std::is_same<T, char8_t>::value ||
+#endif
+ std::is_same<T, char16_t>::value ||
+ std::is_same<T, char32_t>::value) &&
+ !std::is_same<T, char_type>::value,
+ int> = 0>
+ FMT_CONSTEXPR FMT_INLINE auto map(T) -> unformattable_char {
+ return {};
+ }
- FMT_CONSTEXPR float map(float val) { return val; }
- FMT_CONSTEXPR double map(double val) { return val; }
- FMT_CONSTEXPR long double map(long double val) { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(long double val) -> long double {
+ return val;
+ }
- FMT_CONSTEXPR const char_type* map(char_type* val) { return val; }
- FMT_CONSTEXPR const char_type* map(const char_type* val) { return val; }
- template <typename T, FMT_ENABLE_IF(is_string<T>::value)>
- FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) {
- static_assert(std::is_same<char_type, char_t<T>>::value,
- "mixing character types is disallowed");
+ FMT_CONSTEXPR FMT_INLINE auto map(char_type* val) -> const char_type* {
+ return val;
+ }
+ FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* {
+ return val;
+ }
+ template <typename T,
+ FMT_ENABLE_IF(is_string<T>::value && !std::is_pointer<T>::value &&
+ std::is_same<char_type, char_t<T>>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
+ -> basic_string_view<char_type> {
return to_string_view(val);
}
+ template <typename T,
+ FMT_ENABLE_IF(is_string<T>::value && !std::is_pointer<T>::value &&
+ !std::is_same<char_type, char_t<T>>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char {
+ return {};
+ }
template <typename T,
FMT_ENABLE_IF(
std::is_constructible<basic_string_view<char_type>, T>::value &&
!is_string<T>::value && !has_formatter<T, Context>::value &&
- !has_fallback_formatter<T, Context>::value)>
- FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) {
+ !has_fallback_formatter<T, char_type>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
+ -> basic_string_view<char_type> {
return basic_string_view<char_type>(val);
}
template <
std::is_constructible<std_string_view<char_type>, T>::value &&
!std::is_constructible<basic_string_view<char_type>, T>::value &&
!is_string<T>::value && !has_formatter<T, Context>::value &&
- !has_fallback_formatter<T, Context>::value)>
- FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) {
+ !has_fallback_formatter<T, char_type>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
+ -> basic_string_view<char_type> {
return std_string_view<char_type>(val);
}
- FMT_CONSTEXPR const char* map(const signed char* val) {
- static_assert(std::is_same<char_type, char>::value, "invalid string type");
- return reinterpret_cast<const char*>(val);
+
+ using cstring_result = conditional_t<std::is_same<char_type, char>::value,
+ const char*, unformattable_pointer>;
+
+ FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val)
+ -> cstring_result {
+ return map(reinterpret_cast<const char*>(val));
}
- FMT_CONSTEXPR const char* map(const unsigned char* val) {
- static_assert(std::is_same<char_type, char>::value, "invalid string type");
- return reinterpret_cast<const char*>(val);
+ FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val)
+ -> cstring_result {
+ return map(reinterpret_cast<const char*>(val));
}
- FMT_CONSTEXPR const char* map(signed char* val) {
- const auto* const_val = val;
- return map(const_val);
+ FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(signed char* val)
+ -> cstring_result {
+ return map(reinterpret_cast<const char*>(val));
}
- FMT_CONSTEXPR const char* map(unsigned char* val) {
- const auto* const_val = val;
- return map(const_val);
+ FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val)
+ -> cstring_result {
+ return map(reinterpret_cast<const char*>(val));
}
- FMT_CONSTEXPR const void* map(void* val) { return val; }
- FMT_CONSTEXPR const void* map(const void* val) { return val; }
- FMT_CONSTEXPR const void* map(std::nullptr_t val) { return val; }
- template <typename T> FMT_CONSTEXPR int map(const T*) {
- // Formatting of arbitrary pointers is disallowed. If you want to output
- // a pointer cast it to "void *" or "const void *". In particular, this
- // forbids formatting of "[const] volatile char *" which is printed as bool
- // by iostreams.
- static_assert(!sizeof(T), "formatting of non-void pointers is disallowed");
- return 0;
+ FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; }
+ FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* {
+ return val;
+ }
+ FMT_CONSTEXPR FMT_INLINE auto map(std::nullptr_t val) -> const void* {
+ return val;
+ }
+
+ // We use SFINAE instead of a const T* parameter to avoid conflicting with
+ // the C array overload.
+ template <
+ typename T,
+ FMT_ENABLE_IF(
+ std::is_member_pointer<T>::value ||
+ std::is_function<typename std::remove_pointer<T>::type>::value ||
+ (std::is_convertible<const T&, const void*>::value &&
+ !std::is_convertible<const T&, const char_type*>::value))>
+ FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer {
+ return {};
+ }
+
+ template <typename T, std::size_t N,
+ FMT_ENABLE_IF(!std::is_same<T, wchar_t>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] {
+ return values;
}
template <typename T,
- FMT_ENABLE_IF(std::is_enum<T>::value &&
- !has_formatter<T, Context>::value &&
- !has_fallback_formatter<T, Context>::value)>
- FMT_CONSTEXPR auto map(const T& val)
+ FMT_ENABLE_IF(
+ std::is_enum<T>::value&& std::is_convertible<T, int>::value &&
+ !has_formatter<T, Context>::value &&
+ !has_fallback_formatter<T, char_type>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
-> decltype(std::declval<arg_mapper>().map(
static_cast<typename std::underlying_type<T>::type>(val))) {
return map(static_cast<typename std::underlying_type<T>::type>(val));
}
- template <typename T,
- FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value &&
- (has_formatter<T, Context>::value ||
- has_fallback_formatter<T, Context>::value))>
- FMT_CONSTEXPR const T& map(const T& val) {
+
+ FMT_CONSTEXPR FMT_INLINE auto map(detail::byte val) -> unsigned {
+ return map(static_cast<unsigned char>(val));
+ }
+
+ template <typename T, typename U = remove_cvref_t<T>>
+ struct formattable
+ : bool_constant<has_const_formatter<U, Context>() ||
+ !std::is_const<remove_reference_t<T>>::value ||
+ has_fallback_formatter<U, char_type>::value> {};
+
+#if FMT_MSC_VER != 0 && FMT_MSC_VER < 1910
+ // Workaround a bug in MSVC.
+ template <typename T> FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& {
+ return val;
+ }
+#else
+ template <typename T, FMT_ENABLE_IF(formattable<T>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& {
return val;
}
+ template <typename T, FMT_ENABLE_IF(!formattable<T>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const {
+ return {};
+ }
+#endif
+
+ template <typename T, typename U = remove_cvref_t<T>,
+ FMT_ENABLE_IF(!is_string<U>::value && !is_char<U>::value &&
+ !std::is_array<U>::value &&
+ (has_formatter<U, Context>::value ||
+ has_fallback_formatter<U, char_type>::value))>
+ FMT_CONSTEXPR FMT_INLINE auto map(T&& val)
+ -> decltype(this->do_map(std::forward<T>(val))) {
+ return do_map(std::forward<T>(val));
+ }
- template <typename T>
- FMT_CONSTEXPR auto map(const named_arg<char_type, T>& val)
- -> decltype(std::declval<arg_mapper>().map(val.value)) {
- return map(val.value);
+ template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg)
+ -> decltype(std::declval<arg_mapper>().map(named_arg.value)) {
+ return map(named_arg.value);
}
- unformattable map(...) { return {}; }
+ auto map(...) -> unformattable { return {}; }
};
// A type constant after applying arg_mapper<Context>.
enum { max_packed_args = 62 / packed_arg_bits };
enum : unsigned long long { is_unpacked_bit = 1ULL << 63 };
enum : unsigned long long { has_named_args_bit = 1ULL << 62 };
-} // namespace detail
+
+FMT_END_DETAIL_NAMESPACE
+
+// An output iterator that appends to a buffer.
+// It is used to reduce symbol sizes for the common case.
+class appender : public std::back_insert_iterator<detail::buffer<char>> {
+ using base = std::back_insert_iterator<detail::buffer<char>>;
+
+ template <typename T>
+ friend auto get_buffer(appender out) -> detail::buffer<char>& {
+ return detail::get_container(out);
+ }
+
+ public:
+ using std::back_insert_iterator<detail::buffer<char>>::back_insert_iterator;
+ appender(base it) FMT_NOEXCEPT : base(it) {}
+ using _Unchecked_type = appender; // Mark iterator as checked.
+
+ auto operator++() FMT_NOEXCEPT -> appender& { return *this; }
+
+ auto operator++(int) FMT_NOEXCEPT -> appender { return *this; }
+};
// A formatting argument. It is a trivially copyable/constructible type to
// allow storage in basic_memory_buffer.
detail::type type_;
template <typename ContextType, typename T>
- friend FMT_CONSTEXPR basic_format_arg<ContextType> detail::make_arg(
- const T& value);
+ friend FMT_CONSTEXPR auto detail::make_arg(const T& value)
+ -> basic_format_arg<ContextType>;
template <typename Visitor, typename Ctx>
friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis,
return type_ != detail::type::none_type;
}
- detail::type type() const { return type_; }
+ auto type() const -> detail::type { return type_; }
- bool is_integral() const { return detail::is_integral_type(type_); }
- bool is_arithmetic() const { return detail::is_arithmetic_type(type_); }
+ auto is_integral() const -> bool { return detail::is_integral_type(type_); }
+ auto is_arithmetic() const -> bool {
+ return detail::is_arithmetic_type(type_);
+ }
};
/**
\endrst
*/
template <typename Visitor, typename Context>
-FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg(
+FMT_CONSTEXPR FMT_INLINE auto visit_format_arg(
Visitor&& vis, const basic_format_arg<Context>& arg) -> decltype(vis(0)) {
- using char_type = typename Context::char_type;
switch (arg.type_) {
case detail::type::none_type:
break;
return vis(arg.value_.long_long_value);
case detail::type::ulong_long_type:
return vis(arg.value_.ulong_long_value);
-#if FMT_USE_INT128
case detail::type::int128_type:
- return vis(arg.value_.int128_value);
+ return vis(detail::convert_for_visit(arg.value_.int128_value));
case detail::type::uint128_type:
- return vis(arg.value_.uint128_value);
-#else
- case detail::type::int128_type:
- case detail::type::uint128_type:
- break;
-#endif
+ return vis(detail::convert_for_visit(arg.value_.uint128_value));
case detail::type::bool_type:
return vis(arg.value_.bool_value);
case detail::type::char_type:
case detail::type::cstring_type:
return vis(arg.value_.string.data);
case detail::type::string_type:
- return vis(basic_string_view<char_type>(arg.value_.string.data,
- arg.value_.string.size));
+ using sv = basic_string_view<typename Context::char_type>;
+ return vis(sv(arg.value_.string.data, arg.value_.string.size));
case detail::type::pointer_type:
return vis(arg.value_.pointer);
case detail::type::custom_type:
return vis(monostate());
}
-template <typename T> struct formattable : std::false_type {};
+FMT_BEGIN_DETAIL_NAMESPACE
-namespace detail {
+template <typename Char, typename InputIt>
+auto copy_str(InputIt begin, InputIt end, appender out) -> appender {
+ get_container(out).append(begin, end);
+ return out;
+}
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500
// A workaround for gcc 4.8 to make void_t work in a SFINAE context.
template <typename... Ts> struct void_t_impl { using type = void; };
template <typename... Ts>
using void_t = typename detail::void_t_impl<Ts...>::type;
+#else
+template <typename...> using void_t = void;
+#endif
template <typename It, typename T, typename Enable = void>
struct is_output_iterator : std::false_type {};
template <typename Container>
struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>>
: is_contiguous<Container> {};
-template <typename Char>
-struct is_contiguous_back_insert_iterator<buffer_appender<Char>>
- : std::true_type {};
+template <>
+struct is_contiguous_back_insert_iterator<appender> : std::true_type {};
// A type-erased reference to an std::locale to avoid heavy <locale> include.
class locale_ref {
const void* locale_; // A type-erased pointer to std::locale.
public:
- locale_ref() : locale_(nullptr) {}
+ constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale> explicit locale_ref(const Locale& loc);
explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; }
- template <typename Locale> Locale get() const;
+ template <typename Locale> auto get() const -> Locale;
};
-template <typename> constexpr unsigned long long encode_types() { return 0; }
-
+template <typename> constexpr auto encode_types() -> unsigned long long {
+ return 0;
+}
+
template <typename Context, typename Arg, typename... Args>
-constexpr unsigned long long encode_types() {
+constexpr auto encode_types() -> unsigned long long {
return static_cast<unsigned>(mapped_type_constant<Arg, Context>::value) |
(encode_types<Context, Args...>() << packed_arg_bits);
}
template <typename Context, typename T>
-FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value) {
+FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg<Context> {
basic_format_arg<Context> arg;
arg.type_ = mapped_type_constant<T, Context>::value;
arg.value_ = arg_mapper<Context>().map(value);
return arg;
}
-template <typename T> int check(unformattable) {
- static_assert(
- formattable<T>(),
- "Cannot format an argument. To make type T formattable provide a "
- "formatter<T> specialization: https://fmt.dev/latest/api.html#udt");
- return 0;
-}
-template <typename T, typename U> inline const U& check(const U& val) {
- return val;
-}
-
// The type template parameter is there to avoid an ODR violation when using
// a fallback formatter in one translation unit and an implicit conversion in
// another (not recommended).
template <bool IS_PACKED, typename Context, type, typename T,
FMT_ENABLE_IF(IS_PACKED)>
-inline value<Context> make_arg(const T& val) {
- return check<T>(arg_mapper<Context>().map(val));
+FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value<Context> {
+ const auto& arg = arg_mapper<Context>().map(std::forward<T>(val));
+
+ constexpr bool formattable_char =
+ !std::is_same<decltype(arg), const unformattable_char&>::value;
+ static_assert(formattable_char, "Mixing character types is disallowed.");
+
+ constexpr bool formattable_const =
+ !std::is_same<decltype(arg), const unformattable_const&>::value;
+ static_assert(formattable_const, "Cannot format a const argument.");
+
+ // Formatting of arbitrary pointers is disallowed. If you want to output
+ // a pointer cast it to "void *" or "const void *". In particular, this
+ // forbids formatting of "[const] volatile char *" which is printed as bool
+ // by iostreams.
+ constexpr bool formattable_pointer =
+ !std::is_same<decltype(arg), const unformattable_pointer&>::value;
+ static_assert(formattable_pointer,
+ "Formatting of non-void pointers is disallowed.");
+
+ constexpr bool formattable =
+ !std::is_same<decltype(arg), const unformattable&>::value;
+ static_assert(
+ formattable,
+ "Cannot format an argument. To make type T formattable provide a "
+ "formatter<T> specialization: https://fmt.dev/latest/api.html#udt");
+ return {arg};
}
template <bool IS_PACKED, typename Context, type, typename T,
FMT_ENABLE_IF(!IS_PACKED)>
-inline basic_format_arg<Context> make_arg(const T& value) {
+inline auto make_arg(const T& value) -> basic_format_arg<Context> {
return make_arg<Context>(value);
}
-
-template <typename T> struct is_reference_wrapper : std::false_type {};
-template <typename T>
-struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
-
-template <typename T> const T& unwrap(const T& v) { return v; }
-template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
- return static_cast<const T&>(v);
-}
-
-class dynamic_arg_list {
- // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
- // templates it doesn't complain about inability to deduce single translation
- // unit for placing vtable. So storage_node_base is made a fake template.
- template <typename = void> struct node {
- virtual ~node() = default;
- std::unique_ptr<node<>> next;
- };
-
- template <typename T> struct typed_node : node<> {
- T value;
-
- template <typename Arg>
- FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
-
- template <typename Char>
- FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
- : value(arg.data(), arg.size()) {}
- };
-
- std::unique_ptr<node<>> head_;
-
- public:
- template <typename T, typename Arg> const T& push(const Arg& arg) {
- auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
- auto& value = new_node->value;
- new_node->next = std::move(head_);
- head_ = std::move(new_node);
- return value;
- }
-};
-} // namespace detail
+FMT_END_DETAIL_NAMESPACE
// Formatting context.
template <typename OutputIt, typename Char> class basic_format_context {
using parse_context_type = basic_format_parse_context<Char>;
template <typename T> using formatter_type = formatter<T, char_type>;
+ basic_format_context(basic_format_context&&) = default;
basic_format_context(const basic_format_context&) = delete;
void operator=(const basic_format_context&) = delete;
/**
Constructs a ``basic_format_context`` object. References to the arguments are
stored in the object so make sure they have appropriate lifetimes.
*/
- basic_format_context(OutputIt out,
- basic_format_args<basic_format_context> ctx_args,
- detail::locale_ref loc = detail::locale_ref())
+ constexpr basic_format_context(
+ OutputIt out, basic_format_args<basic_format_context> ctx_args,
+ detail::locale_ref loc = detail::locale_ref())
: out_(out), args_(ctx_args), loc_(loc) {}
- format_arg arg(int id) const { return args_.get(id); }
- format_arg arg(basic_string_view<char_type> name) { return args_.get(name); }
- int arg_id(basic_string_view<char_type> name) { return args_.get_id(name); }
- const basic_format_args<basic_format_context>& args() const { return args_; }
+ constexpr auto arg(int id) const -> format_arg { return args_.get(id); }
+ FMT_CONSTEXPR auto arg(basic_string_view<char_type> name) -> format_arg {
+ return args_.get(name);
+ }
+ FMT_CONSTEXPR auto arg_id(basic_string_view<char_type> name) -> int {
+ return args_.get_id(name);
+ }
+ auto args() const -> const basic_format_args<basic_format_context>& {
+ return args_;
+ }
- detail::error_handler error_handler() { return {}; }
+ FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; }
void on_error(const char* message) { error_handler().on_error(message); }
// Returns an iterator to the beginning of the output range.
- iterator out() { return out_; }
+ FMT_CONSTEXPR auto out() -> iterator { return out_; }
// Advances the begin iterator to ``it``.
void advance_to(iterator it) {
if (!detail::is_back_insert_iterator<iterator>()) out_ = it;
}
- detail::locale_ref locale() { return loc_; }
+ FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; }
};
template <typename Char>
using buffer_context =
basic_format_context<detail::buffer_appender<Char>, Char>;
using format_context = buffer_context<char>;
-using wformat_context = buffer_context<wchar_t>;
// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164.
#define FMT_BUFFER_CONTEXT(Char) \
basic_format_context<detail::buffer_appender<Char>, Char>
+template <typename T, typename Char = char>
+using is_formattable = bool_constant<
+ !std::is_base_of<detail::unformattable,
+ decltype(detail::arg_mapper<buffer_context<Char>>().map(
+ std::declval<T>()))>::value &&
+ !detail::has_fallback_formatter<T, Char>::value>;
+
/**
\rst
An array of references to arguments. It can be implicitly converted into
: 0);
public:
- format_arg_store(const Args&... args)
+ template <typename... T>
+ FMT_CONSTEXPR FMT_INLINE format_arg_store(T&&... args)
:
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
basic_format_args<Context>(*this),
#endif
data_{detail::make_arg<
is_packed, Context,
- detail::mapped_type_constant<Args, Context>::value>(args)...} {
+ detail::mapped_type_constant<remove_cvref_t<T>, Context>::value>(
+ std::forward<T>(args))...} {
detail::init_named_args(data_.named_args(), 0, 0, args...);
}
};
\endrst
*/
template <typename Context = format_context, typename... Args>
-inline format_arg_store<Context, Args...> make_format_args(
- const Args&... args) {
- return {args...};
-}
-
-/**
- \rst
- Constructs a `~fmt::format_arg_store` object that contains references
- to arguments and can be implicitly converted to `~fmt::format_args`.
- If ``format_str`` is a compile-time string then `make_args_checked` checks
- its validity at compile time.
- \endrst
- */
-template <typename... Args, typename S, typename Char = char_t<S>>
-inline auto make_args_checked(const S& format_str,
- const remove_reference_t<Args>&... args)
- -> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
- static_assert(
- detail::count<(
- std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
- std::is_reference<Args>::value)...>() == 0,
- "passing views as lvalues is disallowed");
- detail::check_format_string<Args...>(format_str);
- return {args...};
+constexpr auto make_format_args(Args&&... args)
+ -> format_arg_store<Context, remove_cvref_t<Args>...> {
+ return {std::forward<Args>(args)...};
}
/**
\rst
- Returns a named argument to be used in a formatting function. It should only
- be used in a call to a formatting function.
+ Returns a named argument to be used in a formatting function.
+ It should only be used in a call to a formatting function or
+ `dynamic_format_arg_store::push_back`.
**Example**::
\endrst
*/
template <typename Char, typename T>
-inline detail::named_arg<Char, T> arg(const Char* name, const T& arg) {
+inline auto arg(const Char* name, const T& arg) -> detail::named_arg<Char, T> {
static_assert(!detail::is_named_arg<T>(), "nested named arguments");
return {name, arg};
}
-/**
- \rst
- A dynamic version of `fmt::format_arg_store`.
- It's equipped with a storage to potentially temporary objects which lifetimes
- could be shorter than the format arguments object.
-
- It can be implicitly converted into `~fmt::basic_format_args` for passing
- into type-erased formatting functions such as `~fmt::vformat`.
- \endrst
- */
-template <typename Context>
-class dynamic_format_arg_store
-#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
- // Workaround a GCC template argument substitution bug.
- : public basic_format_args<Context>
-#endif
-{
- private:
- using char_type = typename Context::char_type;
-
- template <typename T> struct need_copy {
- static constexpr detail::type mapped_type =
- detail::mapped_type_constant<T, Context>::value;
-
- enum {
- value = !(detail::is_reference_wrapper<T>::value ||
- std::is_same<T, basic_string_view<char_type>>::value ||
- std::is_same<T, detail::std_string_view<char_type>>::value ||
- (mapped_type != detail::type::cstring_type &&
- mapped_type != detail::type::string_type &&
- mapped_type != detail::type::custom_type))
- };
- };
-
- template <typename T>
- using stored_type = conditional_t<detail::is_string<T>::value,
- std::basic_string<char_type>, T>;
-
- // Storage of basic_format_arg must be contiguous.
- std::vector<basic_format_arg<Context>> data_;
- std::vector<detail::named_arg_info<char_type>> named_info_;
-
- // Storage of arguments not fitting into basic_format_arg must grow
- // without relocation because items in data_ refer to it.
- detail::dynamic_arg_list dynamic_args_;
-
- friend class basic_format_args<Context>;
-
- unsigned long long get_types() const {
- return detail::is_unpacked_bit | data_.size() |
- (named_info_.empty()
- ? 0ULL
- : static_cast<unsigned long long>(detail::has_named_args_bit));
- }
-
- const basic_format_arg<Context>* data() const {
- return named_info_.empty() ? data_.data() : data_.data() + 1;
- }
-
- template <typename T> void emplace_arg(const T& arg) {
- data_.emplace_back(detail::make_arg<Context>(arg));
- }
-
- template <typename T>
- void emplace_arg(const detail::named_arg<char_type, T>& arg) {
- if (named_info_.empty()) {
- constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
- data_.insert(data_.begin(), {zero_ptr, 0});
- }
- data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
- auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
- data->pop_back();
- };
- std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
- guard{&data_, pop_one};
- named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
- data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
- guard.release();
- }
-
- public:
- /**
- \rst
- Adds an argument into the dynamic store for later passing to a formatting
- function.
-
- Note that custom types and string types (but not string views) are copied
- into the store dynamically allocating memory if necessary.
-
- **Example**::
-
- fmt::dynamic_format_arg_store<fmt::format_context> store;
- store.push_back(42);
- store.push_back("abc");
- store.push_back(1.5f);
- std::string result = fmt::vformat("{} and {} and {}", store);
- \endrst
- */
- template <typename T> void push_back(const T& arg) {
- if (detail::const_check(need_copy<T>::value))
- emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
- else
- emplace_arg(detail::unwrap(arg));
- }
-
- /**
- \rst
- Adds a reference to the argument into the dynamic store for later passing to
- a formatting function. Supports named arguments wrapped in
- ``std::reference_wrapper`` via ``std::ref()``/``std::cref()``.
-
- **Example**::
-
- fmt::dynamic_format_arg_store<fmt::format_context> store;
- char str[] = "1234567890";
- store.push_back(std::cref(str));
- int a1_val{42};
- auto a1 = fmt::arg("a1_", a1_val);
- store.push_back(std::cref(a1));
-
- // Changing str affects the output but only for string and custom types.
- str[0] = 'X';
-
- std::string result = fmt::vformat("{} and {a1_}");
- assert(result == "X234567890 and 42");
- \endrst
- */
- template <typename T> void push_back(std::reference_wrapper<T> arg) {
- static_assert(
- detail::is_named_arg<typename std::remove_cv<T>::type>::value ||
- need_copy<T>::value,
- "objects of built-in types and string views are always copied");
- emplace_arg(arg.get());
- }
-
- /**
- Adds named argument into the dynamic store for later passing to a formatting
- function. ``std::reference_wrapper`` is supported to avoid copying of the
- argument.
- */
- template <typename T>
- void push_back(const detail::named_arg<char_type, T>& arg) {
- const char_type* arg_name =
- dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
- if (detail::const_check(need_copy<T>::value)) {
- emplace_arg(
- fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
- } else {
- emplace_arg(fmt::arg(arg_name, arg.value));
- }
- }
-
- /** Erase all elements from the store */
- void clear() {
- data_.clear();
- named_info_.clear();
- dynamic_args_ = detail::dynamic_arg_list();
- }
-
- /**
- \rst
- Reserves space to store at least *new_cap* arguments including
- *new_cap_named* named arguments.
- \endrst
- */
- void reserve(size_t new_cap, size_t new_cap_named) {
- FMT_ASSERT(new_cap >= new_cap_named,
- "Set of arguments includes set of named arguments");
- data_.reserve(new_cap);
- named_info_.reserve(new_cap_named);
- }
-};
-
/**
\rst
A view of a collection of formatting arguments. To avoid lifetime issues it
const format_arg* args_;
};
- bool is_packed() const { return (desc_ & detail::is_unpacked_bit) == 0; }
- bool has_named_args() const {
+ constexpr auto is_packed() const -> bool {
+ return (desc_ & detail::is_unpacked_bit) == 0;
+ }
+ auto has_named_args() const -> bool {
return (desc_ & detail::has_named_args_bit) != 0;
}
- detail::type type(int index) const {
+ FMT_CONSTEXPR auto type(int index) const -> detail::type {
int shift = index * detail::packed_arg_bits;
unsigned int mask = (1 << detail::packed_arg_bits) - 1;
return static_cast<detail::type>((desc_ >> shift) & mask);
}
- basic_format_args(unsigned long long desc,
- const detail::value<Context>* values)
+ constexpr FMT_INLINE basic_format_args(unsigned long long desc,
+ const detail::value<Context>* values)
: desc_(desc), values_(values) {}
- basic_format_args(unsigned long long desc, const format_arg* args)
+ constexpr basic_format_args(unsigned long long desc, const format_arg* args)
: desc_(desc), args_(args) {}
public:
- basic_format_args() : desc_(0) {}
+ constexpr basic_format_args() : desc_(0), args_(nullptr) {}
/**
\rst
\endrst
*/
template <typename... Args>
- FMT_INLINE basic_format_args(const format_arg_store<Context, Args...>& store)
- : basic_format_args(store.desc, store.data_.args()) {}
+ constexpr FMT_INLINE basic_format_args(
+ const format_arg_store<Context, Args...>& store)
+ : basic_format_args(format_arg_store<Context, Args...>::desc,
+ store.data_.args()) {}
/**
\rst
`~fmt::dynamic_format_arg_store`.
\endrst
*/
- FMT_INLINE basic_format_args(const dynamic_format_arg_store<Context>& store)
+ constexpr FMT_INLINE basic_format_args(
+ const dynamic_format_arg_store<Context>& store)
: basic_format_args(store.get_types(), store.data()) {}
/**
Constructs a `basic_format_args` object from a dynamic set of arguments.
\endrst
*/
- basic_format_args(const format_arg* args, int count)
+ constexpr basic_format_args(const format_arg* args, int count)
: basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count),
args) {}
/** Returns the argument with the specified id. */
- format_arg get(int id) const {
+ FMT_CONSTEXPR auto get(int id) const -> format_arg {
format_arg arg;
if (!is_packed()) {
if (id < max_size()) arg = args_[id];
return arg;
}
- template <typename Char> format_arg get(basic_string_view<Char> name) const {
+ template <typename Char>
+ auto get(basic_string_view<Char> name) const -> format_arg {
int id = get_id(name);
return id >= 0 ? get(id) : format_arg();
}
- template <typename Char> int get_id(basic_string_view<Char> name) const {
+ template <typename Char>
+ auto get_id(basic_string_view<Char> name) const -> int {
if (!has_named_args()) return -1;
const auto& named_args =
(is_packed() ? values_[-1] : args_[-1].value_).named_args;
return -1;
}
- int max_size() const {
+ auto max_size() const -> int {
unsigned long long max_packed = detail::max_packed_args;
return static_cast<int>(is_packed() ? max_packed
: desc_ & ~detail::is_unpacked_bit);
}
};
-#ifdef FMT_ARM_ABI_COMPATIBILITY
/** An alias to ``basic_format_args<format_context>``. */
-// Separate types would result in shorter symbols but break ABI compatibility
+// A separate type would result in shorter symbols but break ABI compatibility
// between clang and gcc on ARM (#1919).
using format_args = basic_format_args<format_context>;
-using wformat_args = basic_format_args<wformat_context>;
-#else
-// DEPRECATED! These are kept for ABI compatibility.
-// It is a separate type rather than an alias to make symbols readable.
-struct format_args : basic_format_args<format_context> {
- template <typename... Args>
- FMT_INLINE format_args(const Args&... args) : basic_format_args(args...) {}
+
+// We cannot use enum classes as bit fields because of a gcc bug
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414.
+namespace align {
+enum type { none, left, right, center, numeric };
+}
+using align_t = align::type;
+namespace sign {
+enum type { none, minus, plus, space };
+}
+using sign_t = sign::type;
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+// Workaround an array initialization issue in gcc 4.8.
+template <typename Char> struct fill_t {
+ private:
+ enum { max_size = 4 };
+ Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)};
+ unsigned char size_ = 1;
+
+ public:
+ FMT_CONSTEXPR void operator=(basic_string_view<Char> s) {
+ auto size = s.size();
+ if (size > max_size) return throw_format_error("invalid fill");
+ for (size_t i = 0; i < size; ++i) data_[i] = s[i];
+ size_ = static_cast<unsigned char>(size);
+ }
+
+ constexpr auto size() const -> size_t { return size_; }
+ constexpr auto data() const -> const Char* { return data_; }
+
+ FMT_CONSTEXPR auto operator[](size_t index) -> Char& { return data_[index]; }
+ FMT_CONSTEXPR auto operator[](size_t index) const -> const Char& {
+ return data_[index];
+ }
+};
+FMT_END_DETAIL_NAMESPACE
+
+enum class presentation_type : unsigned char {
+ none,
+ // Integer types should go first,
+ dec, // 'd'
+ oct, // 'o'
+ hex_lower, // 'x'
+ hex_upper, // 'X'
+ bin_lower, // 'b'
+ bin_upper, // 'B'
+ hexfloat_lower, // 'a'
+ hexfloat_upper, // 'A'
+ exp_lower, // 'e'
+ exp_upper, // 'E'
+ fixed_lower, // 'f'
+ fixed_upper, // 'F'
+ general_lower, // 'g'
+ general_upper, // 'G'
+ chr, // 'c'
+ string, // 's'
+ pointer // 'p'
+};
+
+// Format specifiers for built-in and string types.
+template <typename Char> struct basic_format_specs {
+ int width;
+ int precision;
+ presentation_type type;
+ align_t align : 4;
+ sign_t sign : 3;
+ bool alt : 1; // Alternate form ('#').
+ bool localized : 1;
+ detail::fill_t<Char> fill;
+
+ constexpr basic_format_specs()
+ : width(0),
+ precision(-1),
+ type(presentation_type::none),
+ align(align::none),
+ sign(sign::none),
+ alt(false),
+ localized(false) {}
+};
+
+using format_specs = basic_format_specs<char>;
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+enum class arg_id_kind { none, index, name };
+
+// An argument reference.
+template <typename Char> struct arg_ref {
+ FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}
+
+ FMT_CONSTEXPR explicit arg_ref(int index)
+ : kind(arg_id_kind::index), val(index) {}
+ FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name)
+ : kind(arg_id_kind::name), val(name) {}
+
+ FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& {
+ kind = arg_id_kind::index;
+ val.index = idx;
+ return *this;
+ }
+
+ arg_id_kind kind;
+ union value {
+ FMT_CONSTEXPR value(int id = 0) : index{id} {}
+ FMT_CONSTEXPR value(basic_string_view<Char> n) : name(n) {}
+
+ int index;
+ basic_string_view<Char> name;
+ } val;
+};
+
+// Format specifiers with width and precision resolved at formatting rather
+// than parsing time to allow re-using the same parsed specifiers with
+// different sets of arguments (precompilation of format strings).
+template <typename Char>
+struct dynamic_format_specs : basic_format_specs<Char> {
+ arg_ref<Char> width_ref;
+ arg_ref<Char> precision_ref;
+};
+
+struct auto_id {};
+
+// A format specifier handler that sets fields in basic_format_specs.
+template <typename Char> class specs_setter {
+ protected:
+ basic_format_specs<Char>& specs_;
+
+ public:
+ explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char>& specs)
+ : specs_(specs) {}
+
+ FMT_CONSTEXPR specs_setter(const specs_setter& other)
+ : specs_(other.specs_) {}
+
+ FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; }
+ FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
+ specs_.fill = fill;
+ }
+ FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; }
+ FMT_CONSTEXPR void on_hash() { specs_.alt = true; }
+ FMT_CONSTEXPR void on_localized() { specs_.localized = true; }
+
+ FMT_CONSTEXPR void on_zero() {
+ if (specs_.align == align::none) specs_.align = align::numeric;
+ specs_.fill[0] = Char('0');
+ }
+
+ FMT_CONSTEXPR void on_width(int width) { specs_.width = width; }
+ FMT_CONSTEXPR void on_precision(int precision) {
+ specs_.precision = precision;
+ }
+ FMT_CONSTEXPR void end_precision() {}
+
+ FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; }
+};
+
+// Format spec handler that saves references to arguments representing dynamic
+// width and precision to be resolved at formatting time.
+template <typename ParseContext>
+class dynamic_specs_handler
+ : public specs_setter<typename ParseContext::char_type> {
+ public:
+ using char_type = typename ParseContext::char_type;
+
+ FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs<char_type>& specs,
+ ParseContext& ctx)
+ : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {}
+
+ FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other)
+ : specs_setter<char_type>(other),
+ specs_(other.specs_),
+ context_(other.context_) {}
+
+ template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
+ specs_.width_ref = make_arg_ref(arg_id);
+ }
+
+ template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
+ specs_.precision_ref = make_arg_ref(arg_id);
+ }
+
+ FMT_CONSTEXPR void on_error(const char* message) {
+ context_.on_error(message);
+ }
+
+ private:
+ dynamic_format_specs<char_type>& specs_;
+ ParseContext& context_;
+
+ using arg_ref_type = arg_ref<char_type>;
+
+ FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type {
+ context_.check_arg_id(arg_id);
+ return arg_ref_type(arg_id);
+ }
+
+ FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type {
+ return arg_ref_type(context_.next_arg_id());
+ }
+
+ FMT_CONSTEXPR auto make_arg_ref(basic_string_view<char_type> arg_id)
+ -> arg_ref_type {
+ context_.check_arg_id(arg_id);
+ basic_string_view<char_type> format_str(
+ context_.begin(), to_unsigned(context_.end() - context_.begin()));
+ return arg_ref_type(arg_id);
+ }
+};
+
+template <typename Char> constexpr bool is_ascii_letter(Char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+// Converts a character to ASCII. Returns a number > 127 on conversion failure.
+template <typename Char, FMT_ENABLE_IF(std::is_integral<Char>::value)>
+constexpr auto to_ascii(Char value) -> Char {
+ return value;
+}
+template <typename Char, FMT_ENABLE_IF(std::is_enum<Char>::value)>
+constexpr auto to_ascii(Char value) ->
+ typename std::underlying_type<Char>::type {
+ return value;
+}
+
+template <typename Char>
+FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int {
+ if (const_check(sizeof(Char) != 1)) return 1;
+ auto lengths =
+ "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4";
+ int len = lengths[static_cast<unsigned char>(*begin) >> 3];
+
+ // Compute the pointer to the next character early so that the next
+ // iteration can start working on the next character. Neither Clang
+ // nor GCC figure out this reordering on their own.
+ return len + !len;
+}
+
+// Return the result via the out param to workaround gcc bug 77539.
+template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
+FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool {
+ for (out = first; out != last; ++out) {
+ if (*out == value) return true;
+ }
+ return false;
+}
+
+template <>
+inline auto find<false, char>(const char* first, const char* last, char value,
+ const char*& out) -> bool {
+ out = static_cast<const char*>(
+ std::memchr(first, value, to_unsigned(last - first)));
+ return out != nullptr;
+}
+
+// Parses the range [begin, end) as an unsigned integer. This function assumes
+// that the range is non-empty and the first character is a digit.
+template <typename Char>
+FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end,
+ int error_value) noexcept -> int {
+ FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', "");
+ unsigned value = 0, prev = 0;
+ auto p = begin;
+ do {
+ prev = value;
+ value = value * 10 + unsigned(*p - '0');
+ ++p;
+ } while (p != end && '0' <= *p && *p <= '9');
+ auto num_digits = p - begin;
+ begin = p;
+ if (num_digits <= std::numeric_limits<int>::digits10)
+ return static_cast<int>(value);
+ // Check for overflow.
+ const unsigned max = to_unsigned((std::numeric_limits<int>::max)());
+ return num_digits == std::numeric_limits<int>::digits10 + 1 &&
+ prev * 10ull + unsigned(p[-1] - '0') <= max
+ ? static_cast<int>(value)
+ : error_value;
+}
+
+// Parses fill and alignment.
+template <typename Char, typename Handler>
+FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end,
+ Handler&& handler) -> const Char* {
+ FMT_ASSERT(begin != end, "");
+ auto align = align::none;
+ auto p = begin + code_point_length(begin);
+ if (p >= end) p = begin;
+ for (;;) {
+ switch (to_ascii(*p)) {
+ case '<':
+ align = align::left;
+ break;
+ case '>':
+ align = align::right;
+ break;
+ case '^':
+ align = align::center;
+ break;
+ default:
+ break;
+ }
+ if (align != align::none) {
+ if (p != begin) {
+ auto c = *begin;
+ if (c == '{')
+ return handler.on_error("invalid fill character '{'"), begin;
+ handler.on_fill(basic_string_view<Char>(begin, to_unsigned(p - begin)));
+ begin = p + 1;
+ } else
+ ++begin;
+ handler.on_align(align);
+ break;
+ } else if (p == begin) {
+ break;
+ }
+ p = begin;
+ }
+ return begin;
+}
+
+template <typename Char> FMT_CONSTEXPR bool is_name_start(Char c) {
+ return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c;
+}
+
+template <typename Char, typename IDHandler>
+FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end,
+ IDHandler&& handler) -> const Char* {
+ FMT_ASSERT(begin != end, "");
+ Char c = *begin;
+ if (c >= '0' && c <= '9') {
+ int index = 0;
+ if (c != '0')
+ index =
+ parse_nonnegative_int(begin, end, (std::numeric_limits<int>::max)());
+ else
+ ++begin;
+ if (begin == end || (*begin != '}' && *begin != ':'))
+ handler.on_error("invalid format string");
+ else
+ handler(index);
+ return begin;
+ }
+ if (!is_name_start(c)) {
+ handler.on_error("invalid format string");
+ return begin;
+ }
+ auto it = begin;
+ do {
+ ++it;
+ } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9')));
+ handler(basic_string_view<Char>(begin, to_unsigned(it - begin)));
+ return it;
+}
+
+template <typename Char, typename IDHandler>
+FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end,
+ IDHandler&& handler) -> const Char* {
+ Char c = *begin;
+ if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler);
+ handler();
+ return begin;
+}
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end,
+ Handler&& handler) -> const Char* {
+ using detail::auto_id;
+ struct width_adapter {
+ Handler& handler;
+
+ FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); }
+ FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); }
+ FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
+ handler.on_dynamic_width(id);
+ }
+ FMT_CONSTEXPR void on_error(const char* message) {
+ if (message) handler.on_error(message);
+ }
+ };
+
+ FMT_ASSERT(begin != end, "");
+ if ('0' <= *begin && *begin <= '9') {
+ int width = parse_nonnegative_int(begin, end, -1);
+ if (width != -1)
+ handler.on_width(width);
+ else
+ handler.on_error("number is too big");
+ } else if (*begin == '{') {
+ ++begin;
+ if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler});
+ if (begin == end || *begin != '}')
+ return handler.on_error("invalid format string"), begin;
+ ++begin;
+ }
+ return begin;
+}
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end,
+ Handler&& handler) -> const Char* {
+ using detail::auto_id;
+ struct precision_adapter {
+ Handler& handler;
+
+ FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); }
+ FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); }
+ FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
+ handler.on_dynamic_precision(id);
+ }
+ FMT_CONSTEXPR void on_error(const char* message) {
+ if (message) handler.on_error(message);
+ }
+ };
+
+ ++begin;
+ auto c = begin != end ? *begin : Char();
+ if ('0' <= c && c <= '9') {
+ auto precision = parse_nonnegative_int(begin, end, -1);
+ if (precision != -1)
+ handler.on_precision(precision);
+ else
+ handler.on_error("number is too big");
+ } else if (c == '{') {
+ ++begin;
+ if (begin != end)
+ begin = parse_arg_id(begin, end, precision_adapter{handler});
+ if (begin == end || *begin++ != '}')
+ return handler.on_error("invalid format string"), begin;
+ } else {
+ return handler.on_error("missing precision specifier"), begin;
+ }
+ handler.end_precision();
+ return begin;
+}
+
+template <typename Char>
+FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type {
+ switch (to_ascii(type)) {
+ case 'd':
+ return presentation_type::dec;
+ case 'o':
+ return presentation_type::oct;
+ case 'x':
+ return presentation_type::hex_lower;
+ case 'X':
+ return presentation_type::hex_upper;
+ case 'b':
+ return presentation_type::bin_lower;
+ case 'B':
+ return presentation_type::bin_upper;
+ case 'a':
+ return presentation_type::hexfloat_lower;
+ case 'A':
+ return presentation_type::hexfloat_upper;
+ case 'e':
+ return presentation_type::exp_lower;
+ case 'E':
+ return presentation_type::exp_upper;
+ case 'f':
+ return presentation_type::fixed_lower;
+ case 'F':
+ return presentation_type::fixed_upper;
+ case 'g':
+ return presentation_type::general_lower;
+ case 'G':
+ return presentation_type::general_upper;
+ case 'c':
+ return presentation_type::chr;
+ case 's':
+ return presentation_type::string;
+ case 'p':
+ return presentation_type::pointer;
+ default:
+ return presentation_type::none;
+ }
+}
+
+// Parses standard format specifiers and sends notifications about parsed
+// components to handler.
+template <typename Char, typename SpecHandler>
+FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin,
+ const Char* end,
+ SpecHandler&& handler)
+ -> const Char* {
+ if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) &&
+ *begin != 'L') {
+ presentation_type type = parse_presentation_type(*begin++);
+ if (type == presentation_type::none)
+ handler.on_error("invalid type specifier");
+ handler.on_type(type);
+ return begin;
+ }
+
+ if (begin == end) return begin;
+
+ begin = parse_align(begin, end, handler);
+ if (begin == end) return begin;
+
+ // Parse sign.
+ switch (to_ascii(*begin)) {
+ case '+':
+ handler.on_sign(sign::plus);
+ ++begin;
+ break;
+ case '-':
+ handler.on_sign(sign::minus);
+ ++begin;
+ break;
+ case ' ':
+ handler.on_sign(sign::space);
+ ++begin;
+ break;
+ default:
+ break;
+ }
+ if (begin == end) return begin;
+
+ if (*begin == '#') {
+ handler.on_hash();
+ if (++begin == end) return begin;
+ }
+
+ // Parse zero flag.
+ if (*begin == '0') {
+ handler.on_zero();
+ if (++begin == end) return begin;
+ }
+
+ begin = parse_width(begin, end, handler);
+ if (begin == end) return begin;
+
+ // Parse precision.
+ if (*begin == '.') {
+ begin = parse_precision(begin, end, handler);
+ if (begin == end) return begin;
+ }
+
+ if (*begin == 'L') {
+ handler.on_localized();
+ ++begin;
+ }
+
+ // Parse type.
+ if (begin != end && *begin != '}') {
+ presentation_type type = parse_presentation_type(*begin++);
+ if (type == presentation_type::none)
+ handler.on_error("invalid type specifier");
+ handler.on_type(type);
+ }
+ return begin;
+}
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end,
+ Handler&& handler) -> const Char* {
+ struct id_adapter {
+ Handler& handler;
+ int arg_id;
+
+ FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); }
+ FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); }
+ FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
+ arg_id = handler.on_arg_id(id);
+ }
+ FMT_CONSTEXPR void on_error(const char* message) {
+ if (message) handler.on_error(message);
+ }
+ };
+
+ ++begin;
+ if (begin == end) return handler.on_error("invalid format string"), end;
+ if (*begin == '}') {
+ handler.on_replacement_field(handler.on_arg_id(), begin);
+ } else if (*begin == '{') {
+ handler.on_text(begin, begin + 1);
+ } else {
+ auto adapter = id_adapter{handler, 0};
+ begin = parse_arg_id(begin, end, adapter);
+ Char c = begin != end ? *begin : Char();
+ if (c == '}') {
+ handler.on_replacement_field(adapter.arg_id, begin);
+ } else if (c == ':') {
+ begin = handler.on_format_specs(adapter.arg_id, begin + 1, end);
+ if (begin == end || *begin != '}')
+ return handler.on_error("unknown format specifier"), end;
+ } else {
+ return handler.on_error("missing '}' in format string"), end;
+ }
+ }
+ return begin + 1;
+}
+
+template <bool IS_CONSTEXPR, typename Char, typename Handler>
+FMT_CONSTEXPR FMT_INLINE void parse_format_string(
+ basic_string_view<Char> format_str, Handler&& handler) {
+ // Workaround a name-lookup bug in MSVC's modules implementation.
+ using detail::find;
+
+ auto begin = format_str.data();
+ auto end = begin + format_str.size();
+ if (end - begin < 32) {
+ // Use a simple loop instead of memchr for small strings.
+ const Char* p = begin;
+ while (p != end) {
+ auto c = *p++;
+ if (c == '{') {
+ handler.on_text(begin, p - 1);
+ begin = p = parse_replacement_field(p - 1, end, handler);
+ } else if (c == '}') {
+ if (p == end || *p != '}')
+ return handler.on_error("unmatched '}' in format string");
+ handler.on_text(begin, p);
+ begin = ++p;
+ }
+ }
+ handler.on_text(begin, end);
+ return;
+ }
+ struct writer {
+ FMT_CONSTEXPR void operator()(const Char* pbegin, const Char* pend) {
+ if (pbegin == pend) return;
+ for (;;) {
+ const Char* p = nullptr;
+ if (!find<IS_CONSTEXPR>(pbegin, pend, Char('}'), p))
+ return handler_.on_text(pbegin, pend);
+ ++p;
+ if (p == pend || *p != '}')
+ return handler_.on_error("unmatched '}' in format string");
+ handler_.on_text(pbegin, p);
+ pbegin = p + 1;
+ }
+ }
+ Handler& handler_;
+ } write{handler};
+ while (begin != end) {
+ // Doing two passes with memchr (one for '{' and another for '}') is up to
+ // 2.5x faster than the naive one-pass implementation on big format strings.
+ const Char* p = begin;
+ if (*begin != '{' && !find<IS_CONSTEXPR>(begin + 1, end, Char('{'), p))
+ return write(begin, end);
+ write(begin, p);
+ begin = parse_replacement_field(p, end, handler);
+ }
+}
+
+template <typename T, typename ParseContext>
+FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx)
+ -> decltype(ctx.begin()) {
+ using char_type = typename ParseContext::char_type;
+ using context = buffer_context<char_type>;
+ using mapped_type = conditional_t<
+ mapped_type_constant<T, context>::value != type::custom_type,
+ decltype(arg_mapper<context>().map(std::declval<const T&>())), T>;
+ auto f = conditional_t<has_formatter<mapped_type, context>::value,
+ formatter<mapped_type, char_type>,
+ fallback_formatter<T, char_type>>();
+ return f.parse(ctx);
+}
+
+// A parse context with extra argument id checks. It is only used at compile
+// time because adding checks at runtime would introduce substantial overhead
+// and would be redundant since argument ids are checked when arguments are
+// retrieved anyway.
+template <typename Char, typename ErrorHandler = error_handler>
+class compile_parse_context
+ : public basic_format_parse_context<Char, ErrorHandler> {
+ private:
+ int num_args_;
+ using base = basic_format_parse_context<Char, ErrorHandler>;
+
+ public:
+ explicit FMT_CONSTEXPR compile_parse_context(
+ basic_string_view<Char> format_str,
+ int num_args = (std::numeric_limits<int>::max)(), ErrorHandler eh = {})
+ : base(format_str, eh), num_args_(num_args) {}
+
+ FMT_CONSTEXPR auto next_arg_id() -> int {
+ int id = base::next_arg_id();
+ if (id >= num_args_) this->on_error("argument not found");
+ return id;
+ }
+
+ FMT_CONSTEXPR void check_arg_id(int id) {
+ base::check_arg_id(id);
+ if (id >= num_args_) this->on_error("argument not found");
+ }
+ using base::check_arg_id;
};
-struct wformat_args : basic_format_args<wformat_context> {
- using basic_format_args::basic_format_args;
+
+template <typename ErrorHandler>
+FMT_CONSTEXPR void check_int_type_spec(presentation_type type,
+ ErrorHandler&& eh) {
+ if (type > presentation_type::bin_upper && type != presentation_type::chr)
+ eh.on_error("invalid type specifier");
+}
+
+// Checks char specs and returns true if the type spec is char (and not int).
+template <typename Char, typename ErrorHandler = error_handler>
+FMT_CONSTEXPR auto check_char_specs(const basic_format_specs<Char>& specs,
+ ErrorHandler&& eh = {}) -> bool {
+ if (specs.type != presentation_type::none &&
+ specs.type != presentation_type::chr) {
+ check_int_type_spec(specs.type, eh);
+ return false;
+ }
+ if (specs.align == align::numeric || specs.sign != sign::none || specs.alt)
+ eh.on_error("invalid format specifier for char");
+ return true;
+}
+
+// A floating-point presentation format.
+enum class float_format : unsigned char {
+ general, // General: exponent notation or fixed point based on magnitude.
+ exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3.
+ fixed, // Fixed point with the default precision of 6, e.g. 0.0012.
+ hex
+};
+
+struct float_specs {
+ int precision;
+ float_format format : 8;
+ sign_t sign : 8;
+ bool upper : 1;
+ bool locale : 1;
+ bool binary32 : 1;
+ bool fallback : 1;
+ bool showpoint : 1;
+};
+
+template <typename ErrorHandler = error_handler, typename Char>
+FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs<Char>& specs,
+ ErrorHandler&& eh = {})
+ -> float_specs {
+ auto result = float_specs();
+ result.showpoint = specs.alt;
+ result.locale = specs.localized;
+ switch (specs.type) {
+ case presentation_type::none:
+ result.format = float_format::general;
+ break;
+ case presentation_type::general_upper:
+ result.upper = true;
+ FMT_FALLTHROUGH;
+ case presentation_type::general_lower:
+ result.format = float_format::general;
+ break;
+ case presentation_type::exp_upper:
+ result.upper = true;
+ FMT_FALLTHROUGH;
+ case presentation_type::exp_lower:
+ result.format = float_format::exp;
+ result.showpoint |= specs.precision != 0;
+ break;
+ case presentation_type::fixed_upper:
+ result.upper = true;
+ FMT_FALLTHROUGH;
+ case presentation_type::fixed_lower:
+ result.format = float_format::fixed;
+ result.showpoint |= specs.precision != 0;
+ break;
+ case presentation_type::hexfloat_upper:
+ result.upper = true;
+ FMT_FALLTHROUGH;
+ case presentation_type::hexfloat_lower:
+ result.format = float_format::hex;
+ break;
+ default:
+ eh.on_error("invalid type specifier");
+ break;
+ }
+ return result;
+}
+
+template <typename ErrorHandler = error_handler>
+FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type,
+ ErrorHandler&& eh = {}) -> bool {
+ if (type == presentation_type::none || type == presentation_type::string)
+ return true;
+ if (type != presentation_type::pointer) eh.on_error("invalid type specifier");
+ return false;
+}
+
+template <typename ErrorHandler = error_handler>
+FMT_CONSTEXPR void check_string_type_spec(presentation_type type,
+ ErrorHandler&& eh = {}) {
+ if (type != presentation_type::none && type != presentation_type::string)
+ eh.on_error("invalid type specifier");
+}
+
+template <typename ErrorHandler>
+FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type,
+ ErrorHandler&& eh) {
+ if (type != presentation_type::none && type != presentation_type::pointer)
+ eh.on_error("invalid type specifier");
+}
+
+// A parse_format_specs handler that checks if specifiers are consistent with
+// the argument type.
+template <typename Handler> class specs_checker : public Handler {
+ private:
+ detail::type arg_type_;
+
+ FMT_CONSTEXPR void require_numeric_argument() {
+ if (!is_arithmetic_type(arg_type_))
+ this->on_error("format specifier requires numeric argument");
+ }
+
+ public:
+ FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type)
+ : Handler(handler), arg_type_(arg_type) {}
+
+ FMT_CONSTEXPR void on_align(align_t align) {
+ if (align == align::numeric) require_numeric_argument();
+ Handler::on_align(align);
+ }
+
+ FMT_CONSTEXPR void on_sign(sign_t s) {
+ require_numeric_argument();
+ if (is_integral_type(arg_type_) && arg_type_ != type::int_type &&
+ arg_type_ != type::long_long_type && arg_type_ != type::char_type) {
+ this->on_error("format specifier requires signed argument");
+ }
+ Handler::on_sign(s);
+ }
+
+ FMT_CONSTEXPR void on_hash() {
+ require_numeric_argument();
+ Handler::on_hash();
+ }
+
+ FMT_CONSTEXPR void on_localized() {
+ require_numeric_argument();
+ Handler::on_localized();
+ }
+
+ FMT_CONSTEXPR void on_zero() {
+ require_numeric_argument();
+ Handler::on_zero();
+ }
+
+ FMT_CONSTEXPR void end_precision() {
+ if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type)
+ this->on_error("precision not allowed for this argument type");
+ }
};
+
+constexpr int invalid_arg_index = -1;
+
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <int N, typename T, typename... Args, typename Char>
+constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
+ if constexpr (detail::is_statically_named_arg<T>()) {
+ if (name == T::name) return N;
+ }
+ if constexpr (sizeof...(Args) > 0)
+ return get_arg_index_by_name<N + 1, Args...>(name);
+ (void)name; // Workaround an MSVC bug about "unused" parameter.
+ return invalid_arg_index;
+}
+#endif
+
+template <typename... Args, typename Char>
+FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+ if constexpr (sizeof...(Args) > 0)
+ return get_arg_index_by_name<0, Args...>(name);
+#endif
+ (void)name;
+ return invalid_arg_index;
+}
+
+template <typename Char, typename ErrorHandler, typename... Args>
+class format_string_checker {
+ private:
+ using parse_context_type = compile_parse_context<Char, ErrorHandler>;
+ enum { num_args = sizeof...(Args) };
+
+ // Format specifier parsing function.
+ using parse_func = const Char* (*)(parse_context_type&);
+
+ parse_context_type context_;
+ parse_func parse_funcs_[num_args > 0 ? num_args : 1];
+
+ public:
+ explicit FMT_CONSTEXPR format_string_checker(
+ basic_string_view<Char> format_str, ErrorHandler eh)
+ : context_(format_str, num_args, eh),
+ parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {}
+
+ FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
+
+ FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); }
+ FMT_CONSTEXPR auto on_arg_id(int id) -> int {
+ return context_.check_arg_id(id), id;
+ }
+ FMT_CONSTEXPR auto on_arg_id(basic_string_view<Char> id) -> int {
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+ auto index = get_arg_index_by_name<Args...>(id);
+ if (index == invalid_arg_index) on_error("named argument is not found");
+ return context_.check_arg_id(index), index;
+#else
+ (void)id;
+ on_error("compile-time checks for named arguments require C++20 support");
+ return 0;
#endif
+ }
+
+ FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
-namespace detail {
+ FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*)
+ -> const Char* {
+ context_.advance_to(context_.begin() + (begin - &*context_.begin()));
+ // id >= 0 check is a workaround for gcc 10 bug (#2065).
+ return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin;
+ }
-template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
-std::basic_string<Char> vformat(
- basic_string_view<Char> format_str,
- basic_format_args<buffer_context<type_identity_t<Char>>> args);
+ FMT_CONSTEXPR void on_error(const char* message) {
+ context_.on_error(message);
+ }
+};
-FMT_API std::string vformat(string_view format_str, format_args args);
+template <typename... Args, typename S,
+ enable_if_t<(is_compile_string<S>::value), int>>
+void check_format_string(S format_str) {
+ FMT_CONSTEXPR auto s = to_string_view(format_str);
+ using checker = format_string_checker<typename S::char_type, error_handler,
+ remove_cvref_t<Args>...>;
+ FMT_CONSTEXPR bool invalid_format =
+ (parse_format_string<true>(s, checker(s, {})), true);
+ ignore_unused(invalid_format);
+}
template <typename Char>
void vformat_to(
- buffer<Char>& buf, basic_string_view<Char> format_str,
+ buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
- detail::locale_ref loc = {});
-
-template <typename Char, typename Args,
- FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
-inline void vprint_mojibake(std::FILE*, basic_string_view<Char>, const Args&) {}
+ locale_ref loc = {});
FMT_API void vprint_mojibake(std::FILE*, string_view, format_args);
#ifndef _WIN32
inline void vprint_mojibake(std::FILE*, string_view, format_args) {}
#endif
-} // namespace detail
+FMT_END_DETAIL_NAMESPACE
+
+// A formatter specialization for the core types corresponding to detail::type
+// constants.
+template <typename T, typename Char>
+struct formatter<T, Char,
+ enable_if_t<detail::type_constant<T, Char>::value !=
+ detail::type::custom_type>> {
+ private:
+ detail::dynamic_format_specs<Char> specs_;
+
+ public:
+ // Parses format specifiers stopping either at the end of the range or at the
+ // terminating '}'.
+ template <typename ParseContext>
+ FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+ auto begin = ctx.begin(), end = ctx.end();
+ if (begin == end) return begin;
+ using handler_type = detail::dynamic_specs_handler<ParseContext>;
+ auto type = detail::type_constant<T, Char>::value;
+ auto checker =
+ detail::specs_checker<handler_type>(handler_type(specs_, ctx), type);
+ auto it = detail::parse_format_specs(begin, end, checker);
+ auto eh = ctx.error_handler();
+ switch (type) {
+ case detail::type::none_type:
+ FMT_ASSERT(false, "invalid argument type");
+ break;
+ case detail::type::bool_type:
+ if (specs_.type == presentation_type::none ||
+ specs_.type == presentation_type::string) {
+ break;
+ }
+ FMT_FALLTHROUGH;
+ case detail::type::int_type:
+ case detail::type::uint_type:
+ case detail::type::long_long_type:
+ case detail::type::ulong_long_type:
+ case detail::type::int128_type:
+ case detail::type::uint128_type:
+ detail::check_int_type_spec(specs_.type, eh);
+ break;
+ case detail::type::char_type:
+ detail::check_char_specs(specs_, eh);
+ break;
+ case detail::type::float_type:
+ if (detail::const_check(FMT_USE_FLOAT))
+ detail::parse_float_type_spec(specs_, eh);
+ else
+ FMT_ASSERT(false, "float support disabled");
+ break;
+ case detail::type::double_type:
+ if (detail::const_check(FMT_USE_DOUBLE))
+ detail::parse_float_type_spec(specs_, eh);
+ else
+ FMT_ASSERT(false, "double support disabled");
+ break;
+ case detail::type::long_double_type:
+ if (detail::const_check(FMT_USE_LONG_DOUBLE))
+ detail::parse_float_type_spec(specs_, eh);
+ else
+ FMT_ASSERT(false, "long double support disabled");
+ break;
+ case detail::type::cstring_type:
+ detail::check_cstring_type_spec(specs_.type, eh);
+ break;
+ case detail::type::string_type:
+ detail::check_string_type_spec(specs_.type, eh);
+ break;
+ case detail::type::pointer_type:
+ detail::check_pointer_type_spec(specs_.type, eh);
+ break;
+ case detail::type::custom_type:
+ // Custom format specifiers are checked in parse functions of
+ // formatter specializations.
+ break;
+ }
+ return it;
+ }
+
+ template <typename FormatContext>
+ FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const
+ -> decltype(ctx.out());
+};
+
+template <typename Char> struct basic_runtime { basic_string_view<Char> str; };
+
+/** A compile-time format string. */
+template <typename Char, typename... Args> class basic_format_string {
+ private:
+ basic_string_view<Char> str_;
+
+ public:
+ template <typename S,
+ FMT_ENABLE_IF(
+ std::is_convertible<const S&, basic_string_view<Char>>::value)>
+ FMT_CONSTEVAL FMT_INLINE basic_format_string(const S& s) : str_(s) {
+ static_assert(
+ detail::count<
+ (std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
+ std::is_reference<Args>::value)...>() == 0,
+ "passing views as lvalues is disallowed");
+#ifdef FMT_HAS_CONSTEVAL
+ if constexpr (detail::count_named_args<Args...>() ==
+ detail::count_statically_named_args<Args...>()) {
+ using checker = detail::format_string_checker<Char, detail::error_handler,
+ remove_cvref_t<Args>...>;
+ detail::parse_format_string<true>(str_, checker(s, {}));
+ }
+#else
+ detail::check_format_string<Args...>(s);
+#endif
+ }
+ basic_format_string(basic_runtime<Char> r) : str_(r.str) {}
+
+ FMT_INLINE operator basic_string_view<Char>() const { return str_; }
+};
+
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+// Workaround broken conversion on older gcc.
+template <typename... Args> using format_string = string_view;
+template <typename S> auto runtime(const S& s) -> basic_string_view<char_t<S>> {
+ return s;
+}
+#else
+template <typename... Args>
+using format_string = basic_format_string<char, type_identity_t<Args>...>;
+/**
+ \rst
+ Creates a runtime format string.
+
+ **Example**::
+
+ // Check format string at runtime instead of compile-time.
+ fmt::print(fmt::runtime("{:d}"), "I am not a number");
+ \endrst
+ */
+template <typename S> auto runtime(const S& s) -> basic_runtime<char_t<S>> {
+ return {{s}};
+}
+#endif
+
+FMT_API auto vformat(string_view fmt, format_args args) -> std::string;
+
+/**
+ \rst
+ Formats ``args`` according to specifications in ``fmt`` and returns the result
+ as a string.
+
+ **Example**::
+
+ #include <fmt/core.h>
+ std::string message = fmt::format("The answer is {}.", 42);
+ \endrst
+*/
+template <typename... T>
+FMT_NODISCARD FMT_INLINE auto format(format_string<T...> fmt, T&&... args)
+ -> std::string {
+ return vformat(fmt, fmt::make_format_args(args...));
+}
/** Formats a string and writes the output to ``out``. */
-// GCC 8 and earlier cannot handle std::back_insert_iterator<Container> with
-// vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead.
-template <typename OutputIt, typename S, typename Char = char_t<S>,
- bool enable = detail::is_output_iterator<OutputIt, Char>::value>
-auto vformat_to(OutputIt out, const S& format_str,
- basic_format_args<buffer_context<type_identity_t<Char>>> args)
- -> typename std::enable_if<enable, OutputIt>::type {
- decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
- detail::vformat_to(buf, to_string_view(format_str), args);
+template <typename OutputIt,
+ FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
+auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt {
+ using detail::get_buffer;
+ auto&& buf = get_buffer<char>(out);
+ detail::vformat_to(buf, fmt, args, {});
return detail::get_iterator(buf);
}
/**
\rst
- Formats arguments, writes the result to the output iterator ``out`` and returns
- the iterator past the end of the output range.
+ Formats ``args`` according to specifications in ``fmt``, writes the result to
+ the output iterator ``out`` and returns the iterator past the end of the output
+ range. `format_to` does not append a terminating null character.
**Example**::
- std::vector<char> out;
+ auto out = std::vector<char>();
fmt::format_to(std::back_inserter(out), "{}", 42);
\endrst
*/
-// We cannot use FMT_ENABLE_IF because of a bug in gcc 8.3.
-template <typename OutputIt, typename S, typename... Args,
- bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
-inline auto format_to(OutputIt out, const S& format_str, Args&&... args) ->
- typename std::enable_if<enable, OutputIt>::type {
- const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
- return vformat_to(out, to_string_view(format_str), vargs);
+template <typename OutputIt, typename... T,
+ FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
+FMT_INLINE auto format_to(OutputIt out, format_string<T...> fmt, T&&... args)
+ -> OutputIt {
+ return vformat_to(out, fmt, fmt::make_format_args(args...));
}
template <typename OutputIt> struct format_to_n_result {
size_t size;
};
-template <typename OutputIt, typename Char, typename... Args,
- FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
-inline format_to_n_result<OutputIt> vformat_to_n(
- OutputIt out, size_t n, basic_string_view<Char> format_str,
- basic_format_args<buffer_context<type_identity_t<Char>>> args) {
- detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
- n);
- detail::vformat_to(buf, format_str, args);
+template <typename OutputIt, typename... T,
+ FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
+auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args)
+ -> format_to_n_result<OutputIt> {
+ using traits = detail::fixed_buffer_traits;
+ auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
+ detail::vformat_to(buf, fmt, args, {});
return {buf.out(), buf.count()};
}
/**
- \rst
- Formats arguments, writes up to ``n`` characters of the result to the output
- iterator ``out`` and returns the total output size and the iterator past the
- end of the output range.
- \endrst
+ \rst
+ Formats ``args`` according to specifications in ``fmt``, writes up to ``n``
+ characters of the result to the output iterator ``out`` and returns the total
+ (not truncated) output size and the iterator past the end of the output range.
+ `format_to_n` does not append a terminating null character.
+ \endrst
*/
-template <typename OutputIt, typename S, typename... Args,
- bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
-inline auto format_to_n(OutputIt out, size_t n, const S& format_str,
- const Args&... args) ->
- typename std::enable_if<enable, format_to_n_result<OutputIt>>::type {
- const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
- return vformat_to_n(out, n, to_string_view(format_str), vargs);
+template <typename OutputIt, typename... T,
+ FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
+FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string<T...> fmt,
+ T&&... args) -> format_to_n_result<OutputIt> {
+ return vformat_to_n(out, n, fmt, fmt::make_format_args(args...));
}
-/**
- Returns the number of characters in the output of
- ``format(format_str, args...)``.
- */
-template <typename... Args>
-inline size_t formatted_size(string_view format_str, Args&&... args) {
- const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
- detail::counting_buffer<> buf;
- detail::vformat_to(buf, format_str, vargs);
+/** Returns the number of chars in the output of ``format(fmt, args...)``. */
+template <typename... T>
+FMT_NODISCARD FMT_INLINE auto formatted_size(format_string<T...> fmt,
+ T&&... args) -> size_t {
+ auto buf = detail::counting_buffer<>();
+ detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {});
return buf.count();
}
-template <typename S, typename Char = char_t<S>>
-FMT_INLINE std::basic_string<Char> vformat(
- const S& format_str,
- basic_format_args<buffer_context<type_identity_t<Char>>> args) {
- return detail::vformat(to_string_view(format_str), args);
-}
+FMT_API void vprint(string_view fmt, format_args args);
+FMT_API void vprint(std::FILE* f, string_view fmt, format_args args);
/**
\rst
- Formats arguments and returns the result as a string.
+ Formats ``args`` according to specifications in ``fmt`` and writes the output
+ to ``stdout``.
**Example**::
- #include <fmt/core.h>
- std::string message = fmt::format("The answer is {}", 42);
+ fmt::print("Elapsed time: {0:.2f} seconds", 1.23);
\endrst
-*/
-// Pass char_t as a default template parameter instead of using
-// std::basic_string<char_t<S>> to reduce the symbol size.
-template <typename S, typename... Args, typename Char = char_t<S>>
-FMT_INLINE std::basic_string<Char> format(const S& format_str, Args&&... args) {
- const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
- return detail::vformat(to_string_view(format_str), vargs);
+ */
+template <typename... T>
+FMT_INLINE void print(format_string<T...> fmt, T&&... args) {
+ const auto& vargs = fmt::make_format_args(args...);
+ return detail::is_utf8() ? vprint(fmt, vargs)
+ : detail::vprint_mojibake(stdout, fmt, vargs);
}
-FMT_API void vprint(string_view, format_args);
-FMT_API void vprint(std::FILE*, string_view, format_args);
-
/**
\rst
- Formats ``args`` according to specifications in ``format_str`` and writes the
- output to the file ``f``. Strings are assumed to be Unicode-encoded unless the
- ``FMT_UNICODE`` macro is set to 0.
+ Formats ``args`` according to specifications in ``fmt`` and writes the
+ output to the file ``f``.
**Example**::
fmt::print(stderr, "Don't {}!", "panic");
\endrst
*/
-template <typename S, typename... Args, typename Char = char_t<S>>
-inline void print(std::FILE* f, const S& format_str, Args&&... args) {
- const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
- return detail::is_unicode<Char>()
- ? vprint(f, to_string_view(format_str), vargs)
- : detail::vprint_mojibake(f, to_string_view(format_str), vargs);
+template <typename... T>
+FMT_INLINE void print(std::FILE* f, format_string<T...> fmt, T&&... args) {
+ const auto& vargs = fmt::make_format_args(args...);
+ return detail::is_utf8() ? vprint(f, fmt, vargs)
+ : detail::vprint_mojibake(f, fmt, vargs);
}
-/**
- \rst
- Formats ``args`` according to specifications in ``format_str`` and writes
- the output to ``stdout``. Strings are assumed to be Unicode-encoded unless
- the ``FMT_UNICODE`` macro is set to 0.
-
- **Example**::
-
- fmt::print("Elapsed time: {0:.2f} seconds", 1.23);
- \endrst
- */
-template <typename S, typename... Args, typename Char = char_t<S>>
-inline void print(const S& format_str, Args&&... args) {
- const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
- return detail::is_unicode<Char>()
- ? vprint(to_string_view(format_str), vargs)
- : detail::vprint_mojibake(stdout, to_string_view(format_str),
- vargs);
-}
+FMT_MODULE_EXPORT_END
+FMT_GCC_PRAGMA("GCC pop_options")
FMT_END_NAMESPACE
+#ifdef FMT_HEADER_ONLY
+# include "format.h"
+#endif
#endif // FMT_CORE_H_
#ifndef FMT_FORMAT_INL_H_
#define FMT_FORMAT_INL_H_
-#include <cassert>
+#include <algorithm>
#include <cctype>
+#include <cerrno> // errno
#include <climits>
#include <cmath>
#include <cstdarg>
#include "format.h"
-// Dummy implementations of strerror_r and strerror_s called if corresponding
-// system functions are not available.
-inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; }
-inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; }
-
FMT_BEGIN_NAMESPACE
namespace detail {
std::terminate();
}
+FMT_FUNC void throw_format_error(const char* message) {
+ FMT_THROW(format_error(message));
+}
+
#ifndef _MSC_VER
# define FMT_SNPRINTF snprintf
#else // _MSC_VER
# define FMT_SNPRINTF fmt_snprintf
#endif // _MSC_VER
-// A portable thread-safe version of strerror.
-// Sets buffer to point to a string describing the error code.
-// This can be either a pointer to a string stored in buffer,
-// or a pointer to some static immutable string.
-// Returns one of the following values:
-// 0 - success
-// ERANGE - buffer is not large enough to store the error message
-// other - failure
-// Buffer should be at least of size 1.
-inline int safe_strerror(int error_code, char*& buffer,
- size_t buffer_size) FMT_NOEXCEPT {
- FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer");
-
- class dispatcher {
- private:
- int error_code_;
- char*& buffer_;
- size_t buffer_size_;
-
- // A noop assignment operator to avoid bogus warnings.
- void operator=(const dispatcher&) {}
-
- // Handle the result of XSI-compliant version of strerror_r.
- int handle(int result) {
- // glibc versions before 2.13 return result in errno.
- return result == -1 ? errno : result;
- }
-
- // Handle the result of GNU-specific version of strerror_r.
- FMT_MAYBE_UNUSED
- int handle(char* message) {
- // If the buffer is full then the message is probably truncated.
- if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1)
- return ERANGE;
- buffer_ = message;
- return 0;
- }
-
- // Handle the case when strerror_r is not available.
- FMT_MAYBE_UNUSED
- int handle(detail::null<>) {
- return fallback(strerror_s(buffer_, buffer_size_, error_code_));
- }
-
- // Fallback to strerror_s when strerror_r is not available.
- FMT_MAYBE_UNUSED
- int fallback(int result) {
- // If the buffer is full then the message is probably truncated.
- return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? ERANGE
- : result;
- }
-
-#if !FMT_MSC_VER
- // Fallback to strerror if strerror_r and strerror_s are not available.
- int fallback(detail::null<>) {
- errno = 0;
- buffer_ = strerror(error_code_);
- return errno;
- }
-#endif
-
- public:
- dispatcher(int err_code, char*& buf, size_t buf_size)
- : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
-
- int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); }
- };
- return dispatcher(error_code, buffer, buffer_size).run();
-}
-
FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT {
// Report error code making sure that the output fits into
error_code_size += detail::to_unsigned(detail::count_digits(abs_value));
auto it = buffer_appender<char>(out);
if (message.size() <= inline_buffer_size - error_code_size)
- format_to(it, "{}{}", message, SEP);
- format_to(it, "{}{}", ERROR_STR, error_code);
- assert(out.size() <= inline_buffer_size);
+ format_to(it, FMT_STRING("{}{}"), message, SEP);
+ format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code);
+ FMT_ASSERT(out.size() <= inline_buffer_size, "");
}
FMT_FUNC void report_error(format_func func, int error_code,
- string_view message) FMT_NOEXCEPT {
+ const char* message) FMT_NOEXCEPT {
memory_buffer full_message;
func(full_message, error_code, message);
// Don't use fwrite_fully because the latter may throw.
- (void)std::fwrite(full_message.data(), full_message.size(), 1, stderr);
- std::fputc('\n', stderr);
+ if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0)
+ std::fputc('\n', stderr);
}
// A wrapper around fwrite that throws on error.
size_t written = std::fwrite(ptr, size, count, stream);
if (written < count) FMT_THROW(system_error(errno, "cannot write to file"));
}
-} // namespace detail
-
-#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
-namespace detail {
+#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Locale>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, std::locale>::value, "");
return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale();
}
-template <typename Char> FMT_FUNC std::string grouping_impl(locale_ref loc) {
- return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>()).grouping();
-}
-template <typename Char> FMT_FUNC Char thousands_sep_impl(locale_ref loc) {
- return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
- .thousands_sep();
+template <typename Char>
+FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> {
+ auto& facet = std::use_facet<std::numpunct<Char>>(loc.get<std::locale>());
+ auto grouping = facet.grouping();
+ auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep();
+ return {std::move(grouping), thousands_sep};
}
template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) {
return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
.decimal_point();
}
-} // namespace detail
#else
template <typename Char>
-FMT_FUNC std::string detail::grouping_impl(locale_ref) {
- return "\03";
-}
-template <typename Char> FMT_FUNC Char detail::thousands_sep_impl(locale_ref) {
- return FMT_STATIC_THOUSANDS_SEPARATOR;
+FMT_FUNC auto thousands_sep_impl(locale_ref) -> thousands_sep_result<Char> {
+ return {"\03", FMT_STATIC_THOUSANDS_SEPARATOR};
}
-template <typename Char> FMT_FUNC Char detail::decimal_point_impl(locale_ref) {
+template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
return '.';
}
#endif
+} // namespace detail
+#if !FMT_MSC_VER
FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT = default;
-FMT_API FMT_FUNC system_error::~system_error() FMT_NOEXCEPT = default;
+#endif
-FMT_FUNC void system_error::init(int err_code, string_view format_str,
- format_args args) {
- error_code_ = err_code;
- memory_buffer buffer;
- format_system_error(buffer, err_code, vformat(format_str, args));
- std::runtime_error& base = *this;
- base = std::runtime_error(to_string(buffer));
+FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str,
+ format_args args) {
+ auto ec = std::error_code(error_code, std::generic_category());
+ return std::system_error(ec, vformat(format_str, args));
}
namespace detail {
return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1;
}
-template <typename T>
-const typename basic_data<T>::digit_pair basic_data<T>::digits[] = {
- {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
- {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
- {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
- {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
- {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
- {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
- {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
- {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
- {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
- {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
- {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
- {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
- {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
- {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
- {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
- {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
- {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
-
-template <typename T>
-const char basic_data<T>::hex_digits[] = "0123456789abcdef";
-
-#define FMT_POWERS_OF_10(factor) \
- factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \
- (factor)*1000000, (factor)*10000000, (factor)*100000000, \
- (factor)*1000000000
-
-template <typename T>
-const uint64_t basic_data<T>::powers_of_10_64[] = {
- 1, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL),
- 10000000000000000000ULL};
-
-template <typename T>
-const uint32_t basic_data<T>::zero_or_powers_of_10_32[] = {0,
- FMT_POWERS_OF_10(1)};
-template <typename T>
-const uint64_t basic_data<T>::zero_or_powers_of_10_64[] = {
- 0, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL),
- 10000000000000000000ULL};
-
-template <typename T>
-const uint32_t basic_data<T>::zero_or_powers_of_10_32_new[] = {
- 0, 0, FMT_POWERS_OF_10(1)};
-
-template <typename T>
-const uint64_t basic_data<T>::zero_or_powers_of_10_64_new[] = {
- 0, 0, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL),
- 10000000000000000000ULL};
-
-// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
-// These are generated by support/compute-powers.py.
-template <typename T>
-const uint64_t basic_data<T>::grisu_pow10_significands[] = {
- 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
- 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
- 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
- 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
- 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
- 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
- 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
- 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
- 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
- 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
- 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
- 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
- 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
- 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
- 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
- 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
- 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
- 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
- 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
- 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
- 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
- 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
- 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
- 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
- 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
- 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
- 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
- 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
- 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
-};
-
-// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
-// to significands above.
-template <typename T>
-const int16_t basic_data<T>::grisu_pow10_exponents[] = {
- -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
- -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
- -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
- -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
- -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
- 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
- 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
- 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066};
-
-template <typename T>
-const divtest_table_entry<uint32_t> basic_data<T>::divtest_table_for_pow5_32[] =
- {{0x00000001, 0xffffffff}, {0xcccccccd, 0x33333333},
- {0xc28f5c29, 0x0a3d70a3}, {0x26e978d5, 0x020c49ba},
- {0x3afb7e91, 0x0068db8b}, {0x0bcbe61d, 0x0014f8b5},
- {0x68c26139, 0x000431bd}, {0xae8d46a5, 0x0000d6bf},
- {0x22e90e21, 0x00002af3}, {0x3a2e9c6d, 0x00000897},
- {0x3ed61f49, 0x000001b7}};
-
-template <typename T>
-const divtest_table_entry<uint64_t> basic_data<T>::divtest_table_for_pow5_64[] =
- {{0x0000000000000001, 0xffffffffffffffff},
- {0xcccccccccccccccd, 0x3333333333333333},
- {0x8f5c28f5c28f5c29, 0x0a3d70a3d70a3d70},
- {0x1cac083126e978d5, 0x020c49ba5e353f7c},
- {0xd288ce703afb7e91, 0x0068db8bac710cb2},
- {0x5d4e8fb00bcbe61d, 0x0014f8b588e368f0},
- {0x790fb65668c26139, 0x000431bde82d7b63},
- {0xe5032477ae8d46a5, 0x0000d6bf94d5e57a},
- {0xc767074b22e90e21, 0x00002af31dc46118},
- {0x8e47ce423a2e9c6d, 0x0000089705f4136b},
- {0x4fa7f60d3ed61f49, 0x000001b7cdfd9d7b},
- {0x0fee64690c913975, 0x00000057f5ff85e5},
- {0x3662e0e1cf503eb1, 0x000000119799812d},
- {0xa47a2cf9f6433fbd, 0x0000000384b84d09},
- {0x54186f653140a659, 0x00000000b424dc35},
- {0x7738164770402145, 0x0000000024075f3d},
- {0xe4a4d1417cd9a041, 0x000000000734aca5},
- {0xc75429d9e5c5200d, 0x000000000170ef54},
- {0xc1773b91fac10669, 0x000000000049c977},
- {0x26b172506559ce15, 0x00000000000ec1e4},
- {0xd489e3a9addec2d1, 0x000000000002f394},
- {0x90e860bb892c8d5d, 0x000000000000971d},
- {0x502e79bf1b6f4f79, 0x0000000000001e39},
- {0xdcd618596be30fe5, 0x000000000000060b}};
-
-template <typename T>
-const uint64_t basic_data<T>::dragonbox_pow10_significands_64[] = {
- 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
- 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
- 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
- 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb,
- 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a,
- 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810,
- 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff,
- 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd,
- 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424,
- 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b,
- 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000,
- 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000,
- 0xc350000000000000, 0xf424000000000000, 0x9896800000000000,
- 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000,
- 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000,
- 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000,
- 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000,
- 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000,
- 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0,
- 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940984,
- 0xa18f07d736b90be5, 0xc9f2c9cd04674ede, 0xfc6f7c4045812296,
- 0x9dc5ada82b70b59d, 0xc5371912364ce305, 0xf684df56c3e01bc6,
- 0x9a130b963a6c115c, 0xc097ce7bc90715b3, 0xf0bdc21abb48db20,
- 0x96769950b50d88f4, 0xbc143fa4e250eb31, 0xeb194f8e1ae525fd,
- 0x92efd1b8d0cf37be, 0xb7abc627050305ad, 0xe596b7b0c643c719,
- 0x8f7e32ce7bea5c6f, 0xb35dbf821ae4f38b, 0xe0352f62a19e306e};
+// log10(2) = 0x0.4d104d427de7fbcc...
+static constexpr uint64_t log10_2_significand = 0x4d104d427de7fbcc;
+
+template <typename T = void> struct basic_impl_data {
+ // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
+ // These are generated by support/compute-powers.py.
+ static constexpr uint64_t pow10_significands[87] = {
+ 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
+ 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
+ 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
+ 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
+ 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
+ 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
+ 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
+ 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
+ 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
+ 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
+ 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
+ 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
+ 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
+ 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
+ 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
+ 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
+ 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
+ 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
+ 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
+ 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
+ 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
+ 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
+ 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
+ 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
+ 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
+ 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
+ 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
+ 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
+ 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
+ };
-template <typename T>
-const uint128_wrapper basic_data<T>::dragonbox_pow10_significands_128[] = {
-#if FMT_USE_FULL_CACHE_DRAGONBOX
- {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
- {0x9faacf3df73609b1, 0x77b191618c54e9ad},
- {0xc795830d75038c1d, 0xd59df5b9ef6a2418},
- {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e},
- {0x9becce62836ac577, 0x4ee367f9430aec33},
- {0xc2e801fb244576d5, 0x229c41f793cda740},
- {0xf3a20279ed56d48a, 0x6b43527578c11110},
- {0x9845418c345644d6, 0x830a13896b78aaaa},
- {0xbe5691ef416bd60c, 0x23cc986bc656d554},
- {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9},
- {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa},
- {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54},
- {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69},
- {0x91376c36d99995be, 0x23100809b9c21fa2},
- {0xb58547448ffffb2d, 0xabd40a0c2832a78b},
- {0xe2e69915b3fff9f9, 0x16c90c8f323f516d},
- {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4},
- {0xb1442798f49ffb4a, 0x99cd11cfdf41779d},
- {0xdd95317f31c7fa1d, 0x40405643d711d584},
- {0x8a7d3eef7f1cfc52, 0x482835ea666b2573},
- {0xad1c8eab5ee43b66, 0xda3243650005eed0},
- {0xd863b256369d4a40, 0x90bed43e40076a83},
- {0x873e4f75e2224e68, 0x5a7744a6e804a292},
- {0xa90de3535aaae202, 0x711515d0a205cb37},
- {0xd3515c2831559a83, 0x0d5a5b44ca873e04},
- {0x8412d9991ed58091, 0xe858790afe9486c3},
- {0xa5178fff668ae0b6, 0x626e974dbe39a873},
- {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
- {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a},
- {0xa139029f6a239f72, 0x1c1fffc1ebc44e81},
- {0xc987434744ac874e, 0xa327ffb266b56221},
- {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9},
- {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa},
- {0xc4ce17b399107c22, 0xcb550fb4384d21d4},
- {0xf6019da07f549b2b, 0x7e2a53a146606a49},
- {0x99c102844f94e0fb, 0x2eda7444cbfc426e},
- {0xc0314325637a1939, 0xfa911155fefb5309},
- {0xf03d93eebc589f88, 0x793555ab7eba27cb},
- {0x96267c7535b763b5, 0x4bc1558b2f3458df},
- {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17},
- {0xea9c227723ee8bcb, 0x465e15a979c1cadd},
- {0x92a1958a7675175f, 0x0bfacd89ec191eca},
- {0xb749faed14125d36, 0xcef980ec671f667c},
- {0xe51c79a85916f484, 0x82b7e12780e7401b},
- {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811},
- {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16},
- {0xdfbdcece67006ac9, 0x67a791e093e1d49b},
- {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1},
- {0xaecc49914078536d, 0x58fae9f773886e19},
- {0xda7f5bf590966848, 0xaf39a475506a899f},
- {0x888f99797a5e012d, 0x6d8406c952429604},
- {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84},
- {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65},
- {0x855c3be0a17fcd26, 0x5cf2eea09a550680},
- {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f},
- {0xd0601d8efc57b08b, 0xf13b94daf124da27},
- {0x823c12795db6ce57, 0x76c53d08d6b70859},
- {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f},
- {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a},
- {0xfe5d54150b090b02, 0xd3f93b35435d7c4d},
- {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0},
- {0xc6b8e9b0709f109a, 0x359ab6419ca1091c},
- {0xf867241c8cc6d4c0, 0xc30163d203c94b63},
- {0x9b407691d7fc44f8, 0x79e0de63425dcf1e},
- {0xc21094364dfb5636, 0x985915fc12f542e5},
- {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e},
- {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43},
- {0xbd8430bd08277231, 0x50c6ff782a838354},
- {0xece53cec4a314ebd, 0xa4f8bf5635246429},
- {0x940f4613ae5ed136, 0x871b7795e136be9a},
- {0xb913179899f68584, 0x28e2557b59846e40},
- {0xe757dd7ec07426e5, 0x331aeada2fe589d0},
- {0x9096ea6f3848984f, 0x3ff0d2c85def7622},
- {0xb4bca50b065abe63, 0x0fed077a756b53aa},
- {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895},
- {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d},
- {0xb080392cc4349dec, 0xbd8d794d96aacfb4},
- {0xdca04777f541c567, 0xecf0d7a0fc5583a1},
- {0x89e42caaf9491b60, 0xf41686c49db57245},
- {0xac5d37d5b79b6239, 0x311c2875c522ced6},
- {0xd77485cb25823ac7, 0x7d633293366b828c},
- {0x86a8d39ef77164bc, 0xae5dff9c02033198},
- {0xa8530886b54dbdeb, 0xd9f57f830283fdfd},
- {0xd267caa862a12d66, 0xd072df63c324fd7c},
- {0x8380dea93da4bc60, 0x4247cb9e59f71e6e},
- {0xa46116538d0deb78, 0x52d9be85f074e609},
- {0xcd795be870516656, 0x67902e276c921f8c},
- {0x806bd9714632dff6, 0x00ba1cd8a3db53b7},
- {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5},
- {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce},
- {0xfad2a4b13d1b5d6c, 0x796b805720085f82},
- {0x9cc3a6eec6311a63, 0xcbe3303674053bb1},
- {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d},
- {0xf4f1b4d515acb93b, 0xee92fb5515482d45},
- {0x991711052d8bf3c5, 0x751bdd152d4d1c4b},
- {0xbf5cd54678eef0b6, 0xd262d45a78a0635e},
- {0xef340a98172aace4, 0x86fb897116c87c35},
- {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1},
- {0xbae0a846d2195712, 0x8974836059cca10a},
- {0xe998d258869facd7, 0x2bd1a438703fc94c},
- {0x91ff83775423cc06, 0x7b6306a34627ddd0},
- {0xb67f6455292cbf08, 0x1a3bc84c17b1d543},
- {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94},
- {0x8e938662882af53e, 0x547eb47b7282ee9d},
- {0xb23867fb2a35b28d, 0xe99e619a4f23aa44},
- {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5},
- {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05},
- {0xae0b158b4738705e, 0x9624ab50b148d446},
- {0xd98ddaee19068c76, 0x3badd624dd9b0958},
- {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7},
- {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d},
- {0xd47487cc8470652b, 0x7647c32000696720},
- {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074},
- {0xa5fb0a17c777cf09, 0xf468107100525891},
- {0xcf79cc9db955c2cc, 0x7182148d4066eeb5},
- {0x81ac1fe293d599bf, 0xc6f14cd848405531},
- {0xa21727db38cb002f, 0xb8ada00e5a506a7d},
- {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d},
- {0xfd442e4688bd304a, 0x908f4a166d1da664},
- {0x9e4a9cec15763e2e, 0x9a598e4e043287ff},
- {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe},
- {0xf7549530e188c128, 0xd12bee59e68ef47d},
- {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf},
- {0xc13a148e3032d6e7, 0xe36a52363c1faf02},
- {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2},
- {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba},
- {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8},
- {0xebdf661791d60f56, 0x111b495b3464ad22},
- {0x936b9fcebb25c995, 0xcab10dd900beec35},
- {0xb84687c269ef3bfb, 0x3d5d514f40eea743},
- {0xe65829b3046b0afa, 0x0cb4a5a3112a5113},
- {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac},
- {0xb3f4e093db73a093, 0x59ed216765690f57},
- {0xe0f218b8d25088b8, 0x306869c13ec3532d},
- {0x8c974f7383725573, 0x1e414218c73a13fc},
- {0xafbd2350644eeacf, 0xe5d1929ef90898fb},
- {0xdbac6c247d62a583, 0xdf45f746b74abf3a},
- {0x894bc396ce5da772, 0x6b8bba8c328eb784},
- {0xab9eb47c81f5114f, 0x066ea92f3f326565},
- {0xd686619ba27255a2, 0xc80a537b0efefebe},
- {0x8613fd0145877585, 0xbd06742ce95f5f37},
- {0xa798fc4196e952e7, 0x2c48113823b73705},
- {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6},
- {0x82ef85133de648c4, 0x9a984d73dbe722fc},
- {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb},
- {0xcc963fee10b7d1b3, 0x318df905079926a9},
- {0xffbbcfe994e5c61f, 0xfdf17746497f7053},
- {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634},
- {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1},
- {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1},
- {0x9c1661a651213e2d, 0x06bea10ca65c084f},
- {0xc31bfa0fe5698db8, 0x486e494fcff30a63},
- {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb},
- {0x986ddb5c6b3a76b7, 0xf89629465a75e01d},
- {0xbe89523386091465, 0xf6bbb397f1135824},
- {0xee2ba6c0678b597f, 0x746aa07ded582e2d},
- {0x94db483840b717ef, 0xa8c2a44eb4571cdd},
- {0xba121a4650e4ddeb, 0x92f34d62616ce414},
- {0xe896a0d7e51e1566, 0x77b020baf9c81d18},
- {0x915e2486ef32cd60, 0x0ace1474dc1d122f},
- {0xb5b5ada8aaff80b8, 0x0d819992132456bb},
- {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a},
- {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2},
- {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3},
- {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf},
- {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c},
- {0xad4ab7112eb3929d, 0x86c16c98d2c953c7},
- {0xd89d64d57a607744, 0xe871c7bf077ba8b8},
- {0x87625f056c7c4a8b, 0x11471cd764ad4973},
- {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0},
- {0xd389b47879823479, 0x4aff1d108d4ec2c4},
- {0x843610cb4bf160cb, 0xcedf722a585139bb},
- {0xa54394fe1eedb8fe, 0xc2974eb4ee658829},
- {0xce947a3da6a9273e, 0x733d226229feea33},
- {0x811ccc668829b887, 0x0806357d5a3f5260},
- {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8},
- {0xc9bcff6034c13052, 0xfc89b393dd02f0b6},
- {0xfc2c3f3841f17c67, 0xbbac2078d443ace3},
- {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e},
- {0xc5029163f384a931, 0x0a9e795e65d4df12},
- {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6},
- {0x99ea0196163fa42e, 0x504bced1bf8e4e46},
- {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7},
- {0xf07da27a82c37088, 0x5d767327bb4e5a4d},
- {0x964e858c91ba2655, 0x3a6a07f8d510f870},
- {0xbbe226efb628afea, 0x890489f70a55368c},
- {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f},
- {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e},
- {0xb77ada0617e3bbcb, 0x09ce6ebb40173745},
- {0xe55990879ddcaabd, 0xcc420a6a101d0516},
- {0x8f57fa54c2a9eab6, 0x9fa946824a12232e},
- {0xb32df8e9f3546564, 0x47939822dc96abfa},
- {0xdff9772470297ebd, 0x59787e2b93bc56f8},
- {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b},
- {0xaefae51477a06b03, 0xede622920b6b23f2},
- {0xdab99e59958885c4, 0xe95fab368e45ecee},
- {0x88b402f7fd75539b, 0x11dbcb0218ebb415},
- {0xaae103b5fcd2a881, 0xd652bdc29f26a11a},
- {0xd59944a37c0752a2, 0x4be76d3346f04960},
- {0x857fcae62d8493a5, 0x6f70a4400c562ddc},
- {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953},
- {0xd097ad07a71f26b2, 0x7e2000a41346a7a8},
- {0x825ecc24c873782f, 0x8ed400668c0c28c9},
- {0xa2f67f2dfa90563b, 0x728900802f0f32fb},
- {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba},
- {0xfea126b7d78186bc, 0xe2f610c84987bfa9},
- {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca},
- {0xc6ede63fa05d3143, 0x91503d1c79720dbc},
- {0xf8a95fcf88747d94, 0x75a44c6397ce912b},
- {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb},
- {0xc24452da229b021b, 0xfbe85badce996169},
- {0xf2d56790ab41c2a2, 0xfae27299423fb9c4},
- {0x97c560ba6b0919a5, 0xdccd879fc967d41b},
- {0xbdb6b8e905cb600f, 0x5400e987bbc1c921},
- {0xed246723473e3813, 0x290123e9aab23b69},
- {0x9436c0760c86e30b, 0xf9a0b6720aaf6522},
- {0xb94470938fa89bce, 0xf808e40e8d5b3e6a},
- {0xe7958cb87392c2c2, 0xb60b1d1230b20e05},
- {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3},
- {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4},
- {0xe2280b6c20dd5232, 0x25c6da63c38de1b1},
- {0x8d590723948a535f, 0x579c487e5a38ad0f},
- {0xb0af48ec79ace837, 0x2d835a9df0c6d852},
- {0xdcdb1b2798182244, 0xf8e431456cf88e66},
- {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900},
- {0xac8b2d36eed2dac5, 0xe272467e3d222f40},
- {0xd7adf884aa879177, 0x5b0ed81dcc6abb10},
- {0x86ccbb52ea94baea, 0x98e947129fc2b4ea},
- {0xa87fea27a539e9a5, 0x3f2398d747b36225},
- {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae},
- {0x83a3eeeef9153e89, 0x1953cf68300424ad},
- {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8},
- {0xcdb02555653131b6, 0x3792f412cb06794e},
- {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1},
- {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5},
- {0xc8de047564d20a8b, 0xf245825a5a445276},
- {0xfb158592be068d2e, 0xeed6e2f0f0d56713},
- {0x9ced737bb6c4183d, 0x55464dd69685606c},
- {0xc428d05aa4751e4c, 0xaa97e14c3c26b887},
- {0xf53304714d9265df, 0xd53dd99f4b3066a9},
- {0x993fe2c6d07b7fab, 0xe546a8038efe402a},
- {0xbf8fdb78849a5f96, 0xde98520472bdd034},
- {0xef73d256a5c0f77c, 0x963e66858f6d4441},
- {0x95a8637627989aad, 0xdde7001379a44aa9},
- {0xbb127c53b17ec159, 0x5560c018580d5d53},
- {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7},
- {0x9226712162ab070d, 0xcab3961304ca70e9},
- {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23},
- {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b},
- {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243},
- {0xb267ed1940f1c61c, 0x55f038b237591ed4},
- {0xdf01e85f912e37a3, 0x6b6c46dec52f6689},
- {0x8b61313bbabce2c6, 0x2323ac4b3b3da016},
- {0xae397d8aa96c1b77, 0xabec975e0a0d081b},
- {0xd9c7dced53c72255, 0x96e7bd358c904a22},
- {0x881cea14545c7575, 0x7e50d64177da2e55},
- {0xaa242499697392d2, 0xdde50bd1d5d0b9ea},
- {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865},
- {0x84ec3c97da624ab4, 0xbd5af13bef0b113f},
- {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f},
- {0xcfb11ead453994ba, 0x67de18eda5814af3},
- {0x81ceb32c4b43fcf4, 0x80eacf948770ced8},
- {0xa2425ff75e14fc31, 0xa1258379a94d028e},
- {0xcad2f7f5359a3b3e, 0x096ee45813a04331},
- {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd},
- {0x9e74d1b791e07e48, 0x775ea264cf55347e},
- {0xc612062576589dda, 0x95364afe032a819e},
- {0xf79687aed3eec551, 0x3a83ddbd83f52205},
- {0x9abe14cd44753b52, 0xc4926a9672793543},
- {0xc16d9a0095928a27, 0x75b7053c0f178294},
- {0xf1c90080baf72cb1, 0x5324c68b12dd6339},
- {0x971da05074da7bee, 0xd3f6fc16ebca5e04},
- {0xbce5086492111aea, 0x88f4bb1ca6bcf585},
- {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6},
- {0x9392ee8e921d5d07, 0x3aff322e62439fd0},
- {0xb877aa3236a4b449, 0x09befeb9fad487c3},
- {0xe69594bec44de15b, 0x4c2ebe687989a9b4},
- {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11},
- {0xb424dc35095cd80f, 0x538484c19ef38c95},
- {0xe12e13424bb40e13, 0x2865a5f206b06fba},
- {0x8cbccc096f5088cb, 0xf93f87b7442e45d4},
- {0xafebff0bcb24aafe, 0xf78f69a51539d749},
- {0xdbe6fecebdedd5be, 0xb573440e5a884d1c},
- {0x89705f4136b4a597, 0x31680a88f8953031},
- {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e},
- {0xd6bf94d5e57a42bc, 0x3d32907604691b4d},
- {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110},
- {0xa7c5ac471b478423, 0x0fcf80dc33721d54},
- {0xd1b71758e219652b, 0xd3c36113404ea4a9},
- {0x83126e978d4fdf3b, 0x645a1cac083126ea},
- {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4},
- {0xcccccccccccccccc, 0xcccccccccccccccd},
- {0x8000000000000000, 0x0000000000000000},
- {0xa000000000000000, 0x0000000000000000},
- {0xc800000000000000, 0x0000000000000000},
- {0xfa00000000000000, 0x0000000000000000},
- {0x9c40000000000000, 0x0000000000000000},
- {0xc350000000000000, 0x0000000000000000},
- {0xf424000000000000, 0x0000000000000000},
- {0x9896800000000000, 0x0000000000000000},
- {0xbebc200000000000, 0x0000000000000000},
- {0xee6b280000000000, 0x0000000000000000},
- {0x9502f90000000000, 0x0000000000000000},
- {0xba43b74000000000, 0x0000000000000000},
- {0xe8d4a51000000000, 0x0000000000000000},
- {0x9184e72a00000000, 0x0000000000000000},
- {0xb5e620f480000000, 0x0000000000000000},
- {0xe35fa931a0000000, 0x0000000000000000},
- {0x8e1bc9bf04000000, 0x0000000000000000},
- {0xb1a2bc2ec5000000, 0x0000000000000000},
- {0xde0b6b3a76400000, 0x0000000000000000},
- {0x8ac7230489e80000, 0x0000000000000000},
- {0xad78ebc5ac620000, 0x0000000000000000},
- {0xd8d726b7177a8000, 0x0000000000000000},
- {0x878678326eac9000, 0x0000000000000000},
- {0xa968163f0a57b400, 0x0000000000000000},
- {0xd3c21bcecceda100, 0x0000000000000000},
- {0x84595161401484a0, 0x0000000000000000},
- {0xa56fa5b99019a5c8, 0x0000000000000000},
- {0xcecb8f27f4200f3a, 0x0000000000000000},
- {0x813f3978f8940984, 0x4000000000000000},
- {0xa18f07d736b90be5, 0x5000000000000000},
- {0xc9f2c9cd04674ede, 0xa400000000000000},
- {0xfc6f7c4045812296, 0x4d00000000000000},
- {0x9dc5ada82b70b59d, 0xf020000000000000},
- {0xc5371912364ce305, 0x6c28000000000000},
- {0xf684df56c3e01bc6, 0xc732000000000000},
- {0x9a130b963a6c115c, 0x3c7f400000000000},
- {0xc097ce7bc90715b3, 0x4b9f100000000000},
- {0xf0bdc21abb48db20, 0x1e86d40000000000},
- {0x96769950b50d88f4, 0x1314448000000000},
- {0xbc143fa4e250eb31, 0x17d955a000000000},
- {0xeb194f8e1ae525fd, 0x5dcfab0800000000},
- {0x92efd1b8d0cf37be, 0x5aa1cae500000000},
- {0xb7abc627050305ad, 0xf14a3d9e40000000},
- {0xe596b7b0c643c719, 0x6d9ccd05d0000000},
- {0x8f7e32ce7bea5c6f, 0xe4820023a2000000},
- {0xb35dbf821ae4f38b, 0xdda2802c8a800000},
- {0xe0352f62a19e306e, 0xd50b2037ad200000},
- {0x8c213d9da502de45, 0x4526f422cc340000},
- {0xaf298d050e4395d6, 0x9670b12b7f410000},
- {0xdaf3f04651d47b4c, 0x3c0cdd765f114000},
- {0x88d8762bf324cd0f, 0xa5880a69fb6ac800},
- {0xab0e93b6efee0053, 0x8eea0d047a457a00},
- {0xd5d238a4abe98068, 0x72a4904598d6d880},
- {0x85a36366eb71f041, 0x47a6da2b7f864750},
- {0xa70c3c40a64e6c51, 0x999090b65f67d924},
- {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d},
- {0x82818f1281ed449f, 0xbff8f10e7a8921a4},
- {0xa321f2d7226895c7, 0xaff72d52192b6a0d},
- {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490},
- {0xfee50b7025c36a08, 0x02f236d04753d5b4},
- {0x9f4f2726179a2245, 0x01d762422c946590},
- {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5},
- {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2},
- {0x9b934c3b330c8577, 0x63cc55f49f88eb2f},
- {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb},
- {0xf316271c7fc3908a, 0x8bef464e3945ef7a},
- {0x97edd871cfda3a56, 0x97758bf0e3cbb5ac},
- {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317},
- {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd},
- {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a},
- {0xb975d6b6ee39e436, 0xb3e2fd538e122b44},
- {0xe7d34c64a9c85d44, 0x60dbbca87196b616},
- {0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd},
- {0xb51d13aea4a488dd, 0x6babab6398bdbe41},
- {0xe264589a4dcdab14, 0xc696963c7eed2dd1},
- {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2},
- {0xb0de65388cc8ada8, 0x3b25a55f43294bcb},
- {0xdd15fe86affad912, 0x49ef0eb713f39ebe},
- {0x8a2dbf142dfcc7ab, 0x6e3569326c784337},
- {0xacb92ed9397bf996, 0x49c2c37f07965404},
- {0xd7e77a8f87daf7fb, 0xdc33745ec97be906},
- {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3},
- {0xa8acd7c0222311bc, 0xc40832ea0d68ce0c},
- {0xd2d80db02aabd62b, 0xf50a3fa490c30190},
- {0x83c7088e1aab65db, 0x792667c6da79e0fa},
- {0xa4b8cab1a1563f52, 0x577001b891185938},
- {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86},
- {0x80b05e5ac60b6178, 0x544f8158315b05b4},
- {0xa0dc75f1778e39d6, 0x696361ae3db1c721},
- {0xc913936dd571c84c, 0x03bc3a19cd1e38e9},
- {0xfb5878494ace3a5f, 0x04ab48a04065c723},
- {0x9d174b2dcec0e47b, 0x62eb0d64283f9c76},
- {0xc45d1df942711d9a, 0x3ba5d0bd324f8394},
- {0xf5746577930d6500, 0xca8f44ec7ee36479},
- {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb},
- {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e},
- {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e},
- {0x95d04aee3b80ece5, 0xbba1f1d158724a12},
- {0xbb445da9ca61281f, 0x2a8a6e45ae8edc97},
- {0xea1575143cf97226, 0xf52d09d71a3293bd},
- {0x924d692ca61be758, 0x593c2626705f9c56},
- {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c},
- {0xe498f455c38b997a, 0x0b6dfb9c0f956447},
- {0x8edf98b59a373fec, 0x4724bd4189bd5eac},
- {0xb2977ee300c50fe7, 0x58edec91ec2cb657},
- {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed},
- {0x8b865b215899f46c, 0xbd79e0d20082ee74},
- {0xae67f1e9aec07187, 0xecd8590680a3aa11},
- {0xda01ee641a708de9, 0xe80e6f4820cc9495},
- {0x884134fe908658b2, 0x3109058d147fdcdd},
- {0xaa51823e34a7eede, 0xbd4b46f0599fd415},
- {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a},
- {0x850fadc09923329e, 0x03e2cf6bc604ddb0},
- {0xa6539930bf6bff45, 0x84db8346b786151c},
- {0xcfe87f7cef46ff16, 0xe612641865679a63},
- {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e},
- {0xa26da3999aef7749, 0xe3be5e330f38f09d},
- {0xcb090c8001ab551c, 0x5cadf5bfd3072cc5},
- {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6},
- {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa},
- {0xc646d63501a1511d, 0xb281e1fd541501b8},
- {0xf7d88bc24209a565, 0x1f225a7ca91a4226},
- {0x9ae757596946075f, 0x3375788de9b06958},
- {0xc1a12d2fc3978937, 0x0052d6b1641c83ae},
- {0xf209787bb47d6b84, 0xc0678c5dbd23a49a},
- {0x9745eb4d50ce6332, 0xf840b7ba963646e0},
- {0xbd176620a501fbff, 0xb650e5a93bc3d898},
- {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe},
- {0x93ba47c980e98cdf, 0xc66f336c36b10137},
- {0xb8a8d9bbe123f017, 0xb80b0047445d4184},
- {0xe6d3102ad96cec1d, 0xa60dc059157491e5},
- {0x9043ea1ac7e41392, 0x87c89837ad68db2f},
- {0xb454e4a179dd1877, 0x29babe4598c311fb},
- {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a},
- {0x8ce2529e2734bb1d, 0x1899e4a65f58660c},
- {0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f},
- {0xdc21a1171d42645d, 0x76707543f4fa1f73},
- {0x899504ae72497eba, 0x6a06494a791c53a8},
- {0xabfa45da0edbde69, 0x0487db9d17636892},
- {0xd6f8d7509292d603, 0x45a9d2845d3c42b6},
- {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2},
- {0xa7f26836f282b732, 0x8e6cac7768d7141e},
- {0xd1ef0244af2364ff, 0x3207d795430cd926},
- {0x8335616aed761f1f, 0x7f44e6bd49e807b8},
- {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6},
- {0xcd036837130890a1, 0x36dba887c37a8c0f},
- {0x802221226be55a64, 0xc2494954da2c9789},
- {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c},
- {0xc83553c5c8965d3d, 0x6f92829494e5acc7},
- {0xfa42a8b73abbf48c, 0xcb772339ba1f17f9},
- {0x9c69a97284b578d7, 0xff2a760414536efb},
- {0xc38413cf25e2d70d, 0xfef5138519684aba},
- {0xf46518c2ef5b8cd1, 0x7eb258665fc25d69},
- {0x98bf2f79d5993802, 0xef2f773ffbd97a61},
- {0xbeeefb584aff8603, 0xaafb550ffacfd8fa},
- {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38},
- {0x952ab45cfa97a0b2, 0xdd945a747bf26183},
- {0xba756174393d88df, 0x94f971119aeef9e4},
- {0xe912b9d1478ceb17, 0x7a37cd5601aab85d},
- {0x91abb422ccb812ee, 0xac62e055c10ab33a},
- {0xb616a12b7fe617aa, 0x577b986b314d6009},
- {0xe39c49765fdf9d94, 0xed5a7e85fda0b80b},
- {0x8e41ade9fbebc27d, 0x14588f13be847307},
- {0xb1d219647ae6b31c, 0x596eb2d8ae258fc8},
- {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb},
- {0x8aec23d680043bee, 0x25de7bb9480d5854},
- {0xada72ccc20054ae9, 0xaf561aa79a10ae6a},
- {0xd910f7ff28069da4, 0x1b2ba1518094da04},
- {0x87aa9aff79042286, 0x90fb44d2f05d0842},
- {0xa99541bf57452b28, 0x353a1607ac744a53},
- {0xd3fa922f2d1675f2, 0x42889b8997915ce8},
- {0x847c9b5d7c2e09b7, 0x69956135febada11},
- {0xa59bc234db398c25, 0x43fab9837e699095},
- {0xcf02b2c21207ef2e, 0x94f967e45e03f4bb},
- {0x8161afb94b44f57d, 0x1d1be0eebac278f5},
- {0xa1ba1ba79e1632dc, 0x6462d92a69731732},
- {0xca28a291859bbf93, 0x7d7b8f7503cfdcfe},
- {0xfcb2cb35e702af78, 0x5cda735244c3d43e},
- {0x9defbf01b061adab, 0x3a0888136afa64a7},
- {0xc56baec21c7a1916, 0x088aaa1845b8fdd0},
- {0xf6c69a72a3989f5b, 0x8aad549e57273d45},
- {0x9a3c2087a63f6399, 0x36ac54e2f678864b},
- {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd},
- {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5},
- {0x969eb7c47859e743, 0x9f644ae5a4b1b325},
- {0xbc4665b596706114, 0x873d5d9f0dde1fee},
- {0xeb57ff22fc0c7959, 0xa90cb506d155a7ea},
- {0x9316ff75dd87cbd8, 0x09a7f12442d588f2},
- {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb2f},
- {0xe5d3ef282a242e81, 0x8f1668c8a86da5fa},
- {0x8fa475791a569d10, 0xf96e017d694487bc},
- {0xb38d92d760ec4455, 0x37c981dcc395a9ac},
- {0xe070f78d3927556a, 0x85bbe253f47b1417},
- {0x8c469ab843b89562, 0x93956d7478ccec8e},
- {0xaf58416654a6babb, 0x387ac8d1970027b2},
- {0xdb2e51bfe9d0696a, 0x06997b05fcc0319e},
- {0x88fcf317f22241e2, 0x441fece3bdf81f03},
- {0xab3c2fddeeaad25a, 0xd527e81cad7626c3},
- {0xd60b3bd56a5586f1, 0x8a71e223d8d3b074},
- {0x85c7056562757456, 0xf6872d5667844e49},
- {0xa738c6bebb12d16c, 0xb428f8ac016561db},
- {0xd106f86e69d785c7, 0xe13336d701beba52},
- {0x82a45b450226b39c, 0xecc0024661173473},
- {0xa34d721642b06084, 0x27f002d7f95d0190},
- {0xcc20ce9bd35c78a5, 0x31ec038df7b441f4},
- {0xff290242c83396ce, 0x7e67047175a15271},
- {0x9f79a169bd203e41, 0x0f0062c6e984d386},
- {0xc75809c42c684dd1, 0x52c07b78a3e60868},
- {0xf92e0c3537826145, 0xa7709a56ccdf8a82},
- {0x9bbcc7a142b17ccb, 0x88a66076400bb691},
- {0xc2abf989935ddbfe, 0x6acff893d00ea435},
- {0xf356f7ebf83552fe, 0x0583f6b8c4124d43},
- {0x98165af37b2153de, 0xc3727a337a8b704a},
- {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c},
- {0xeda2ee1c7064130c, 0x1162def06f79df73},
- {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8},
- {0xb9a74a0637ce2ee1, 0x6d953e2bd7173692},
- {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437},
- {0x910ab1d4db9914a0, 0x1d9c9892400a22a2},
- {0xb54d5e4a127f59c8, 0x2503beb6d00cab4b},
- {0xe2a0b5dc971f303a, 0x2e44ae64840fd61d},
- {0x8da471a9de737e24, 0x5ceaecfed289e5d2},
- {0xb10d8e1456105dad, 0x7425a83e872c5f47},
- {0xdd50f1996b947518, 0xd12f124e28f77719},
- {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f},
- {0xace73cbfdc0bfb7b, 0x636cc64d1001550b},
- {0xd8210befd30efa5a, 0x3c47f7e05401aa4e},
- {0x8714a775e3e95c78, 0x65acfaec34810a71},
- {0xa8d9d1535ce3b396, 0x7f1839a741a14d0d},
- {0xd31045a8341ca07c, 0x1ede48111209a050},
- {0x83ea2b892091e44d, 0x934aed0aab460432},
- {0xa4e4b66b68b65d60, 0xf81da84d5617853f},
- {0xce1de40642e3f4b9, 0x36251260ab9d668e},
- {0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019},
- {0xa1075a24e4421730, 0xb24cf65b8612f81f},
- {0xc94930ae1d529cfc, 0xdee033f26797b627},
- {0xfb9b7cd9a4a7443c, 0x169840ef017da3b1},
- {0x9d412e0806e88aa5, 0x8e1f289560ee864e},
- {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2},
- {0xf5b5d7ec8acb58a2, 0xae10af696774b1db},
- {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29},
- {0xbff610b0cc6edd3f, 0x17fd090a58d32af3},
- {0xeff394dcff8a948e, 0xddfc4b4cef07f5b0},
- {0x95f83d0a1fb69cd9, 0x4abdaf101564f98e},
- {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1},
- {0xea53df5fd18d5513, 0x84c86189216dc5ed},
- {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4},
- {0xb7118682dbb66a77, 0x3fbc8c33221dc2a1},
- {0xe4d5e82392a40515, 0x0fabaf3feaa5334a},
- {0x8f05b1163ba6832d, 0x29cb4d87f2a7400e},
- {0xb2c71d5bca9023f8, 0x743e20e9ef511012},
- {0xdf78e4b2bd342cf6, 0x914da9246b255416},
- {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e},
- {0xae9672aba3d0c320, 0xa184ac2473b529b1},
- {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e},
- {0x8865899617fb1871, 0x7e2fa67c7a658892},
- {0xaa7eebfb9df9de8d, 0xddbb901b98feeab7},
- {0xd51ea6fa85785631, 0x552a74227f3ea565},
- {0x8533285c936b35de, 0xd53a88958f87275f},
- {0xa67ff273b8460356, 0x8a892abaf368f137},
- {0xd01fef10a657842c, 0x2d2b7569b0432d85},
- {0x8213f56a67f6b29b, 0x9c3b29620e29fc73},
- {0xa298f2c501f45f42, 0x8349f3ba91b47b8f},
- {0xcb3f2f7642717713, 0x241c70a936219a73},
- {0xfe0efb53d30dd4d7, 0xed238cd383aa0110},
- {0x9ec95d1463e8a506, 0xf4363804324a40aa},
- {0xc67bb4597ce2ce48, 0xb143c6053edcd0d5},
- {0xf81aa16fdc1b81da, 0xdd94b7868e94050a},
- {0x9b10a4e5e9913128, 0xca7cf2b4191c8326},
- {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0},
- {0xf24a01a73cf2dccf, 0xbc633b39673c8cec},
- {0x976e41088617ca01, 0xd5be0503e085d813},
- {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18},
- {0xec9c459d51852ba2, 0xddf8e7d60ed1219e},
- {0x93e1ab8252f33b45, 0xcabb90e5c942b503},
- {0xb8da1662e7b00a17, 0x3d6a751f3b936243},
- {0xe7109bfba19c0c9d, 0x0cc512670a783ad4},
- {0x906a617d450187e2, 0x27fb2b80668b24c5},
- {0xb484f9dc9641e9da, 0xb1f9f660802dedf6},
- {0xe1a63853bbd26451, 0x5e7873f8a0396973},
- {0x8d07e33455637eb2, 0xdb0b487b6423e1e8},
- {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62},
- {0xdc5c5301c56b75f7, 0x7641a140cc7810fb},
- {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d},
- {0xac2820d9623bf429, 0x546345fa9fbdcd44},
- {0xd732290fbacaf133, 0xa97c177947ad4095},
- {0x867f59a9d4bed6c0, 0x49ed8eabcccc485d},
- {0xa81f301449ee8c70, 0x5c68f256bfff5a74},
- {0xd226fc195c6a2f8c, 0x73832eec6fff3111},
- {0x83585d8fd9c25db7, 0xc831fd53c5ff7eab},
- {0xa42e74f3d032f525, 0xba3e7ca8b77f5e55},
- {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb},
- {0x80444b5e7aa7cf85, 0x7980d163cf5b81b3},
- {0xa0555e361951c366, 0xd7e105bcc332621f},
- {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7},
- {0xfa856334878fc150, 0xb14f98f6f0feb951},
- {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3},
- {0xc3b8358109e84f07, 0x0a862f80ec4700c8},
- {0xf4a642e14c6262c8, 0xcd27bb612758c0fa},
- {0x98e7e9cccfbd7dbd, 0x8038d51cb897789c},
- {0xbf21e44003acdd2c, 0xe0470a63e6bd56c3},
- {0xeeea5d5004981478, 0x1858ccfce06cac74},
- {0x95527a5202df0ccb, 0x0f37801e0c43ebc8},
- {0xbaa718e68396cffd, 0xd30560258f54e6ba},
- {0xe950df20247c83fd, 0x47c6b82ef32a2069},
- {0x91d28b7416cdd27e, 0x4cdc331d57fa5441},
- {0xb6472e511c81471d, 0xe0133fe4adf8e952},
- {0xe3d8f9e563a198e5, 0x58180fddd97723a6},
- {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648},
- {0xb201833b35d63f73, 0x2cd2cc6551e513da},
- {0xde81e40a034bcf4f, 0xf8077f7ea65e58d1},
- {0x8b112e86420f6191, 0xfb04afaf27faf782},
- {0xadd57a27d29339f6, 0x79c5db9af1f9b563},
- {0xd94ad8b1c7380874, 0x18375281ae7822bc},
- {0x87cec76f1c830548, 0x8f2293910d0b15b5},
- {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb22},
- {0xd433179d9c8cb841, 0x5fa60692a46151eb},
- {0x849feec281d7f328, 0xdbc7c41ba6bcd333},
- {0xa5c7ea73224deff3, 0x12b9b522906c0800},
- {0xcf39e50feae16bef, 0xd768226b34870a00},
- {0x81842f29f2cce375, 0xe6a1158300d46640},
- {0xa1e53af46f801c53, 0x60495ae3c1097fd0},
- {0xca5e89b18b602368, 0x385bb19cb14bdfc4},
- {0xfcf62c1dee382c42, 0x46729e03dd9ed7b5},
- {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d1},
- {0xc5a05277621be293, 0xc7098b7305241885},
- {0xf70867153aa2db38, 0xb8cbee4fc66d1ea7}
-#else
- {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
- {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
- {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f},
- {0x86a8d39ef77164bc, 0xae5dff9c02033198},
- {0xd98ddaee19068c76, 0x3badd624dd9b0958},
- {0xafbd2350644eeacf, 0xe5d1929ef90898fb},
- {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2},
- {0xe55990879ddcaabd, 0xcc420a6a101d0516},
- {0xb94470938fa89bce, 0xf808e40e8d5b3e6a},
- {0x95a8637627989aad, 0xdde7001379a44aa9},
- {0xf1c90080baf72cb1, 0x5324c68b12dd6339},
- {0xc350000000000000, 0x0000000000000000},
- {0x9dc5ada82b70b59d, 0xf020000000000000},
- {0xfee50b7025c36a08, 0x02f236d04753d5b4},
- {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86},
- {0xa6539930bf6bff45, 0x84db8346b786151c},
- {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2},
- {0xd910f7ff28069da4, 0x1b2ba1518094da04},
- {0xaf58416654a6babb, 0x387ac8d1970027b2},
- {0x8da471a9de737e24, 0x5ceaecfed289e5d2},
- {0xe4d5e82392a40515, 0x0fabaf3feaa5334a},
- {0xb8da1662e7b00a17, 0x3d6a751f3b936243},
- {0x95527a5202df0ccb, 0x0f37801e0c43ebc8}
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wnarrowing"
#endif
+ // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
+ // to significands above.
+ static constexpr int16_t pow10_exponents[87] = {
+ -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
+ -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
+ -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
+ -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
+ -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
+ 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
+ 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
+ 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066};
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+# pragma GCC diagnostic pop
+#endif
+
+ static constexpr uint64_t power_of_10_64[20] = {
+ 1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL),
+ 10000000000000000000ULL};
};
-#if !FMT_USE_FULL_CACHE_DRAGONBOX
-template <typename T>
-const uint64_t basic_data<T>::powers_of_5_64[] = {
- 0x0000000000000001, 0x0000000000000005, 0x0000000000000019,
- 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,
- 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,
- 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd,
- 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9,
- 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5,
- 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631,
- 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed,
- 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9};
+// This is a struct rather than an alias to avoid shadowing warnings in gcc.
+struct impl_data : basic_impl_data<> {};
+#if __cplusplus < 201703L
template <typename T>
-const uint32_t basic_data<T>::dragonbox_pow10_recovery_errors[] = {
- 0x50001400, 0x54044100, 0x54014555, 0x55954415, 0x54115555, 0x00000001,
- 0x50000000, 0x00104000, 0x54010004, 0x05004001, 0x55555544, 0x41545555,
- 0x54040551, 0x15445545, 0x51555514, 0x10000015, 0x00101100, 0x01100015,
- 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04450514, 0x45414110,
- 0x55555145, 0x50544050, 0x15040155, 0x11054140, 0x50111514, 0x11451454,
- 0x00400541, 0x00000000, 0x55555450, 0x10056551, 0x10054011, 0x55551014,
- 0x69514555, 0x05151109, 0x00155555};
+constexpr uint64_t basic_impl_data<T>::pow10_significands[];
+template <typename T> constexpr int16_t basic_impl_data<T>::pow10_exponents[];
+template <typename T> constexpr uint64_t basic_impl_data<T>::power_of_10_64[];
#endif
-template <typename T>
-const char basic_data<T>::foreground_color[] = "\x1b[38;2;";
-template <typename T>
-const char basic_data<T>::background_color[] = "\x1b[48;2;";
-template <typename T> const char basic_data<T>::reset_color[] = "\x1b[0m";
-template <typename T> const wchar_t basic_data<T>::wreset_color[] = L"\x1b[0m";
-template <typename T> const char basic_data<T>::signs[] = {0, '-', '+', ' '};
-template <typename T>
-const char basic_data<T>::left_padding_shifts[] = {31, 31, 0, 1, 0};
-template <typename T>
-const char basic_data<T>::right_padding_shifts[] = {0, 31, 0, 1, 0};
-
template <typename T> struct bits {
static FMT_CONSTEXPR_DECL const int value =
static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
};
-class fp;
-template <int SHIFT = 0> fp normalize(fp value);
-
-// Lower (upper) boundary is a value half way between a floating-point value
-// and its predecessor (successor). Boundaries have the same exponent as the
-// value so only significands are stored.
-struct boundaries {
- uint64_t lower;
- uint64_t upper;
-};
-
-// A handmade floating-point number f * pow(2, e).
-class fp {
- private:
- using significand_type = uint64_t;
-
- template <typename Float>
- using is_supported_float = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
- sizeof(Float) == sizeof(uint32_t)>;
+// Returns the number of significand bits in Float excluding the implicit bit.
+template <typename Float> constexpr int num_significand_bits() {
+ // Subtract 1 to account for an implicit most significant bit in the
+ // normalized form.
+ return std::numeric_limits<Float>::digits - 1;
+}
- public:
- significand_type f;
+// A floating-point number f * pow(2, e).
+struct fp {
+ uint64_t f;
int e;
- // All sizes are in bits.
- // Subtract 1 to account for an implicit most significant bit in the
- // normalized form.
- static FMT_CONSTEXPR_DECL const int double_significand_size =
- std::numeric_limits<double>::digits - 1;
- static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
- 1ULL << double_significand_size;
- static FMT_CONSTEXPR_DECL const int significand_size =
- bits<significand_type>::value;
+ static constexpr const int num_significand_bits = bits<decltype(f)>::value;
- fp() : f(0), e(0) {}
- fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
+ constexpr fp() : f(0), e(0) {}
+ constexpr fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
- // Constructs fp from an IEEE754 double. It is a template to prevent compile
- // errors on platforms where double is not IEEE754.
- template <typename Double> explicit fp(Double d) { assign(d); }
+ // Constructs fp from an IEEE754 floating-point number. It is a template to
+ // prevent compile errors on systems where n is not IEEE754.
+ template <typename Float> explicit FMT_CONSTEXPR fp(Float n) { assign(n); }
+
+ template <typename Float>
+ using is_supported = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
+ sizeof(Float) == sizeof(uint32_t)>;
// Assigns d to this and return true iff predecessor is closer than successor.
- template <typename Float, FMT_ENABLE_IF(is_supported_float<Float>::value)>
- bool assign(Float d) {
+ template <typename Float, FMT_ENABLE_IF(is_supported<Float>::value)>
+ FMT_CONSTEXPR bool assign(Float n) {
// Assume float is in the format [sign][exponent][significand].
- using limits = std::numeric_limits<Float>;
- const int float_significand_size = limits::digits - 1;
- const int exponent_size =
- bits<Float>::value - float_significand_size - 1; // -1 for sign
- const uint64_t float_implicit_bit = 1ULL << float_significand_size;
- const uint64_t significand_mask = float_implicit_bit - 1;
- const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
- const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
+ const int num_float_significand_bits =
+ detail::num_significand_bits<Float>();
+ const uint64_t implicit_bit = 1ULL << num_float_significand_bits;
+ const uint64_t significand_mask = implicit_bit - 1;
constexpr bool is_double = sizeof(Float) == sizeof(uint64_t);
- auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(d);
+ auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(n);
f = u & significand_mask;
+ const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
int biased_e =
- static_cast<int>((u & exponent_mask) >> float_significand_size);
- // Predecessor is closer if d is a normalized power of 2 (f == 0) other than
- // the smallest normalized number (biased_e > 1).
+ static_cast<int>((u & exponent_mask) >> num_float_significand_bits);
+ // The predecessor is closer if n is a normalized power of 2 (f == 0) other
+ // than the smallest normalized number (biased_e > 1).
bool is_predecessor_closer = f == 0 && biased_e > 1;
if (biased_e != 0)
- f += float_implicit_bit;
+ f += implicit_bit;
else
biased_e = 1; // Subnormals use biased exponent 1 (min exponent).
- e = biased_e - exponent_bias - float_significand_size;
+ const int exponent_bias = std::numeric_limits<Float>::max_exponent - 1;
+ e = biased_e - exponent_bias - num_float_significand_bits;
return is_predecessor_closer;
}
- template <typename Float, FMT_ENABLE_IF(!is_supported_float<Float>::value)>
+ template <typename Float, FMT_ENABLE_IF(!is_supported<Float>::value)>
bool assign(Float) {
- *this = fp();
+ FMT_ASSERT(false, "");
return false;
}
};
// Normalizes the value converted from double and multiplied by (1 << SHIFT).
-template <int SHIFT> fp normalize(fp value) {
+template <int SHIFT = 0> FMT_CONSTEXPR fp normalize(fp value) {
// Handle subnormals.
- const auto shifted_implicit_bit = fp::implicit_bit << SHIFT;
+ const uint64_t implicit_bit = 1ULL << num_significand_bits<double>();
+ const auto shifted_implicit_bit = implicit_bit << SHIFT;
while ((value.f & shifted_implicit_bit) == 0) {
value.f <<= 1;
--value.e;
}
// Subtract 1 to account for hidden bit.
const auto offset =
- fp::significand_size - fp::double_significand_size - SHIFT - 1;
+ fp::num_significand_bits - num_significand_bits<double>() - SHIFT - 1;
value.f <<= offset;
value.e -= offset;
return value;
inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; }
// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
-inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
+FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
#if FMT_USE_INT128
auto product = static_cast<__uint128_t>(lhs) * rhs;
auto f = static_cast<uint64_t>(product >> 64);
#endif
}
-inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; }
+FMT_CONSTEXPR inline fp operator*(fp x, fp y) {
+ return {multiply(x.f, y.f), x.e + y.e + 64};
+}
// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
-inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
+FMT_CONSTEXPR inline fp get_cached_power(int min_exponent,
+ int& pow10_exponent) {
const int shift = 32;
- const auto significand = static_cast<int64_t>(data::log10_2_significand);
+ const auto significand = static_cast<int64_t>(log10_2_significand);
int index = static_cast<int>(
- ((min_exponent + fp::significand_size - 1) * (significand >> shift) +
+ ((min_exponent + fp::num_significand_bits - 1) * (significand >> shift) +
((int64_t(1) << shift) - 1)) // ceil
>> 32 // arithmetic shift
);
const int dec_exp_step = 8;
index = (index - first_dec_exp - 1) / dec_exp_step + 1;
pow10_exponent = first_dec_exp + index * dec_exp_step;
- return {data::grisu_pow10_significands[index],
- data::grisu_pow10_exponents[index]};
+ return {impl_data::pow10_significands[index],
+ impl_data::pow10_exponents[index]};
}
// A simple accumulator to hold the sums of terms in bigint::square if uint128_t
uint64_t lower;
uint64_t upper;
- accumulator() : lower(0), upper(0) {}
- explicit operator uint32_t() const { return static_cast<uint32_t>(lower); }
+ constexpr accumulator() : lower(0), upper(0) {}
+ constexpr explicit operator uint32_t() const {
+ return static_cast<uint32_t>(lower);
+ }
- void operator+=(uint64_t n) {
+ FMT_CONSTEXPR void operator+=(uint64_t n) {
lower += n;
if (lower < n) ++upper;
}
- void operator>>=(int shift) {
- assert(shift == 32);
+ FMT_CONSTEXPR void operator>>=(int shift) {
+ FMT_ASSERT(shift == 32, "");
(void)shift;
lower = (upper << 32) | (lower >> 32);
upper >>= 32;
basic_memory_buffer<bigit, bigits_capacity> bigits_;
int exp_;
- bigit operator[](int index) const { return bigits_[to_unsigned(index)]; }
- bigit& operator[](int index) { return bigits_[to_unsigned(index)]; }
+ FMT_CONSTEXPR20 bigit operator[](int index) const {
+ return bigits_[to_unsigned(index)];
+ }
+ FMT_CONSTEXPR20 bigit& operator[](int index) {
+ return bigits_[to_unsigned(index)];
+ }
static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
friend struct formatter<bigint>;
- void subtract_bigits(int index, bigit other, bigit& borrow) {
+ FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) {
auto result = static_cast<double_bigit>((*this)[index]) - other - borrow;
(*this)[index] = static_cast<bigit>(result);
borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
}
- void remove_leading_zeros() {
+ FMT_CONSTEXPR20 void remove_leading_zeros() {
int num_bigits = static_cast<int>(bigits_.size()) - 1;
while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits;
bigits_.resize(to_unsigned(num_bigits + 1));
}
// Computes *this -= other assuming aligned bigints and *this >= other.
- void subtract_aligned(const bigint& other) {
+ FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) {
FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
FMT_ASSERT(compare(*this, other) >= 0, "");
bigit borrow = 0;
remove_leading_zeros();
}
- void multiply(uint32_t value) {
+ FMT_CONSTEXPR20 void multiply(uint32_t value) {
const double_bigit wide_value = value;
bigit carry = 0;
for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
if (carry != 0) bigits_.push_back(carry);
}
- void multiply(uint64_t value) {
+ FMT_CONSTEXPR20 void multiply(uint64_t value) {
const bigit mask = ~bigit(0);
const double_bigit lower = value & mask;
const double_bigit upper = value >> bigit_bits;
}
public:
- bigint() : exp_(0) {}
+ FMT_CONSTEXPR20 bigint() : exp_(0) {}
explicit bigint(uint64_t n) { assign(n); }
- ~bigint() { assert(bigits_.capacity() <= bigits_capacity); }
+ FMT_CONSTEXPR20 ~bigint() {
+ FMT_ASSERT(bigits_.capacity() <= bigits_capacity, "");
+ }
bigint(const bigint&) = delete;
void operator=(const bigint&) = delete;
- void assign(const bigint& other) {
+ FMT_CONSTEXPR20 void assign(const bigint& other) {
auto size = other.bigits_.size();
bigits_.resize(size);
auto data = other.bigits_.data();
exp_ = other.exp_;
}
- void assign(uint64_t n) {
+ FMT_CONSTEXPR20 void assign(uint64_t n) {
size_t num_bigits = 0;
do {
bigits_[num_bigits++] = n & ~bigit(0);
exp_ = 0;
}
- int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
+ FMT_CONSTEXPR20 int num_bigits() const {
+ return static_cast<int>(bigits_.size()) + exp_;
+ }
- FMT_NOINLINE bigint& operator<<=(int shift) {
- assert(shift >= 0);
+ FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) {
+ FMT_ASSERT(shift >= 0, "");
exp_ += shift / bigit_bits;
shift %= bigit_bits;
if (shift == 0) return *this;
return *this;
}
- template <typename Int> bigint& operator*=(Int value) {
+ template <typename Int> FMT_CONSTEXPR20 bigint& operator*=(Int value) {
FMT_ASSERT(value > 0, "");
multiply(uint32_or_64_or_128_t<Int>(value));
return *this;
}
- friend int compare(const bigint& lhs, const bigint& rhs) {
+ friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) {
int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits();
if (num_lhs_bigits != num_rhs_bigits)
return num_lhs_bigits > num_rhs_bigits ? 1 : -1;
}
// Returns compare(lhs1 + lhs2, rhs).
- friend int add_compare(const bigint& lhs1, const bigint& lhs2,
- const bigint& rhs) {
+ friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2,
+ const bigint& rhs) {
int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits());
int num_rhs_bigits = rhs.num_bigits();
if (max_lhs_bigits + 1 < num_rhs_bigits) return -1;
}
// Assigns pow(10, exp) to this bigint.
- void assign_pow10(int exp) {
- assert(exp >= 0);
+ FMT_CONSTEXPR20 void assign_pow10(int exp) {
+ FMT_ASSERT(exp >= 0, "");
if (exp == 0) return assign(1);
// Find the top bit.
int bitmask = 1;
*this <<= exp; // Multiply by pow(2, exp) by shifting.
}
- void square() {
- basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
+ FMT_CONSTEXPR20 void square() {
int num_bigits = static_cast<int>(bigits_.size());
int num_result_bigits = 2 * num_bigits;
+ basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
bigits_.resize(to_unsigned(num_result_bigits));
using accumulator_t = conditional_t<FMT_USE_INT128, uint128_t, accumulator>;
auto sum = accumulator_t();
(*this)[bigit_index] = static_cast<bigit>(sum);
sum >>= bits<bigit>::value;
}
- --num_result_bigits;
remove_leading_zeros();
exp_ *= 2;
}
// If this bigint has a bigger exponent than other, adds trailing zero to make
// exponents equal. This simplifies some operations such as subtraction.
- void align(const bigint& other) {
+ FMT_CONSTEXPR20 void align(const bigint& other) {
int exp_difference = exp_ - other.exp_;
if (exp_difference <= 0) return;
int num_bigits = static_cast<int>(bigits_.size());
// Divides this bignum by divisor, assigning the remainder to this and
// returning the quotient.
- int divmod_assign(const bigint& divisor) {
+ FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) {
FMT_ASSERT(this != &divisor, "");
if (compare(*this, divisor) < 0) return 0;
FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, "");
// some number v and the error, returns whether v should be rounded up, down, or
// whether the rounding direction can't be determined due to error.
// error should be less than divisor / 2.
-inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder,
- uint64_t error) {
+FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor,
+ uint64_t remainder,
+ uint64_t error) {
FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow.
FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow.
FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow.
};
}
+struct gen_digits_handler {
+ char* buf;
+ int size;
+ int precision;
+ int exp10;
+ bool fixed;
+
+ FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor,
+ uint64_t remainder, uint64_t error,
+ bool integral) {
+ FMT_ASSERT(remainder < divisor, "");
+ buf[size++] = digit;
+ if (!integral && error >= remainder) return digits::error;
+ if (size < precision) return digits::more;
+ if (!integral) {
+ // Check if error * 2 < divisor with overflow prevention.
+ // The check is not needed for the integral part because error = 1
+ // and divisor > (1 << 32) there.
+ if (error >= divisor || error >= divisor - error) return digits::error;
+ } else {
+ FMT_ASSERT(error == 1 && divisor > 2, "");
+ }
+ auto dir = get_round_direction(divisor, remainder, error);
+ if (dir != round_direction::up)
+ return dir == round_direction::down ? digits::done : digits::error;
+ ++buf[size - 1];
+ for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
+ buf[i] = '0';
+ ++buf[i - 1];
+ }
+ if (buf[0] > '9') {
+ buf[0] = '1';
+ if (fixed)
+ buf[size++] = '0';
+ else
+ ++exp10;
+ }
+ return digits::done;
+ }
+};
+
// Generates output using the Grisu digit-gen algorithm.
// error: the size of the region (lower, upper) outside of which numbers
// definitely do not round to value (Delta in Grisu3).
-template <typename Handler>
-FMT_ALWAYS_INLINE digits::result grisu_gen_digits(fp value, uint64_t error,
- int& exp, Handler& handler) {
+FMT_INLINE FMT_CONSTEXPR20 digits::result grisu_gen_digits(
+ fp value, uint64_t error, int& exp, gen_digits_handler& handler) {
const fp one(1ULL << -value.e, value.e);
// The integral part of scaled value (p1 in Grisu) = value / one. It cannot be
// zero because it contains a product of two 64-bit numbers with MSB set (due
// The fractional part of scaled value (p2 in Grisu) c = value % one.
uint64_t fractional = value.f & (one.f - 1);
exp = count_digits(integral); // kappa in Grisu.
- // Divide by 10 to prevent overflow.
- auto result = handler.on_start(data::powers_of_10_64[exp - 1] << -one.e,
- value.f / 10, error * 10, exp);
- if (result != digits::more) return result;
+ // Non-fixed formats require at least one digit and no precision adjustment.
+ if (handler.fixed) {
+ // Adjust fixed precision by exponent because it is relative to decimal
+ // point.
+ int precision_offset = exp + handler.exp10;
+ if (precision_offset > 0 &&
+ handler.precision > max_value<int>() - precision_offset) {
+ FMT_THROW(format_error("number is too big"));
+ }
+ handler.precision += precision_offset;
+ // Check if precision is satisfied just by leading zeros, e.g.
+ // format("{:.2f}", 0.001) gives "0.00" without generating any digits.
+ if (handler.precision <= 0) {
+ if (handler.precision < 0) return digits::done;
+ // Divide by 10 to prevent overflow.
+ uint64_t divisor = impl_data::power_of_10_64[exp - 1] << -one.e;
+ auto dir = get_round_direction(divisor, value.f / 10, error * 10);
+ if (dir == round_direction::unknown) return digits::error;
+ handler.buf[handler.size++] = dir == round_direction::up ? '1' : '0';
+ return digits::done;
+ }
+ }
// Generate digits for the integral part. This can produce up to 10 digits.
do {
uint32_t digit = 0;
}
--exp;
auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional;
- result = handler.on_digit(static_cast<char>('0' + digit),
- data::powers_of_10_64[exp] << -one.e, remainder,
- error, exp, true);
+ auto result = handler.on_digit(static_cast<char>('0' + digit),
+ impl_data::power_of_10_64[exp] << -one.e,
+ remainder, error, true);
if (result != digits::more) return result;
} while (exp > 0);
// Generate digits for the fractional part.
char digit = static_cast<char>('0' + (fractional >> -one.e));
fractional &= one.f - 1;
--exp;
- result = handler.on_digit(digit, one.f, fractional, error, exp, false);
+ auto result = handler.on_digit(digit, one.f, fractional, error, false);
if (result != digits::more) return result;
}
}
-// The fixed precision digit handler.
-struct fixed_handler {
- char* buf;
- int size;
- int precision;
- int exp10;
- bool fixed;
+// A 128-bit integer type used internally,
+struct uint128_wrapper {
+ uint128_wrapper() = default;
- digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error,
- int& exp) {
- // Non-fixed formats require at least one digit and no precision adjustment.
- if (!fixed) return digits::more;
- // Adjust fixed precision by exponent because it is relative to decimal
- // point.
- precision += exp + exp10;
- // Check if precision is satisfied just by leading zeros, e.g.
- // format("{:.2f}", 0.001) gives "0.00" without generating any digits.
- if (precision > 0) return digits::more;
- if (precision < 0) return digits::done;
- auto dir = get_round_direction(divisor, remainder, error);
- if (dir == round_direction::unknown) return digits::error;
- buf[size++] = dir == round_direction::up ? '1' : '0';
- return digits::done;
+#if FMT_USE_INT128
+ uint128_t internal_;
+
+ constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT
+ : internal_{static_cast<uint128_t>(low) |
+ (static_cast<uint128_t>(high) << 64)} {}
+
+ constexpr uint128_wrapper(uint128_t u) : internal_{u} {}
+
+ constexpr uint64_t high() const FMT_NOEXCEPT {
+ return uint64_t(internal_ >> 64);
}
+ constexpr uint64_t low() const FMT_NOEXCEPT { return uint64_t(internal_); }
- digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder,
- uint64_t error, int, bool integral) {
- FMT_ASSERT(remainder < divisor, "");
- buf[size++] = digit;
- if (!integral && error >= remainder) return digits::error;
- if (size < precision) return digits::more;
- if (!integral) {
- // Check if error * 2 < divisor with overflow prevention.
- // The check is not needed for the integral part because error = 1
- // and divisor > (1 << 32) there.
- if (error >= divisor || error >= divisor - error) return digits::error;
- } else {
- FMT_ASSERT(error == 1 && divisor > 2, "");
- }
- auto dir = get_round_direction(divisor, remainder, error);
- if (dir != round_direction::up)
- return dir == round_direction::down ? digits::done : digits::error;
- ++buf[size - 1];
- for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
- buf[i] = '0';
- ++buf[i - 1];
- }
- if (buf[0] > '9') {
- buf[0] = '1';
- if (fixed)
- buf[size++] = '0';
- else
- ++exp10;
- }
- return digits::done;
+ uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT {
+ internal_ += n;
+ return *this;
}
+#else
+ uint64_t high_;
+ uint64_t low_;
+
+ constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT
+ : high_{high},
+ low_{low} {}
+
+ constexpr uint64_t high() const FMT_NOEXCEPT { return high_; }
+ constexpr uint64_t low() const FMT_NOEXCEPT { return low_; }
+
+ uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT {
+# if defined(_MSC_VER) && defined(_M_X64)
+ unsigned char carry = _addcarry_u64(0, low_, n, &low_);
+ _addcarry_u64(carry, high_, 0, &high_);
+ return *this;
+# else
+ uint64_t sum = low_ + n;
+ high_ += (sum < low_ ? 1 : 0);
+ low_ = sum;
+ return *this;
+# endif
+ }
+#endif
};
// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox.
namespace dragonbox {
// Computes 128-bit result of multiplication of two 64-bit unsigned integers.
-FMT_SAFEBUFFERS inline uint128_wrapper umul128(uint64_t x,
- uint64_t y) FMT_NOEXCEPT {
+inline uint128_wrapper umul128(uint64_t x, uint64_t y) FMT_NOEXCEPT {
#if FMT_USE_INT128
return static_cast<uint128_t>(x) * static_cast<uint128_t>(y);
#elif defined(_MSC_VER) && defined(_M_X64)
}
// Computes upper 64 bits of multiplication of two 64-bit unsigned integers.
-FMT_SAFEBUFFERS inline uint64_t umul128_upper64(uint64_t x,
- uint64_t y) FMT_NOEXCEPT {
+inline uint64_t umul128_upper64(uint64_t x, uint64_t y) FMT_NOEXCEPT {
#if FMT_USE_INT128
auto p = static_cast<uint128_t>(x) * static_cast<uint128_t>(y);
return static_cast<uint64_t>(p >> 64);
// Computes upper 64 bits of multiplication of a 64-bit unsigned integer and a
// 128-bit unsigned integer.
-FMT_SAFEBUFFERS inline uint64_t umul192_upper64(uint64_t x, uint128_wrapper y)
- FMT_NOEXCEPT {
+inline uint64_t umul192_upper64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT {
uint128_wrapper g0 = umul128(x, y.high());
g0 += umul128_upper64(x, y.low());
return g0.high();
// Computes middle 64 bits of multiplication of a 64-bit unsigned integer and a
// 128-bit unsigned integer.
-FMT_SAFEBUFFERS inline uint64_t umul192_middle64(uint64_t x, uint128_wrapper y)
- FMT_NOEXCEPT {
+inline uint64_t umul192_middle64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT {
uint64_t g01 = x * y.high();
uint64_t g10 = umul128_upper64(x, y.low());
return g01 + g10;
inline int floor_log10_pow2(int e) FMT_NOEXCEPT {
FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
const int shift = 22;
- return (e * static_cast<int>(data::log10_2_significand >> (64 - shift))) >>
- shift;
+ return (e * static_cast<int>(log10_2_significand >> (64 - shift))) >> shift;
}
// Various fast log computations.
FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375;
const int shift_amount = 22;
- return (e * static_cast<int>(data::log10_2_significand >>
- (64 - shift_amount)) -
+ return (e * static_cast<int>(log10_2_significand >> (64 - shift_amount)) -
static_cast<int>(log10_4_over_3_fractional_digits >>
(64 - shift_amount))) >>
shift_amount;
#endif
}
+// Table entry type for divisibility test.
+template <typename T> struct divtest_table_entry {
+ T mod_inv;
+ T max_quotient;
+};
+
// Returns true iff x is divisible by pow(5, exp).
inline bool divisible_by_power_of_5(uint32_t x, int exp) FMT_NOEXCEPT {
FMT_ASSERT(exp <= 10, "too large exponent");
- return x * data::divtest_table_for_pow5_32[exp].mod_inv <=
- data::divtest_table_for_pow5_32[exp].max_quotient;
+ static constexpr const divtest_table_entry<uint32_t> divtest_table[] = {
+ {0x00000001, 0xffffffff}, {0xcccccccd, 0x33333333},
+ {0xc28f5c29, 0x0a3d70a3}, {0x26e978d5, 0x020c49ba},
+ {0x3afb7e91, 0x0068db8b}, {0x0bcbe61d, 0x0014f8b5},
+ {0x68c26139, 0x000431bd}, {0xae8d46a5, 0x0000d6bf},
+ {0x22e90e21, 0x00002af3}, {0x3a2e9c6d, 0x00000897},
+ {0x3ed61f49, 0x000001b7}};
+ return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient;
}
inline bool divisible_by_power_of_5(uint64_t x, int exp) FMT_NOEXCEPT {
FMT_ASSERT(exp <= 23, "too large exponent");
- return x * data::divtest_table_for_pow5_64[exp].mod_inv <=
- data::divtest_table_for_pow5_64[exp].max_quotient;
+ static constexpr const divtest_table_entry<uint64_t> divtest_table[] = {
+ {0x0000000000000001, 0xffffffffffffffff},
+ {0xcccccccccccccccd, 0x3333333333333333},
+ {0x8f5c28f5c28f5c29, 0x0a3d70a3d70a3d70},
+ {0x1cac083126e978d5, 0x020c49ba5e353f7c},
+ {0xd288ce703afb7e91, 0x0068db8bac710cb2},
+ {0x5d4e8fb00bcbe61d, 0x0014f8b588e368f0},
+ {0x790fb65668c26139, 0x000431bde82d7b63},
+ {0xe5032477ae8d46a5, 0x0000d6bf94d5e57a},
+ {0xc767074b22e90e21, 0x00002af31dc46118},
+ {0x8e47ce423a2e9c6d, 0x0000089705f4136b},
+ {0x4fa7f60d3ed61f49, 0x000001b7cdfd9d7b},
+ {0x0fee64690c913975, 0x00000057f5ff85e5},
+ {0x3662e0e1cf503eb1, 0x000000119799812d},
+ {0xa47a2cf9f6433fbd, 0x0000000384b84d09},
+ {0x54186f653140a659, 0x00000000b424dc35},
+ {0x7738164770402145, 0x0000000024075f3d},
+ {0xe4a4d1417cd9a041, 0x000000000734aca5},
+ {0xc75429d9e5c5200d, 0x000000000170ef54},
+ {0xc1773b91fac10669, 0x000000000049c977},
+ {0x26b172506559ce15, 0x00000000000ec1e4},
+ {0xd489e3a9addec2d1, 0x000000000002f394},
+ {0x90e860bb892c8d5d, 0x000000000000971d},
+ {0x502e79bf1b6f4f79, 0x0000000000001e39},
+ {0xdcd618596be30fe5, 0x000000000000060b}};
+ return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient;
}
// Replaces n by floor(n / pow(5, N)) returning true if and only if n is
static uint64_t get_cached_power(int k) FMT_NOEXCEPT {
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
"k is out of range");
- return data::dragonbox_pow10_significands_64[k - float_info<float>::min_k];
+ static constexpr const uint64_t pow10_significands[] = {
+ 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
+ 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
+ 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
+ 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb,
+ 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a,
+ 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810,
+ 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff,
+ 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd,
+ 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424,
+ 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b,
+ 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000,
+ 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000,
+ 0xc350000000000000, 0xf424000000000000, 0x9896800000000000,
+ 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000,
+ 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000,
+ 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000,
+ 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000,
+ 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000,
+ 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0,
+ 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940984,
+ 0xa18f07d736b90be5, 0xc9f2c9cd04674ede, 0xfc6f7c4045812296,
+ 0x9dc5ada82b70b59d, 0xc5371912364ce305, 0xf684df56c3e01bc6,
+ 0x9a130b963a6c115c, 0xc097ce7bc90715b3, 0xf0bdc21abb48db20,
+ 0x96769950b50d88f4, 0xbc143fa4e250eb31, 0xeb194f8e1ae525fd,
+ 0x92efd1b8d0cf37be, 0xb7abc627050305ad, 0xe596b7b0c643c719,
+ 0x8f7e32ce7bea5c6f, 0xb35dbf821ae4f38b, 0xe0352f62a19e306e};
+ return pow10_significands[k - float_info<float>::min_k];
}
static carrier_uint compute_mul(carrier_uint u,
FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
"k is out of range");
+ static constexpr const uint128_wrapper pow10_significands[] = {
+#if FMT_USE_FULL_CACHE_DRAGONBOX
+ {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
+ {0x9faacf3df73609b1, 0x77b191618c54e9ad},
+ {0xc795830d75038c1d, 0xd59df5b9ef6a2418},
+ {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e},
+ {0x9becce62836ac577, 0x4ee367f9430aec33},
+ {0xc2e801fb244576d5, 0x229c41f793cda740},
+ {0xf3a20279ed56d48a, 0x6b43527578c11110},
+ {0x9845418c345644d6, 0x830a13896b78aaaa},
+ {0xbe5691ef416bd60c, 0x23cc986bc656d554},
+ {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9},
+ {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa},
+ {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54},
+ {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69},
+ {0x91376c36d99995be, 0x23100809b9c21fa2},
+ {0xb58547448ffffb2d, 0xabd40a0c2832a78b},
+ {0xe2e69915b3fff9f9, 0x16c90c8f323f516d},
+ {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4},
+ {0xb1442798f49ffb4a, 0x99cd11cfdf41779d},
+ {0xdd95317f31c7fa1d, 0x40405643d711d584},
+ {0x8a7d3eef7f1cfc52, 0x482835ea666b2573},
+ {0xad1c8eab5ee43b66, 0xda3243650005eed0},
+ {0xd863b256369d4a40, 0x90bed43e40076a83},
+ {0x873e4f75e2224e68, 0x5a7744a6e804a292},
+ {0xa90de3535aaae202, 0x711515d0a205cb37},
+ {0xd3515c2831559a83, 0x0d5a5b44ca873e04},
+ {0x8412d9991ed58091, 0xe858790afe9486c3},
+ {0xa5178fff668ae0b6, 0x626e974dbe39a873},
+ {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
+ {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a},
+ {0xa139029f6a239f72, 0x1c1fffc1ebc44e81},
+ {0xc987434744ac874e, 0xa327ffb266b56221},
+ {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9},
+ {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa},
+ {0xc4ce17b399107c22, 0xcb550fb4384d21d4},
+ {0xf6019da07f549b2b, 0x7e2a53a146606a49},
+ {0x99c102844f94e0fb, 0x2eda7444cbfc426e},
+ {0xc0314325637a1939, 0xfa911155fefb5309},
+ {0xf03d93eebc589f88, 0x793555ab7eba27cb},
+ {0x96267c7535b763b5, 0x4bc1558b2f3458df},
+ {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17},
+ {0xea9c227723ee8bcb, 0x465e15a979c1cadd},
+ {0x92a1958a7675175f, 0x0bfacd89ec191eca},
+ {0xb749faed14125d36, 0xcef980ec671f667c},
+ {0xe51c79a85916f484, 0x82b7e12780e7401b},
+ {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811},
+ {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16},
+ {0xdfbdcece67006ac9, 0x67a791e093e1d49b},
+ {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1},
+ {0xaecc49914078536d, 0x58fae9f773886e19},
+ {0xda7f5bf590966848, 0xaf39a475506a899f},
+ {0x888f99797a5e012d, 0x6d8406c952429604},
+ {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84},
+ {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65},
+ {0x855c3be0a17fcd26, 0x5cf2eea09a550680},
+ {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f},
+ {0xd0601d8efc57b08b, 0xf13b94daf124da27},
+ {0x823c12795db6ce57, 0x76c53d08d6b70859},
+ {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f},
+ {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a},
+ {0xfe5d54150b090b02, 0xd3f93b35435d7c4d},
+ {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0},
+ {0xc6b8e9b0709f109a, 0x359ab6419ca1091c},
+ {0xf867241c8cc6d4c0, 0xc30163d203c94b63},
+ {0x9b407691d7fc44f8, 0x79e0de63425dcf1e},
+ {0xc21094364dfb5636, 0x985915fc12f542e5},
+ {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e},
+ {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43},
+ {0xbd8430bd08277231, 0x50c6ff782a838354},
+ {0xece53cec4a314ebd, 0xa4f8bf5635246429},
+ {0x940f4613ae5ed136, 0x871b7795e136be9a},
+ {0xb913179899f68584, 0x28e2557b59846e40},
+ {0xe757dd7ec07426e5, 0x331aeada2fe589d0},
+ {0x9096ea6f3848984f, 0x3ff0d2c85def7622},
+ {0xb4bca50b065abe63, 0x0fed077a756b53aa},
+ {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895},
+ {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d},
+ {0xb080392cc4349dec, 0xbd8d794d96aacfb4},
+ {0xdca04777f541c567, 0xecf0d7a0fc5583a1},
+ {0x89e42caaf9491b60, 0xf41686c49db57245},
+ {0xac5d37d5b79b6239, 0x311c2875c522ced6},
+ {0xd77485cb25823ac7, 0x7d633293366b828c},
+ {0x86a8d39ef77164bc, 0xae5dff9c02033198},
+ {0xa8530886b54dbdeb, 0xd9f57f830283fdfd},
+ {0xd267caa862a12d66, 0xd072df63c324fd7c},
+ {0x8380dea93da4bc60, 0x4247cb9e59f71e6e},
+ {0xa46116538d0deb78, 0x52d9be85f074e609},
+ {0xcd795be870516656, 0x67902e276c921f8c},
+ {0x806bd9714632dff6, 0x00ba1cd8a3db53b7},
+ {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5},
+ {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce},
+ {0xfad2a4b13d1b5d6c, 0x796b805720085f82},
+ {0x9cc3a6eec6311a63, 0xcbe3303674053bb1},
+ {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d},
+ {0xf4f1b4d515acb93b, 0xee92fb5515482d45},
+ {0x991711052d8bf3c5, 0x751bdd152d4d1c4b},
+ {0xbf5cd54678eef0b6, 0xd262d45a78a0635e},
+ {0xef340a98172aace4, 0x86fb897116c87c35},
+ {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1},
+ {0xbae0a846d2195712, 0x8974836059cca10a},
+ {0xe998d258869facd7, 0x2bd1a438703fc94c},
+ {0x91ff83775423cc06, 0x7b6306a34627ddd0},
+ {0xb67f6455292cbf08, 0x1a3bc84c17b1d543},
+ {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94},
+ {0x8e938662882af53e, 0x547eb47b7282ee9d},
+ {0xb23867fb2a35b28d, 0xe99e619a4f23aa44},
+ {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5},
+ {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05},
+ {0xae0b158b4738705e, 0x9624ab50b148d446},
+ {0xd98ddaee19068c76, 0x3badd624dd9b0958},
+ {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7},
+ {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d},
+ {0xd47487cc8470652b, 0x7647c32000696720},
+ {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074},
+ {0xa5fb0a17c777cf09, 0xf468107100525891},
+ {0xcf79cc9db955c2cc, 0x7182148d4066eeb5},
+ {0x81ac1fe293d599bf, 0xc6f14cd848405531},
+ {0xa21727db38cb002f, 0xb8ada00e5a506a7d},
+ {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d},
+ {0xfd442e4688bd304a, 0x908f4a166d1da664},
+ {0x9e4a9cec15763e2e, 0x9a598e4e043287ff},
+ {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe},
+ {0xf7549530e188c128, 0xd12bee59e68ef47d},
+ {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf},
+ {0xc13a148e3032d6e7, 0xe36a52363c1faf02},
+ {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2},
+ {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba},
+ {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8},
+ {0xebdf661791d60f56, 0x111b495b3464ad22},
+ {0x936b9fcebb25c995, 0xcab10dd900beec35},
+ {0xb84687c269ef3bfb, 0x3d5d514f40eea743},
+ {0xe65829b3046b0afa, 0x0cb4a5a3112a5113},
+ {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac},
+ {0xb3f4e093db73a093, 0x59ed216765690f57},
+ {0xe0f218b8d25088b8, 0x306869c13ec3532d},
+ {0x8c974f7383725573, 0x1e414218c73a13fc},
+ {0xafbd2350644eeacf, 0xe5d1929ef90898fb},
+ {0xdbac6c247d62a583, 0xdf45f746b74abf3a},
+ {0x894bc396ce5da772, 0x6b8bba8c328eb784},
+ {0xab9eb47c81f5114f, 0x066ea92f3f326565},
+ {0xd686619ba27255a2, 0xc80a537b0efefebe},
+ {0x8613fd0145877585, 0xbd06742ce95f5f37},
+ {0xa798fc4196e952e7, 0x2c48113823b73705},
+ {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6},
+ {0x82ef85133de648c4, 0x9a984d73dbe722fc},
+ {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb},
+ {0xcc963fee10b7d1b3, 0x318df905079926a9},
+ {0xffbbcfe994e5c61f, 0xfdf17746497f7053},
+ {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634},
+ {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1},
+ {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1},
+ {0x9c1661a651213e2d, 0x06bea10ca65c084f},
+ {0xc31bfa0fe5698db8, 0x486e494fcff30a63},
+ {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb},
+ {0x986ddb5c6b3a76b7, 0xf89629465a75e01d},
+ {0xbe89523386091465, 0xf6bbb397f1135824},
+ {0xee2ba6c0678b597f, 0x746aa07ded582e2d},
+ {0x94db483840b717ef, 0xa8c2a44eb4571cdd},
+ {0xba121a4650e4ddeb, 0x92f34d62616ce414},
+ {0xe896a0d7e51e1566, 0x77b020baf9c81d18},
+ {0x915e2486ef32cd60, 0x0ace1474dc1d122f},
+ {0xb5b5ada8aaff80b8, 0x0d819992132456bb},
+ {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a},
+ {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2},
+ {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3},
+ {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf},
+ {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c},
+ {0xad4ab7112eb3929d, 0x86c16c98d2c953c7},
+ {0xd89d64d57a607744, 0xe871c7bf077ba8b8},
+ {0x87625f056c7c4a8b, 0x11471cd764ad4973},
+ {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0},
+ {0xd389b47879823479, 0x4aff1d108d4ec2c4},
+ {0x843610cb4bf160cb, 0xcedf722a585139bb},
+ {0xa54394fe1eedb8fe, 0xc2974eb4ee658829},
+ {0xce947a3da6a9273e, 0x733d226229feea33},
+ {0x811ccc668829b887, 0x0806357d5a3f5260},
+ {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8},
+ {0xc9bcff6034c13052, 0xfc89b393dd02f0b6},
+ {0xfc2c3f3841f17c67, 0xbbac2078d443ace3},
+ {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e},
+ {0xc5029163f384a931, 0x0a9e795e65d4df12},
+ {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6},
+ {0x99ea0196163fa42e, 0x504bced1bf8e4e46},
+ {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7},
+ {0xf07da27a82c37088, 0x5d767327bb4e5a4d},
+ {0x964e858c91ba2655, 0x3a6a07f8d510f870},
+ {0xbbe226efb628afea, 0x890489f70a55368c},
+ {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f},
+ {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e},
+ {0xb77ada0617e3bbcb, 0x09ce6ebb40173745},
+ {0xe55990879ddcaabd, 0xcc420a6a101d0516},
+ {0x8f57fa54c2a9eab6, 0x9fa946824a12232e},
+ {0xb32df8e9f3546564, 0x47939822dc96abfa},
+ {0xdff9772470297ebd, 0x59787e2b93bc56f8},
+ {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b},
+ {0xaefae51477a06b03, 0xede622920b6b23f2},
+ {0xdab99e59958885c4, 0xe95fab368e45ecee},
+ {0x88b402f7fd75539b, 0x11dbcb0218ebb415},
+ {0xaae103b5fcd2a881, 0xd652bdc29f26a11a},
+ {0xd59944a37c0752a2, 0x4be76d3346f04960},
+ {0x857fcae62d8493a5, 0x6f70a4400c562ddc},
+ {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953},
+ {0xd097ad07a71f26b2, 0x7e2000a41346a7a8},
+ {0x825ecc24c873782f, 0x8ed400668c0c28c9},
+ {0xa2f67f2dfa90563b, 0x728900802f0f32fb},
+ {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba},
+ {0xfea126b7d78186bc, 0xe2f610c84987bfa9},
+ {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca},
+ {0xc6ede63fa05d3143, 0x91503d1c79720dbc},
+ {0xf8a95fcf88747d94, 0x75a44c6397ce912b},
+ {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb},
+ {0xc24452da229b021b, 0xfbe85badce996169},
+ {0xf2d56790ab41c2a2, 0xfae27299423fb9c4},
+ {0x97c560ba6b0919a5, 0xdccd879fc967d41b},
+ {0xbdb6b8e905cb600f, 0x5400e987bbc1c921},
+ {0xed246723473e3813, 0x290123e9aab23b69},
+ {0x9436c0760c86e30b, 0xf9a0b6720aaf6522},
+ {0xb94470938fa89bce, 0xf808e40e8d5b3e6a},
+ {0xe7958cb87392c2c2, 0xb60b1d1230b20e05},
+ {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3},
+ {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4},
+ {0xe2280b6c20dd5232, 0x25c6da63c38de1b1},
+ {0x8d590723948a535f, 0x579c487e5a38ad0f},
+ {0xb0af48ec79ace837, 0x2d835a9df0c6d852},
+ {0xdcdb1b2798182244, 0xf8e431456cf88e66},
+ {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900},
+ {0xac8b2d36eed2dac5, 0xe272467e3d222f40},
+ {0xd7adf884aa879177, 0x5b0ed81dcc6abb10},
+ {0x86ccbb52ea94baea, 0x98e947129fc2b4ea},
+ {0xa87fea27a539e9a5, 0x3f2398d747b36225},
+ {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae},
+ {0x83a3eeeef9153e89, 0x1953cf68300424ad},
+ {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8},
+ {0xcdb02555653131b6, 0x3792f412cb06794e},
+ {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1},
+ {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5},
+ {0xc8de047564d20a8b, 0xf245825a5a445276},
+ {0xfb158592be068d2e, 0xeed6e2f0f0d56713},
+ {0x9ced737bb6c4183d, 0x55464dd69685606c},
+ {0xc428d05aa4751e4c, 0xaa97e14c3c26b887},
+ {0xf53304714d9265df, 0xd53dd99f4b3066a9},
+ {0x993fe2c6d07b7fab, 0xe546a8038efe402a},
+ {0xbf8fdb78849a5f96, 0xde98520472bdd034},
+ {0xef73d256a5c0f77c, 0x963e66858f6d4441},
+ {0x95a8637627989aad, 0xdde7001379a44aa9},
+ {0xbb127c53b17ec159, 0x5560c018580d5d53},
+ {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7},
+ {0x9226712162ab070d, 0xcab3961304ca70e9},
+ {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23},
+ {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b},
+ {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243},
+ {0xb267ed1940f1c61c, 0x55f038b237591ed4},
+ {0xdf01e85f912e37a3, 0x6b6c46dec52f6689},
+ {0x8b61313bbabce2c6, 0x2323ac4b3b3da016},
+ {0xae397d8aa96c1b77, 0xabec975e0a0d081b},
+ {0xd9c7dced53c72255, 0x96e7bd358c904a22},
+ {0x881cea14545c7575, 0x7e50d64177da2e55},
+ {0xaa242499697392d2, 0xdde50bd1d5d0b9ea},
+ {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865},
+ {0x84ec3c97da624ab4, 0xbd5af13bef0b113f},
+ {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f},
+ {0xcfb11ead453994ba, 0x67de18eda5814af3},
+ {0x81ceb32c4b43fcf4, 0x80eacf948770ced8},
+ {0xa2425ff75e14fc31, 0xa1258379a94d028e},
+ {0xcad2f7f5359a3b3e, 0x096ee45813a04331},
+ {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd},
+ {0x9e74d1b791e07e48, 0x775ea264cf55347e},
+ {0xc612062576589dda, 0x95364afe032a819e},
+ {0xf79687aed3eec551, 0x3a83ddbd83f52205},
+ {0x9abe14cd44753b52, 0xc4926a9672793543},
+ {0xc16d9a0095928a27, 0x75b7053c0f178294},
+ {0xf1c90080baf72cb1, 0x5324c68b12dd6339},
+ {0x971da05074da7bee, 0xd3f6fc16ebca5e04},
+ {0xbce5086492111aea, 0x88f4bb1ca6bcf585},
+ {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6},
+ {0x9392ee8e921d5d07, 0x3aff322e62439fd0},
+ {0xb877aa3236a4b449, 0x09befeb9fad487c3},
+ {0xe69594bec44de15b, 0x4c2ebe687989a9b4},
+ {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11},
+ {0xb424dc35095cd80f, 0x538484c19ef38c95},
+ {0xe12e13424bb40e13, 0x2865a5f206b06fba},
+ {0x8cbccc096f5088cb, 0xf93f87b7442e45d4},
+ {0xafebff0bcb24aafe, 0xf78f69a51539d749},
+ {0xdbe6fecebdedd5be, 0xb573440e5a884d1c},
+ {0x89705f4136b4a597, 0x31680a88f8953031},
+ {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e},
+ {0xd6bf94d5e57a42bc, 0x3d32907604691b4d},
+ {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110},
+ {0xa7c5ac471b478423, 0x0fcf80dc33721d54},
+ {0xd1b71758e219652b, 0xd3c36113404ea4a9},
+ {0x83126e978d4fdf3b, 0x645a1cac083126ea},
+ {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4},
+ {0xcccccccccccccccc, 0xcccccccccccccccd},
+ {0x8000000000000000, 0x0000000000000000},
+ {0xa000000000000000, 0x0000000000000000},
+ {0xc800000000000000, 0x0000000000000000},
+ {0xfa00000000000000, 0x0000000000000000},
+ {0x9c40000000000000, 0x0000000000000000},
+ {0xc350000000000000, 0x0000000000000000},
+ {0xf424000000000000, 0x0000000000000000},
+ {0x9896800000000000, 0x0000000000000000},
+ {0xbebc200000000000, 0x0000000000000000},
+ {0xee6b280000000000, 0x0000000000000000},
+ {0x9502f90000000000, 0x0000000000000000},
+ {0xba43b74000000000, 0x0000000000000000},
+ {0xe8d4a51000000000, 0x0000000000000000},
+ {0x9184e72a00000000, 0x0000000000000000},
+ {0xb5e620f480000000, 0x0000000000000000},
+ {0xe35fa931a0000000, 0x0000000000000000},
+ {0x8e1bc9bf04000000, 0x0000000000000000},
+ {0xb1a2bc2ec5000000, 0x0000000000000000},
+ {0xde0b6b3a76400000, 0x0000000000000000},
+ {0x8ac7230489e80000, 0x0000000000000000},
+ {0xad78ebc5ac620000, 0x0000000000000000},
+ {0xd8d726b7177a8000, 0x0000000000000000},
+ {0x878678326eac9000, 0x0000000000000000},
+ {0xa968163f0a57b400, 0x0000000000000000},
+ {0xd3c21bcecceda100, 0x0000000000000000},
+ {0x84595161401484a0, 0x0000000000000000},
+ {0xa56fa5b99019a5c8, 0x0000000000000000},
+ {0xcecb8f27f4200f3a, 0x0000000000000000},
+ {0x813f3978f8940984, 0x4000000000000000},
+ {0xa18f07d736b90be5, 0x5000000000000000},
+ {0xc9f2c9cd04674ede, 0xa400000000000000},
+ {0xfc6f7c4045812296, 0x4d00000000000000},
+ {0x9dc5ada82b70b59d, 0xf020000000000000},
+ {0xc5371912364ce305, 0x6c28000000000000},
+ {0xf684df56c3e01bc6, 0xc732000000000000},
+ {0x9a130b963a6c115c, 0x3c7f400000000000},
+ {0xc097ce7bc90715b3, 0x4b9f100000000000},
+ {0xf0bdc21abb48db20, 0x1e86d40000000000},
+ {0x96769950b50d88f4, 0x1314448000000000},
+ {0xbc143fa4e250eb31, 0x17d955a000000000},
+ {0xeb194f8e1ae525fd, 0x5dcfab0800000000},
+ {0x92efd1b8d0cf37be, 0x5aa1cae500000000},
+ {0xb7abc627050305ad, 0xf14a3d9e40000000},
+ {0xe596b7b0c643c719, 0x6d9ccd05d0000000},
+ {0x8f7e32ce7bea5c6f, 0xe4820023a2000000},
+ {0xb35dbf821ae4f38b, 0xdda2802c8a800000},
+ {0xe0352f62a19e306e, 0xd50b2037ad200000},
+ {0x8c213d9da502de45, 0x4526f422cc340000},
+ {0xaf298d050e4395d6, 0x9670b12b7f410000},
+ {0xdaf3f04651d47b4c, 0x3c0cdd765f114000},
+ {0x88d8762bf324cd0f, 0xa5880a69fb6ac800},
+ {0xab0e93b6efee0053, 0x8eea0d047a457a00},
+ {0xd5d238a4abe98068, 0x72a4904598d6d880},
+ {0x85a36366eb71f041, 0x47a6da2b7f864750},
+ {0xa70c3c40a64e6c51, 0x999090b65f67d924},
+ {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d},
+ {0x82818f1281ed449f, 0xbff8f10e7a8921a4},
+ {0xa321f2d7226895c7, 0xaff72d52192b6a0d},
+ {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490},
+ {0xfee50b7025c36a08, 0x02f236d04753d5b4},
+ {0x9f4f2726179a2245, 0x01d762422c946590},
+ {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5},
+ {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2},
+ {0x9b934c3b330c8577, 0x63cc55f49f88eb2f},
+ {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb},
+ {0xf316271c7fc3908a, 0x8bef464e3945ef7a},
+ {0x97edd871cfda3a56, 0x97758bf0e3cbb5ac},
+ {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317},
+ {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd},
+ {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a},
+ {0xb975d6b6ee39e436, 0xb3e2fd538e122b44},
+ {0xe7d34c64a9c85d44, 0x60dbbca87196b616},
+ {0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd},
+ {0xb51d13aea4a488dd, 0x6babab6398bdbe41},
+ {0xe264589a4dcdab14, 0xc696963c7eed2dd1},
+ {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2},
+ {0xb0de65388cc8ada8, 0x3b25a55f43294bcb},
+ {0xdd15fe86affad912, 0x49ef0eb713f39ebe},
+ {0x8a2dbf142dfcc7ab, 0x6e3569326c784337},
+ {0xacb92ed9397bf996, 0x49c2c37f07965404},
+ {0xd7e77a8f87daf7fb, 0xdc33745ec97be906},
+ {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3},
+ {0xa8acd7c0222311bc, 0xc40832ea0d68ce0c},
+ {0xd2d80db02aabd62b, 0xf50a3fa490c30190},
+ {0x83c7088e1aab65db, 0x792667c6da79e0fa},
+ {0xa4b8cab1a1563f52, 0x577001b891185938},
+ {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86},
+ {0x80b05e5ac60b6178, 0x544f8158315b05b4},
+ {0xa0dc75f1778e39d6, 0x696361ae3db1c721},
+ {0xc913936dd571c84c, 0x03bc3a19cd1e38e9},
+ {0xfb5878494ace3a5f, 0x04ab48a04065c723},
+ {0x9d174b2dcec0e47b, 0x62eb0d64283f9c76},
+ {0xc45d1df942711d9a, 0x3ba5d0bd324f8394},
+ {0xf5746577930d6500, 0xca8f44ec7ee36479},
+ {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb},
+ {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e},
+ {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e},
+ {0x95d04aee3b80ece5, 0xbba1f1d158724a12},
+ {0xbb445da9ca61281f, 0x2a8a6e45ae8edc97},
+ {0xea1575143cf97226, 0xf52d09d71a3293bd},
+ {0x924d692ca61be758, 0x593c2626705f9c56},
+ {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c},
+ {0xe498f455c38b997a, 0x0b6dfb9c0f956447},
+ {0x8edf98b59a373fec, 0x4724bd4189bd5eac},
+ {0xb2977ee300c50fe7, 0x58edec91ec2cb657},
+ {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed},
+ {0x8b865b215899f46c, 0xbd79e0d20082ee74},
+ {0xae67f1e9aec07187, 0xecd8590680a3aa11},
+ {0xda01ee641a708de9, 0xe80e6f4820cc9495},
+ {0x884134fe908658b2, 0x3109058d147fdcdd},
+ {0xaa51823e34a7eede, 0xbd4b46f0599fd415},
+ {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a},
+ {0x850fadc09923329e, 0x03e2cf6bc604ddb0},
+ {0xa6539930bf6bff45, 0x84db8346b786151c},
+ {0xcfe87f7cef46ff16, 0xe612641865679a63},
+ {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e},
+ {0xa26da3999aef7749, 0xe3be5e330f38f09d},
+ {0xcb090c8001ab551c, 0x5cadf5bfd3072cc5},
+ {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6},
+ {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa},
+ {0xc646d63501a1511d, 0xb281e1fd541501b8},
+ {0xf7d88bc24209a565, 0x1f225a7ca91a4226},
+ {0x9ae757596946075f, 0x3375788de9b06958},
+ {0xc1a12d2fc3978937, 0x0052d6b1641c83ae},
+ {0xf209787bb47d6b84, 0xc0678c5dbd23a49a},
+ {0x9745eb4d50ce6332, 0xf840b7ba963646e0},
+ {0xbd176620a501fbff, 0xb650e5a93bc3d898},
+ {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe},
+ {0x93ba47c980e98cdf, 0xc66f336c36b10137},
+ {0xb8a8d9bbe123f017, 0xb80b0047445d4184},
+ {0xe6d3102ad96cec1d, 0xa60dc059157491e5},
+ {0x9043ea1ac7e41392, 0x87c89837ad68db2f},
+ {0xb454e4a179dd1877, 0x29babe4598c311fb},
+ {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a},
+ {0x8ce2529e2734bb1d, 0x1899e4a65f58660c},
+ {0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f},
+ {0xdc21a1171d42645d, 0x76707543f4fa1f73},
+ {0x899504ae72497eba, 0x6a06494a791c53a8},
+ {0xabfa45da0edbde69, 0x0487db9d17636892},
+ {0xd6f8d7509292d603, 0x45a9d2845d3c42b6},
+ {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2},
+ {0xa7f26836f282b732, 0x8e6cac7768d7141e},
+ {0xd1ef0244af2364ff, 0x3207d795430cd926},
+ {0x8335616aed761f1f, 0x7f44e6bd49e807b8},
+ {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6},
+ {0xcd036837130890a1, 0x36dba887c37a8c0f},
+ {0x802221226be55a64, 0xc2494954da2c9789},
+ {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c},
+ {0xc83553c5c8965d3d, 0x6f92829494e5acc7},
+ {0xfa42a8b73abbf48c, 0xcb772339ba1f17f9},
+ {0x9c69a97284b578d7, 0xff2a760414536efb},
+ {0xc38413cf25e2d70d, 0xfef5138519684aba},
+ {0xf46518c2ef5b8cd1, 0x7eb258665fc25d69},
+ {0x98bf2f79d5993802, 0xef2f773ffbd97a61},
+ {0xbeeefb584aff8603, 0xaafb550ffacfd8fa},
+ {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38},
+ {0x952ab45cfa97a0b2, 0xdd945a747bf26183},
+ {0xba756174393d88df, 0x94f971119aeef9e4},
+ {0xe912b9d1478ceb17, 0x7a37cd5601aab85d},
+ {0x91abb422ccb812ee, 0xac62e055c10ab33a},
+ {0xb616a12b7fe617aa, 0x577b986b314d6009},
+ {0xe39c49765fdf9d94, 0xed5a7e85fda0b80b},
+ {0x8e41ade9fbebc27d, 0x14588f13be847307},
+ {0xb1d219647ae6b31c, 0x596eb2d8ae258fc8},
+ {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb},
+ {0x8aec23d680043bee, 0x25de7bb9480d5854},
+ {0xada72ccc20054ae9, 0xaf561aa79a10ae6a},
+ {0xd910f7ff28069da4, 0x1b2ba1518094da04},
+ {0x87aa9aff79042286, 0x90fb44d2f05d0842},
+ {0xa99541bf57452b28, 0x353a1607ac744a53},
+ {0xd3fa922f2d1675f2, 0x42889b8997915ce8},
+ {0x847c9b5d7c2e09b7, 0x69956135febada11},
+ {0xa59bc234db398c25, 0x43fab9837e699095},
+ {0xcf02b2c21207ef2e, 0x94f967e45e03f4bb},
+ {0x8161afb94b44f57d, 0x1d1be0eebac278f5},
+ {0xa1ba1ba79e1632dc, 0x6462d92a69731732},
+ {0xca28a291859bbf93, 0x7d7b8f7503cfdcfe},
+ {0xfcb2cb35e702af78, 0x5cda735244c3d43e},
+ {0x9defbf01b061adab, 0x3a0888136afa64a7},
+ {0xc56baec21c7a1916, 0x088aaa1845b8fdd0},
+ {0xf6c69a72a3989f5b, 0x8aad549e57273d45},
+ {0x9a3c2087a63f6399, 0x36ac54e2f678864b},
+ {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd},
+ {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5},
+ {0x969eb7c47859e743, 0x9f644ae5a4b1b325},
+ {0xbc4665b596706114, 0x873d5d9f0dde1fee},
+ {0xeb57ff22fc0c7959, 0xa90cb506d155a7ea},
+ {0x9316ff75dd87cbd8, 0x09a7f12442d588f2},
+ {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb2f},
+ {0xe5d3ef282a242e81, 0x8f1668c8a86da5fa},
+ {0x8fa475791a569d10, 0xf96e017d694487bc},
+ {0xb38d92d760ec4455, 0x37c981dcc395a9ac},
+ {0xe070f78d3927556a, 0x85bbe253f47b1417},
+ {0x8c469ab843b89562, 0x93956d7478ccec8e},
+ {0xaf58416654a6babb, 0x387ac8d1970027b2},
+ {0xdb2e51bfe9d0696a, 0x06997b05fcc0319e},
+ {0x88fcf317f22241e2, 0x441fece3bdf81f03},
+ {0xab3c2fddeeaad25a, 0xd527e81cad7626c3},
+ {0xd60b3bd56a5586f1, 0x8a71e223d8d3b074},
+ {0x85c7056562757456, 0xf6872d5667844e49},
+ {0xa738c6bebb12d16c, 0xb428f8ac016561db},
+ {0xd106f86e69d785c7, 0xe13336d701beba52},
+ {0x82a45b450226b39c, 0xecc0024661173473},
+ {0xa34d721642b06084, 0x27f002d7f95d0190},
+ {0xcc20ce9bd35c78a5, 0x31ec038df7b441f4},
+ {0xff290242c83396ce, 0x7e67047175a15271},
+ {0x9f79a169bd203e41, 0x0f0062c6e984d386},
+ {0xc75809c42c684dd1, 0x52c07b78a3e60868},
+ {0xf92e0c3537826145, 0xa7709a56ccdf8a82},
+ {0x9bbcc7a142b17ccb, 0x88a66076400bb691},
+ {0xc2abf989935ddbfe, 0x6acff893d00ea435},
+ {0xf356f7ebf83552fe, 0x0583f6b8c4124d43},
+ {0x98165af37b2153de, 0xc3727a337a8b704a},
+ {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c},
+ {0xeda2ee1c7064130c, 0x1162def06f79df73},
+ {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8},
+ {0xb9a74a0637ce2ee1, 0x6d953e2bd7173692},
+ {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437},
+ {0x910ab1d4db9914a0, 0x1d9c9892400a22a2},
+ {0xb54d5e4a127f59c8, 0x2503beb6d00cab4b},
+ {0xe2a0b5dc971f303a, 0x2e44ae64840fd61d},
+ {0x8da471a9de737e24, 0x5ceaecfed289e5d2},
+ {0xb10d8e1456105dad, 0x7425a83e872c5f47},
+ {0xdd50f1996b947518, 0xd12f124e28f77719},
+ {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f},
+ {0xace73cbfdc0bfb7b, 0x636cc64d1001550b},
+ {0xd8210befd30efa5a, 0x3c47f7e05401aa4e},
+ {0x8714a775e3e95c78, 0x65acfaec34810a71},
+ {0xa8d9d1535ce3b396, 0x7f1839a741a14d0d},
+ {0xd31045a8341ca07c, 0x1ede48111209a050},
+ {0x83ea2b892091e44d, 0x934aed0aab460432},
+ {0xa4e4b66b68b65d60, 0xf81da84d5617853f},
+ {0xce1de40642e3f4b9, 0x36251260ab9d668e},
+ {0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019},
+ {0xa1075a24e4421730, 0xb24cf65b8612f81f},
+ {0xc94930ae1d529cfc, 0xdee033f26797b627},
+ {0xfb9b7cd9a4a7443c, 0x169840ef017da3b1},
+ {0x9d412e0806e88aa5, 0x8e1f289560ee864e},
+ {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2},
+ {0xf5b5d7ec8acb58a2, 0xae10af696774b1db},
+ {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29},
+ {0xbff610b0cc6edd3f, 0x17fd090a58d32af3},
+ {0xeff394dcff8a948e, 0xddfc4b4cef07f5b0},
+ {0x95f83d0a1fb69cd9, 0x4abdaf101564f98e},
+ {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1},
+ {0xea53df5fd18d5513, 0x84c86189216dc5ed},
+ {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4},
+ {0xb7118682dbb66a77, 0x3fbc8c33221dc2a1},
+ {0xe4d5e82392a40515, 0x0fabaf3feaa5334a},
+ {0x8f05b1163ba6832d, 0x29cb4d87f2a7400e},
+ {0xb2c71d5bca9023f8, 0x743e20e9ef511012},
+ {0xdf78e4b2bd342cf6, 0x914da9246b255416},
+ {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e},
+ {0xae9672aba3d0c320, 0xa184ac2473b529b1},
+ {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e},
+ {0x8865899617fb1871, 0x7e2fa67c7a658892},
+ {0xaa7eebfb9df9de8d, 0xddbb901b98feeab7},
+ {0xd51ea6fa85785631, 0x552a74227f3ea565},
+ {0x8533285c936b35de, 0xd53a88958f87275f},
+ {0xa67ff273b8460356, 0x8a892abaf368f137},
+ {0xd01fef10a657842c, 0x2d2b7569b0432d85},
+ {0x8213f56a67f6b29b, 0x9c3b29620e29fc73},
+ {0xa298f2c501f45f42, 0x8349f3ba91b47b8f},
+ {0xcb3f2f7642717713, 0x241c70a936219a73},
+ {0xfe0efb53d30dd4d7, 0xed238cd383aa0110},
+ {0x9ec95d1463e8a506, 0xf4363804324a40aa},
+ {0xc67bb4597ce2ce48, 0xb143c6053edcd0d5},
+ {0xf81aa16fdc1b81da, 0xdd94b7868e94050a},
+ {0x9b10a4e5e9913128, 0xca7cf2b4191c8326},
+ {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0},
+ {0xf24a01a73cf2dccf, 0xbc633b39673c8cec},
+ {0x976e41088617ca01, 0xd5be0503e085d813},
+ {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18},
+ {0xec9c459d51852ba2, 0xddf8e7d60ed1219e},
+ {0x93e1ab8252f33b45, 0xcabb90e5c942b503},
+ {0xb8da1662e7b00a17, 0x3d6a751f3b936243},
+ {0xe7109bfba19c0c9d, 0x0cc512670a783ad4},
+ {0x906a617d450187e2, 0x27fb2b80668b24c5},
+ {0xb484f9dc9641e9da, 0xb1f9f660802dedf6},
+ {0xe1a63853bbd26451, 0x5e7873f8a0396973},
+ {0x8d07e33455637eb2, 0xdb0b487b6423e1e8},
+ {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62},
+ {0xdc5c5301c56b75f7, 0x7641a140cc7810fb},
+ {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d},
+ {0xac2820d9623bf429, 0x546345fa9fbdcd44},
+ {0xd732290fbacaf133, 0xa97c177947ad4095},
+ {0x867f59a9d4bed6c0, 0x49ed8eabcccc485d},
+ {0xa81f301449ee8c70, 0x5c68f256bfff5a74},
+ {0xd226fc195c6a2f8c, 0x73832eec6fff3111},
+ {0x83585d8fd9c25db7, 0xc831fd53c5ff7eab},
+ {0xa42e74f3d032f525, 0xba3e7ca8b77f5e55},
+ {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb},
+ {0x80444b5e7aa7cf85, 0x7980d163cf5b81b3},
+ {0xa0555e361951c366, 0xd7e105bcc332621f},
+ {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7},
+ {0xfa856334878fc150, 0xb14f98f6f0feb951},
+ {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3},
+ {0xc3b8358109e84f07, 0x0a862f80ec4700c8},
+ {0xf4a642e14c6262c8, 0xcd27bb612758c0fa},
+ {0x98e7e9cccfbd7dbd, 0x8038d51cb897789c},
+ {0xbf21e44003acdd2c, 0xe0470a63e6bd56c3},
+ {0xeeea5d5004981478, 0x1858ccfce06cac74},
+ {0x95527a5202df0ccb, 0x0f37801e0c43ebc8},
+ {0xbaa718e68396cffd, 0xd30560258f54e6ba},
+ {0xe950df20247c83fd, 0x47c6b82ef32a2069},
+ {0x91d28b7416cdd27e, 0x4cdc331d57fa5441},
+ {0xb6472e511c81471d, 0xe0133fe4adf8e952},
+ {0xe3d8f9e563a198e5, 0x58180fddd97723a6},
+ {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648},
+ {0xb201833b35d63f73, 0x2cd2cc6551e513da},
+ {0xde81e40a034bcf4f, 0xf8077f7ea65e58d1},
+ {0x8b112e86420f6191, 0xfb04afaf27faf782},
+ {0xadd57a27d29339f6, 0x79c5db9af1f9b563},
+ {0xd94ad8b1c7380874, 0x18375281ae7822bc},
+ {0x87cec76f1c830548, 0x8f2293910d0b15b5},
+ {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb22},
+ {0xd433179d9c8cb841, 0x5fa60692a46151eb},
+ {0x849feec281d7f328, 0xdbc7c41ba6bcd333},
+ {0xa5c7ea73224deff3, 0x12b9b522906c0800},
+ {0xcf39e50feae16bef, 0xd768226b34870a00},
+ {0x81842f29f2cce375, 0xe6a1158300d46640},
+ {0xa1e53af46f801c53, 0x60495ae3c1097fd0},
+ {0xca5e89b18b602368, 0x385bb19cb14bdfc4},
+ {0xfcf62c1dee382c42, 0x46729e03dd9ed7b5},
+ {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d1},
+ {0xc5a05277621be293, 0xc7098b7305241885},
+ { 0xf70867153aa2db38,
+ 0xb8cbee4fc66d1ea7 }
+#else
+ {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
+ {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
+ {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f},
+ {0x86a8d39ef77164bc, 0xae5dff9c02033198},
+ {0xd98ddaee19068c76, 0x3badd624dd9b0958},
+ {0xafbd2350644eeacf, 0xe5d1929ef90898fb},
+ {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2},
+ {0xe55990879ddcaabd, 0xcc420a6a101d0516},
+ {0xb94470938fa89bce, 0xf808e40e8d5b3e6a},
+ {0x95a8637627989aad, 0xdde7001379a44aa9},
+ {0xf1c90080baf72cb1, 0x5324c68b12dd6339},
+ {0xc350000000000000, 0x0000000000000000},
+ {0x9dc5ada82b70b59d, 0xf020000000000000},
+ {0xfee50b7025c36a08, 0x02f236d04753d5b4},
+ {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86},
+ {0xa6539930bf6bff45, 0x84db8346b786151c},
+ {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2},
+ {0xd910f7ff28069da4, 0x1b2ba1518094da04},
+ {0xaf58416654a6babb, 0x387ac8d1970027b2},
+ {0x8da471a9de737e24, 0x5ceaecfed289e5d2},
+ {0xe4d5e82392a40515, 0x0fabaf3feaa5334a},
+ {0xb8da1662e7b00a17, 0x3d6a751f3b936243},
+ { 0x95527a5202df0ccb,
+ 0x0f37801e0c43ebc8 }
+#endif
+ };
+
#if FMT_USE_FULL_CACHE_DRAGONBOX
- return data::dragonbox_pow10_significands_128[k -
- float_info<double>::min_k];
+ return pow10_significands[k - float_info<double>::min_k];
#else
+ static constexpr const uint64_t powers_of_5_64[] = {
+ 0x0000000000000001, 0x0000000000000005, 0x0000000000000019,
+ 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,
+ 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,
+ 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd,
+ 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9,
+ 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5,
+ 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631,
+ 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed,
+ 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9};
+
+ static constexpr const uint32_t pow10_recovery_errors[] = {
+ 0x50001400, 0x54044100, 0x54014555, 0x55954415, 0x54115555, 0x00000001,
+ 0x50000000, 0x00104000, 0x54010004, 0x05004001, 0x55555544, 0x41545555,
+ 0x54040551, 0x15445545, 0x51555514, 0x10000015, 0x00101100, 0x01100015,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04450514, 0x45414110,
+ 0x55555145, 0x50544050, 0x15040155, 0x11054140, 0x50111514, 0x11451454,
+ 0x00400541, 0x00000000, 0x55555450, 0x10056551, 0x10054011, 0x55551014,
+ 0x69514555, 0x05151109, 0x00155555};
+
static const int compression_ratio = 27;
// Compute base index.
int offset = k - kb;
// Get base cache.
- uint128_wrapper base_cache =
- data::dragonbox_pow10_significands_128[cache_index];
+ uint128_wrapper base_cache = pow10_significands[cache_index];
if (offset == 0) return base_cache;
// Compute the required amount of bit-shift.
FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected");
// Try to recover the real cache.
- uint64_t pow5 = data::powers_of_5_64[offset];
+ uint64_t pow5 = powers_of_5_64[offset];
uint128_wrapper recovered_cache = umul128(base_cache.high(), pow5);
uint128_wrapper middle_low =
umul128(base_cache.low() - (kb < 0 ? 1u : 0u), pow5);
// Get error.
int error_idx = (k - float_info<double>::min_k) / 16;
- uint32_t error = (data::dragonbox_pow10_recovery_errors[error_idx] >>
+ uint32_t error = (pow10_recovery_errors[error_idx] >>
((k - float_info<double>::min_k) % 16) * 2) &
0x3;
}
// Remove trailing zeros from n and return the number of zeros removed (float)
-FMT_ALWAYS_INLINE int remove_trailing_zeros(uint32_t& n) FMT_NOEXCEPT {
+FMT_INLINE int remove_trailing_zeros(uint32_t& n) FMT_NOEXCEPT {
#ifdef FMT_BUILTIN_CTZ
int t = FMT_BUILTIN_CTZ(n);
#else
}
// Removes trailing zeros and returns the number of zeros removed (double)
-FMT_ALWAYS_INLINE int remove_trailing_zeros(uint64_t& n) FMT_NOEXCEPT {
+FMT_INLINE int remove_trailing_zeros(uint64_t& n) FMT_NOEXCEPT {
#ifdef FMT_BUILTIN_CTZLL
int t = FMT_BUILTIN_CTZLL(n);
#else
// The main algorithm for shorter interval case
template <class T>
-FMT_ALWAYS_INLINE FMT_SAFEBUFFERS decimal_fp<T> shorter_interval_case(
- int exponent) FMT_NOEXCEPT {
+FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) FMT_NOEXCEPT {
decimal_fp<T> ret_value;
// Compute k and beta
const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent);
return ret_value;
}
-template <typename T>
-FMT_SAFEBUFFERS decimal_fp<T> to_decimal(T x) FMT_NOEXCEPT {
+template <typename T> decimal_fp<T> to_decimal(T x) FMT_NOEXCEPT {
// Step 1: integer promotion & Schubfach multiplier calculation.
using carrier_uint = typename float_info<T>::carrier_uint;
}
} // namespace dragonbox
-// Formats value using a variation of the Fixed-Precision Positive
-// Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
-// https://fmt.dev/p372-steele.pdf.
-template <typename Double>
-void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
- int& exp10) {
+// Formats a floating-point number using a variation of the Fixed-Precision
+// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
+// https://fmt.dev/papers/p372-steele.pdf.
+FMT_CONSTEXPR20 inline void format_dragon(fp value, bool is_predecessor_closer,
+ int num_digits, buffer<char>& buf,
+ int& exp10) {
bigint numerator; // 2 * R in (FPP)^2.
bigint denominator; // 2 * S in (FPP)^2.
// lower and upper are differences between value and corresponding boundaries.
bigint lower; // (M^- in (FPP)^2).
bigint upper_store; // upper's value if different from lower.
bigint* upper = nullptr; // (M^+ in (FPP)^2).
- fp value;
// Shift numerator and denominator by an extra bit or two (if lower boundary
// is closer) to make lower and upper integers. This eliminates multiplication
// by 2 during later computations.
- const bool is_predecessor_closer =
- binary32 ? value.assign(static_cast<float>(d)) : value.assign(d);
int shift = is_predecessor_closer ? 2 : 1;
uint64_t significand = value.f << shift;
if (value.e >= 0) {
// Generate the given number of digits.
exp10 -= num_digits - 1;
if (num_digits == 0) {
- buf.try_resize(1);
denominator *= 10;
- buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';
+ auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';
+ buf.push_back(digit);
return;
}
buf.try_resize(to_unsigned(num_digits));
buf[num_digits - 1] = static_cast<char>('0' + digit);
}
-template <typename T>
-int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
- static_assert(!std::is_same<T, float>::value, "");
+template <typename Float>
+FMT_HEADER_ONLY_CONSTEXPR20 int format_float(Float value, int precision,
+ float_specs specs,
+ buffer<char>& buf) {
+ // float is passed as double to reduce the number of instantiations.
+ static_assert(!std::is_same<Float, float>::value, "");
FMT_ASSERT(value >= 0, "value is negative");
const bool fixed = specs.format == float_format::fixed;
return 0;
}
buf.try_resize(to_unsigned(precision));
- std::uninitialized_fill_n(buf.data(), precision, '0');
+ fill_n(buf.data(), precision, '0');
return -precision;
}
- if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf);
+ if (specs.fallback) return snprintf_float(value, precision, specs, buf);
- if (precision < 0) {
+ if (!is_constant_evaluated() && precision < 0) {
// Use Dragonbox for the shortest format.
if (specs.binary32) {
auto dec = dragonbox::to_decimal(static_cast<float>(value));
return dec.exponent;
}
- // Use Grisu + Dragon4 for the given precision:
- // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
int exp = 0;
- const int min_exp = -60; // alpha in Grisu.
- int cached_exp10 = 0; // K in Grisu.
- fp normalized = normalize(fp(value));
- const auto cached_pow = get_cached_power(
- min_exp - (normalized.e + fp::significand_size), cached_exp10);
- normalized = normalized * cached_pow;
- // Limit precision to the maximum possible number of significant digits in an
- // IEEE754 double because we don't need to generate zeros.
- const int max_double_digits = 767;
- if (precision > max_double_digits) precision = max_double_digits;
- fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
- if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) {
- exp += handler.size - cached_exp10 - 1;
- fallback_format(value, handler.precision, specs.binary32, buf, exp);
- } else {
- exp += handler.exp10;
- buf.try_resize(to_unsigned(handler.size));
+ bool use_dragon = true;
+ if (is_fast_float<Float>()) {
+ // Use Grisu + Dragon4 for the given precision:
+ // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
+ const int min_exp = -60; // alpha in Grisu.
+ int cached_exp10 = 0; // K in Grisu.
+ fp normalized = normalize(fp(value));
+ const auto cached_pow = get_cached_power(
+ min_exp - (normalized.e + fp::num_significand_bits), cached_exp10);
+ normalized = normalized * cached_pow;
+ gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
+ if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error &&
+ !is_constant_evaluated()) {
+ exp += handler.exp10;
+ buf.try_resize(to_unsigned(handler.size));
+ use_dragon = false;
+ } else {
+ exp += handler.size - cached_exp10 - 1;
+ precision = handler.precision;
+ }
+ }
+ if (use_dragon) {
+ auto f = fp();
+ bool is_predecessor_closer =
+ specs.binary32 ? f.assign(static_cast<float>(value)) : f.assign(value);
+ // Limit precision to the maximum possible number of significant digits in
+ // an IEEE754 double because we don't need to generate zeros.
+ const int max_double_digits = 767;
+ if (precision > max_double_digits) precision = max_double_digits;
+ format_dragon(f, is_predecessor_closer, precision, buf, exp);
}
if (!fixed && !specs.showpoint) {
// Remove trailing zeros.
buf.try_resize(num_digits);
}
return exp;
-} // namespace detail
+}
template <typename T>
int snprintf_float(T value, int precision, float_specs specs,
--exp_pos;
} while (*exp_pos != 'e');
char sign = exp_pos[1];
- assert(sign == '+' || sign == '-');
+ FMT_ASSERT(sign == '+' || sign == '-', "");
int exp = 0;
auto p = exp_pos + 2; // Skip 'e' and sign.
do {
- assert(is_digit(*p));
+ FMT_ASSERT(is_digit(*p), "");
exp = exp * 10 + (*p++ - '0');
} while (p != end);
if (sign == '-') exp = -exp;
return exp - fraction_size;
}
}
-
-// A public domain branchless UTF-8 decoder by Christopher Wellons:
-// https://github.com/skeeto/branchless-utf8
-/* Decode the next character, c, from buf, reporting errors in e.
- *
- * Since this is a branchless decoder, four bytes will be read from the
- * buffer regardless of the actual length of the next character. This
- * means the buffer _must_ have at least three bytes of zero padding
- * following the end of the data stream.
- *
- * Errors are reported in e, which will be non-zero if the parsed
- * character was somehow invalid: invalid byte sequence, non-canonical
- * encoding, or a surrogate half.
- *
- * The function returns a pointer to the next character. When an error
- * occurs, this pointer will be a guess that depends on the particular
- * error, but it will always advance at least one byte.
- */
-inline const char* utf8_decode(const char* buf, uint32_t* c, int* e) {
- static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
- static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
- static const int shiftc[] = {0, 18, 12, 6, 0};
- static const int shifte[] = {0, 6, 4, 2, 0};
-
- int len = code_point_length(buf);
- const char* next = buf + len;
-
- // Assume a four-byte character and load four bytes. Unused bits are
- // shifted out.
- auto s = reinterpret_cast<const unsigned char*>(buf);
- *c = uint32_t(s[0] & masks[len]) << 18;
- *c |= uint32_t(s[1] & 0x3f) << 12;
- *c |= uint32_t(s[2] & 0x3f) << 6;
- *c |= uint32_t(s[3] & 0x3f) << 0;
- *c >>= shiftc[len];
-
- // Accumulate the various error conditions.
- *e = (*c < mins[len]) << 6; // non-canonical encoding
- *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half?
- *e |= (*c > 0x10FFFF) << 8; // out of range?
- *e |= (s[1] & 0xc0) >> 2;
- *e |= (s[2] & 0xc0) >> 4;
- *e |= (s[3]) >> 6;
- *e ^= 0x2a; // top two bits of each tail byte correct?
- *e >>= shifte[len];
-
- return next;
-}
-
-struct stringifier {
- template <typename T> FMT_INLINE std::string operator()(T value) const {
- return to_string(value);
- }
- std::string operator()(basic_format_arg<format_context>::handle h) const {
- memory_buffer buf;
- format_parse_context parse_ctx({});
- format_context format_ctx(buffer_appender<char>(buf), {}, {});
- h.format(parse_ctx, format_ctx);
- return to_string(buf);
- }
-};
} // namespace detail
template <> struct formatter<detail::bigint> {
- format_parse_context::iterator parse(format_parse_context& ctx) {
+ FMT_CONSTEXPR format_parse_context::iterator parse(
+ format_parse_context& ctx) {
return ctx.begin();
}
for (auto i = n.bigits_.size(); i > 0; --i) {
auto value = n.bigits_[i - 1u];
if (first) {
- out = format_to(out, "{:x}", value);
+ out = format_to(out, FMT_STRING("{:x}"), value);
first = false;
continue;
}
- out = format_to(out, "{:08x}", value);
+ out = format_to(out, FMT_STRING("{:08x}"), value);
}
if (n.exp_ > 0)
- out = format_to(out, "p{}", n.exp_ * detail::bigint::bigit_bits);
+ out = format_to(out, FMT_STRING("p{}"),
+ n.exp_ * detail::bigint::bigit_bits);
return out;
}
};
FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
- auto transcode = [this](const char* p) {
- auto cp = uint32_t();
- auto error = 0;
- p = utf8_decode(p, &cp, &error);
- if (error != 0) FMT_THROW(std::runtime_error("invalid utf8"));
+ for_each_codepoint(s, [this](uint32_t cp, string_view) {
+ if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8"));
if (cp <= 0xFFFF) {
buffer_.push_back(static_cast<wchar_t>(cp));
} else {
buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10)));
buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF)));
}
- return p;
- };
- auto p = s.data();
- const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars.
- if (s.size() >= block_size) {
- for (auto end = p + s.size() - block_size + 1; p < end;) p = transcode(p);
- }
- if (auto num_chars_left = s.data() + s.size() - p) {
- char buf[2 * block_size - 1] = {};
- memcpy(buf, p, to_unsigned(num_chars_left));
- p = buf;
- do {
- p = transcode(p);
- } while (p - buf < num_chars_left);
- }
+ return true;
+ });
buffer_.push_back(0);
}
FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
- string_view message) FMT_NOEXCEPT {
+ const char* message) FMT_NOEXCEPT {
FMT_TRY {
- memory_buffer buf;
- buf.resize(inline_buffer_size);
- for (;;) {
- char* system_message = &buf[0];
- int result =
- detail::safe_strerror(error_code, system_message, buf.size());
- if (result == 0) {
- format_to(detail::buffer_appender<char>(out), "{}: {}", message,
- system_message);
- return;
- }
- if (result != ERANGE)
- break; // Can't get error message, report error code instead.
- buf.resize(buf.size() * 2);
- }
+ auto ec = std::error_code(error_code, std::generic_category());
+ write(std::back_inserter(out), std::system_error(ec, message).what());
+ return;
}
FMT_CATCH(...) {}
format_error_code(out, error_code, message);
}
-FMT_FUNC void detail::error_handler::on_error(const char* message) {
- FMT_THROW(format_error(message));
-}
-
FMT_FUNC void report_system_error(int error_code,
- fmt::string_view message) FMT_NOEXCEPT {
+ const char* message) FMT_NOEXCEPT {
report_error(format_system_error, error_code, message);
}
-FMT_FUNC std::string detail::vformat(string_view format_str, format_args args) {
- if (format_str.size() == 2 && equal2(format_str.data(), "{}")) {
- auto arg = args.get(0);
- if (!arg) error_handler().on_error("argument not found");
- return visit_format_arg(stringifier(), arg);
- }
- memory_buffer buffer;
- detail::vformat_to(buffer, format_str, args);
+// DEPRECATED!
+// This function is defined here and not inline for ABI compatiblity.
+FMT_FUNC void detail::error_handler::on_error(const char* message) {
+ throw_format_error(message);
+}
+
+FMT_FUNC std::string vformat(string_view fmt, format_args args) {
+ // Don't optimize the "{}" case to keep the binary size small and because it
+ // can be better optimized in fmt::format anyway.
+ auto buffer = memory_buffer();
+ detail::vformat_to(buffer, fmt, args);
return to_string(buffer);
}
} // namespace detail
#endif
-FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
- memory_buffer buffer;
- detail::vformat_to(buffer, format_str,
- basic_format_args<buffer_context<char>>(args));
+namespace detail {
+FMT_FUNC void print(std::FILE* f, string_view text) {
#ifdef _WIN32
auto fd = _fileno(f);
if (_isatty(fd)) {
- detail::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size()));
+ detail::utf8_to_utf16 u16(string_view(text.data(), text.size()));
auto written = detail::dword();
- if (!detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
- u16.c_str(), static_cast<uint32_t>(u16.size()),
- &written, nullptr)) {
- FMT_THROW(format_error("failed to write to console"));
+ if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
+ u16.c_str(), static_cast<uint32_t>(u16.size()),
+ &written, nullptr)) {
+ return;
}
- return;
+ // Fallback to fwrite on failure. It can happen if the output has been
+ // redirected to NUL.
}
#endif
- detail::fwrite_fully(buffer.data(), 1, buffer.size(), f);
+ detail::fwrite_fully(text.data(), 1, text.size(), f);
+}
+} // namespace detail
+
+FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
+ memory_buffer buffer;
+ detail::vformat_to(buffer, format_str, args);
+ detail::print(f, {buffer.data(), buffer.size()});
}
#ifdef _WIN32
#ifndef FMT_FORMAT_H_
#define FMT_FORMAT_H_
-#include <algorithm>
-#include <cerrno>
-#include <cmath>
-#include <cstdint>
-#include <limits>
-#include <memory>
-#include <stdexcept>
+#include <cmath> // std::signbit
+#include <cstdint> // uint32_t
+#include <limits> // std::numeric_limits
+#include <memory> // std::uninitialized_copy
+#include <stdexcept> // std::runtime_error
+#include <system_error> // std::system_error
+#include <utility> // std::swap
+
+#ifdef __cpp_lib_bit_cast
+# include <bit> // std::bitcast
+#endif
#include "core.h"
-#ifdef __INTEL_COMPILER
-# define FMT_ICC_VERSION __INTEL_COMPILER
-#elif defined(__ICL)
-# define FMT_ICC_VERSION __ICL
+#if FMT_GCC_VERSION
+# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden")))
#else
-# define FMT_ICC_VERSION 0
+# define FMT_GCC_VISIBILITY_HIDDEN
#endif
#ifdef __NVCC__
# define FMT_NOINLINE
#endif
-#if __cplusplus == 201103L || __cplusplus == 201402L
-# if defined(__INTEL_COMPILER) || defined(__PGI)
-# define FMT_FALLTHROUGH
-# elif defined(__clang__)
-# define FMT_FALLTHROUGH [[clang::fallthrough]]
-# elif FMT_GCC_VERSION >= 700 && \
- (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520)
-# define FMT_FALLTHROUGH [[gnu::fallthrough]]
-# else
-# define FMT_FALLTHROUGH
-# endif
-#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \
- (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
-# define FMT_FALLTHROUGH [[fallthrough]]
+#if FMT_MSC_VER
+# define FMT_MSC_DEFAULT = default
#else
-# define FMT_FALLTHROUGH
-#endif
-
-#ifndef FMT_MAYBE_UNUSED
-# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused)
-# define FMT_MAYBE_UNUSED [[maybe_unused]]
-# else
-# define FMT_MAYBE_UNUSED
-# endif
+# define FMT_MSC_DEFAULT
#endif
#ifndef FMT_THROW
# define FMT_THROW(x) throw x
# endif
# else
-# define FMT_THROW(x) \
- do { \
- static_cast<void>(sizeof(x)); \
- FMT_ASSERT(false, ""); \
+# define FMT_THROW(x) \
+ do { \
+ FMT_ASSERT(false, (x).what()); \
} while (false)
# endif
#endif
# define FMT_CATCH(x) if (false)
#endif
+#ifndef FMT_MAYBE_UNUSED
+# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused)
+# define FMT_MAYBE_UNUSED [[maybe_unused]]
+# else
+# define FMT_MAYBE_UNUSED
+# endif
+#endif
+
+// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers.
+#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC
+# define FMT_DEPRECATED_ALIAS
+#else
+# define FMT_DEPRECATED_ALIAS FMT_DEPRECATED
+#endif
+
#ifndef FMT_USE_USER_DEFINED_LITERALS
// EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs.
# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \
# endif
#endif
-#ifndef FMT_USE_UDL_TEMPLATE
-// EDG frontend based compilers (icc, nvcc, PGI, etc) and GCC < 6.4 do not
-// properly support UDL templates and GCC >= 9 warns about them.
-# if FMT_USE_USER_DEFINED_LITERALS && \
- (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 501) && \
- ((FMT_GCC_VERSION >= 604 && __cplusplus >= 201402L) || \
- FMT_CLANG_VERSION >= 304) && \
- !defined(__PGI) && !defined(__NVCC__)
-# define FMT_USE_UDL_TEMPLATE 1
-# else
-# define FMT_USE_UDL_TEMPLATE 0
-# endif
-#endif
-
-#ifndef FMT_USE_FLOAT
-# define FMT_USE_FLOAT 1
-#endif
-
-#ifndef FMT_USE_DOUBLE
-# define FMT_USE_DOUBLE 1
-#endif
-
-#ifndef FMT_USE_LONG_DOUBLE
-# define FMT_USE_LONG_DOUBLE 1
-#endif
-
// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of
-// int_writer template instances to just one by only using the largest integer
-// type. This results in a reduction in binary size but will cause a decrease in
-// integer formatting performance.
+// integer formatter template instantiations to just one by only using the
+// largest integer type. This results in a reduction in binary size but will
+// cause a decrease in integer formatting performance.
#if !defined(FMT_REDUCE_INT_INSTANTIATIONS)
# define FMT_REDUCE_INT_INSTANTIATIONS 0
#endif
// __builtin_clz is broken in clang with Microsoft CodeGen:
-// https://github.com/fmtlib/fmt/issues/519
-#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER
-# define FMT_BUILTIN_CLZ(n) __builtin_clz(n)
-#endif
-#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll)) && !FMT_MSC_VER
-# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n)
-#endif
-#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctz))
-# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n)
+// https://github.com/fmtlib/fmt/issues/519.
+#if !FMT_MSC_VER
+# if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION
+# define FMT_BUILTIN_CLZ(n) __builtin_clz(n)
+# endif
+# if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION
+# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n)
+# endif
#endif
-#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctzll))
-# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n)
+
+// __builtin_ctz is broken in Intel Compiler Classic on Windows:
+// https://github.com/fmtlib/fmt/issues/2510.
+#ifndef __ICL
+# if FMT_HAS_BUILTIN(__builtin_ctz) || FMT_GCC_VERSION || FMT_ICC_VERSION
+# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n)
+# endif
+# if FMT_HAS_BUILTIN(__builtin_ctzll) || FMT_GCC_VERSION || FMT_ICC_VERSION
+# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n)
+# endif
#endif
#if FMT_MSC_VER
// Some compilers masquerade as both MSVC and GCC-likes or otherwise support
// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the
// MSVC intrinsics if the clz and clzll builtins are not available.
-#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && \
- !defined(FMT_BUILTIN_CTZLL) && !defined(_MANAGED)
+#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(FMT_BUILTIN_CTZLL)
FMT_BEGIN_NAMESPACE
namespace detail {
// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning.
-# ifndef __clang__
+# if !defined(__clang__)
# pragma intrinsic(_BitScanForward)
# pragma intrinsic(_BitScanReverse)
-# endif
-# if defined(_WIN64) && !defined(__clang__)
-# pragma intrinsic(_BitScanForward64)
-# pragma intrinsic(_BitScanReverse64)
+# if defined(_WIN64)
+# pragma intrinsic(_BitScanForward64)
+# pragma intrinsic(_BitScanReverse64)
+# endif
# endif
-inline int clz(uint32_t x) {
+inline auto clz(uint32_t x) -> int {
unsigned long r = 0;
_BitScanReverse(&r, x);
FMT_ASSERT(x != 0, "");
// Static analysis complains about using uninitialized data
// "r", but the only way that can happen is if "x" is 0,
// which the callers guarantee to not happen.
- FMT_SUPPRESS_MSC_WARNING(6102)
+ FMT_MSC_WARNING(suppress : 6102)
return 31 ^ static_cast<int>(r);
}
# define FMT_BUILTIN_CLZ(n) detail::clz(n)
-inline int clzll(uint64_t x) {
+inline auto clzll(uint64_t x) -> int {
unsigned long r = 0;
# ifdef _WIN64
_BitScanReverse64(&r, x);
_BitScanReverse(&r, static_cast<uint32_t>(x));
# endif
FMT_ASSERT(x != 0, "");
- FMT_SUPPRESS_MSC_WARNING(6102) // Suppress a bogus static analysis warning.
+ FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning.
return 63 ^ static_cast<int>(r);
}
# define FMT_BUILTIN_CLZLL(n) detail::clzll(n)
-inline int ctz(uint32_t x) {
+inline auto ctz(uint32_t x) -> int {
unsigned long r = 0;
_BitScanForward(&r, x);
FMT_ASSERT(x != 0, "");
- FMT_SUPPRESS_MSC_WARNING(6102) // Suppress a bogus static analysis warning.
+ FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning.
return static_cast<int>(r);
}
# define FMT_BUILTIN_CTZ(n) detail::ctz(n)
-inline int ctzll(uint64_t x) {
+inline auto ctzll(uint64_t x) -> int {
unsigned long r = 0;
FMT_ASSERT(x != 0, "");
- FMT_SUPPRESS_MSC_WARNING(6102) // Suppress a bogus static analysis warning.
+ FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning.
# ifdef _WIN64
_BitScanForward64(&r, x);
# else
FMT_END_NAMESPACE
#endif
-// Enable the deprecated numeric alignment.
-#ifndef FMT_DEPRECATED_NUMERIC_ALIGN
-# define FMT_DEPRECATED_NUMERIC_ALIGN 0
+#ifdef FMT_HEADER_ONLY
+# define FMT_HEADER_ONLY_CONSTEXPR20 FMT_CONSTEXPR20
+#else
+# define FMT_HEADER_ONLY_CONSTEXPR20
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
-// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't have
-// undefined behavior (e.g. due to type aliasing).
-// Example: uint64_t d = bit_cast<uint64_t>(2.718);
-template <typename Dest, typename Source>
-inline Dest bit_cast(const Source& source) {
- static_assert(sizeof(Dest) == sizeof(Source), "size mismatch");
- Dest dest;
- std::memcpy(&dest, &source, sizeof(dest));
- return dest;
+template <typename Streambuf> class formatbuf : public Streambuf {
+ private:
+ using char_type = typename Streambuf::char_type;
+ using streamsize = decltype(std::declval<Streambuf>().sputn(nullptr, 0));
+ using int_type = typename Streambuf::int_type;
+ using traits_type = typename Streambuf::traits_type;
+
+ buffer<char_type>& buffer_;
+
+ public:
+ explicit formatbuf(buffer<char_type>& buf) : buffer_(buf) {}
+
+ protected:
+ // The put area is always empty. This makes the implementation simpler and has
+ // the advantage that the streambuf and the buffer are always in sync and
+ // sputc never writes into uninitialized memory. A disadvantage is that each
+ // call to sputc always results in a (virtual) call to overflow. There is no
+ // disadvantage here for sputn since this always results in a call to xsputn.
+
+ auto overflow(int_type ch) -> int_type override {
+ if (!traits_type::eq_int_type(ch, traits_type::eof()))
+ buffer_.push_back(static_cast<char_type>(ch));
+ return ch;
+ }
+
+ auto xsputn(const char_type* s, streamsize count) -> streamsize override {
+ buffer_.append(s, s + count);
+ return count;
+ }
+};
+
+// Implementation of std::bit_cast for pre-C++20.
+template <typename To, typename From>
+FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To {
+ static_assert(sizeof(To) == sizeof(From), "size mismatch");
+#ifdef __cpp_lib_bit_cast
+ if (is_constant_evaluated()) return std::bit_cast<To>(from);
+#endif
+ auto to = To();
+ std::memcpy(&to, &from, sizeof(to));
+ return to;
}
-inline bool is_big_endian() {
- const auto u = 1u;
+inline auto is_big_endian() -> bool {
+#ifdef _WIN32
+ return false;
+#elif defined(__BIG_ENDIAN__)
+ return true;
+#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__)
+ return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__;
+#else
struct bytes {
- char data[sizeof(u)];
+ char data[sizeof(int)];
};
- return bit_cast<bytes>(u).data[0] == 0;
+ return bit_cast<bytes>(1).data[0] == 0;
+#endif
}
// A fallback implementation of uintptr_t for systems that lack it.
fallback_uintptr() = default;
explicit fallback_uintptr(const void* p) {
*this = bit_cast<fallback_uintptr>(p);
- if (is_big_endian()) {
+ if (const_check(is_big_endian())) {
for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j)
std::swap(value[i], value[j]);
}
};
#ifdef UINTPTR_MAX
using uintptr_t = ::uintptr_t;
-inline uintptr_t to_uintptr(const void* p) { return bit_cast<uintptr_t>(p); }
+inline auto to_uintptr(const void* p) -> uintptr_t {
+ return bit_cast<uintptr_t>(p);
+}
#else
using uintptr_t = fallback_uintptr;
-inline fallback_uintptr to_uintptr(const void* p) {
+inline auto to_uintptr(const void* p) -> fallback_uintptr {
return fallback_uintptr(p);
}
#endif
// Returns the largest possible value for type T. Same as
// std::numeric_limits<T>::max() but shorter and not affected by the max macro.
-template <typename T> constexpr T max_value() {
+template <typename T> constexpr auto max_value() -> T {
return (std::numeric_limits<T>::max)();
}
-template <typename T> constexpr int num_bits() {
+template <typename T> constexpr auto num_bits() -> int {
return std::numeric_limits<T>::digits;
}
// std::numeric_limits<T>::digits may return 0 for 128-bit ints.
-template <> constexpr int num_bits<int128_t>() { return 128; }
-template <> constexpr int num_bits<uint128_t>() { return 128; }
-template <> constexpr int num_bits<fallback_uintptr>() {
+template <> constexpr auto num_bits<int128_t>() -> int { return 128; }
+template <> constexpr auto num_bits<uint128_t>() -> int { return 128; }
+template <> constexpr auto num_bits<fallback_uintptr>() -> int {
return static_cast<int>(sizeof(void*) *
std::numeric_limits<unsigned char>::digits);
}
template <typename T> using sentinel_t = decltype(std::end(std::declval<T&>()));
// A workaround for std::string not having mutable data() until C++17.
-template <typename Char> inline Char* get_data(std::basic_string<Char>& s) {
+template <typename Char>
+inline auto get_data(std::basic_string<Char>& s) -> Char* {
return &s[0];
}
template <typename Container>
-inline typename Container::value_type* get_data(Container& c) {
+inline auto get_data(Container& c) -> typename Container::value_type* {
return c.data();
}
#if defined(_SECURE_SCL) && _SECURE_SCL
// Make a checked iterator to avoid MSVC warnings.
template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>;
-template <typename T> checked_ptr<T> make_checked(T* p, size_t size) {
+template <typename T>
+constexpr auto make_checked(T* p, size_t size) -> checked_ptr<T> {
return {p, size};
}
#else
template <typename T> using checked_ptr = T*;
-template <typename T> inline T* make_checked(T* p, size_t) { return p; }
+template <typename T> constexpr auto make_checked(T* p, size_t) -> T* {
+ return p;
+}
#endif
+// Attempts to reserve space for n extra characters in the output range.
+// Returns a pointer to the reserved range or a reference to it.
template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)>
-#if FMT_CLANG_VERSION
+#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION
__attribute__((no_sanitize("undefined")))
#endif
-inline checked_ptr<typename Container::value_type>
-reserve(std::back_insert_iterator<Container> it, size_t n) {
+inline auto
+reserve(std::back_insert_iterator<Container> it, size_t n)
+ -> checked_ptr<typename Container::value_type> {
Container& c = get_container(it);
size_t size = c.size();
c.resize(size + n);
}
template <typename T>
-inline buffer_appender<T> reserve(buffer_appender<T> it, size_t n) {
+inline auto reserve(buffer_appender<T> it, size_t n) -> buffer_appender<T> {
buffer<T>& buf = get_container(it);
buf.try_reserve(buf.size() + n);
return it;
}
-template <typename Iterator> inline Iterator& reserve(Iterator& it, size_t) {
+template <typename Iterator>
+constexpr auto reserve(Iterator& it, size_t) -> Iterator& {
return it;
}
+template <typename OutputIt>
+using reserve_iterator =
+ remove_reference_t<decltype(reserve(std::declval<OutputIt&>(), 0))>;
+
template <typename T, typename OutputIt>
-constexpr T* to_pointer(OutputIt, size_t) {
+constexpr auto to_pointer(OutputIt, size_t) -> T* {
return nullptr;
}
-template <typename T> T* to_pointer(buffer_appender<T> it, size_t n) {
+template <typename T> auto to_pointer(buffer_appender<T> it, size_t n) -> T* {
buffer<T>& buf = get_container(it);
auto size = buf.size();
if (buf.capacity() < size + n) return nullptr;
}
template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)>
-inline std::back_insert_iterator<Container> base_iterator(
- std::back_insert_iterator<Container>& it,
- checked_ptr<typename Container::value_type>) {
+inline auto base_iterator(std::back_insert_iterator<Container>& it,
+ checked_ptr<typename Container::value_type>)
+ -> std::back_insert_iterator<Container> {
return it;
}
template <typename Iterator>
-inline Iterator base_iterator(Iterator, Iterator it) {
+constexpr auto base_iterator(Iterator, Iterator it) -> Iterator {
return it;
}
-// An output iterator that counts the number of objects written to it and
-// discards them.
-class counting_iterator {
- private:
- size_t count_;
-
- public:
- using iterator_category = std::output_iterator_tag;
- using difference_type = std::ptrdiff_t;
- using pointer = void;
- using reference = void;
- using _Unchecked_type = counting_iterator; // Mark iterator as checked.
-
- struct value_type {
- template <typename T> void operator=(const T&) {}
- };
-
- counting_iterator() : count_(0) {}
-
- size_t count() const { return count_; }
-
- counting_iterator& operator++() {
- ++count_;
- return *this;
- }
- counting_iterator operator++(int) {
- auto it = *this;
- ++*this;
- return it;
- }
-
- friend counting_iterator operator+(counting_iterator it, difference_type n) {
- it.count_ += static_cast<size_t>(n);
- return it;
- }
-
- value_type operator*() const { return {}; }
-};
-
-template <typename OutputIt> class truncating_iterator_base {
- protected:
- OutputIt out_;
- size_t limit_;
- size_t count_;
-
- truncating_iterator_base(OutputIt out, size_t limit)
- : out_(out), limit_(limit), count_(0) {}
-
- public:
- using iterator_category = std::output_iterator_tag;
- using value_type = typename std::iterator_traits<OutputIt>::value_type;
- using difference_type = void;
- using pointer = void;
- using reference = void;
- using _Unchecked_type =
- truncating_iterator_base; // Mark iterator as checked.
-
- OutputIt base() const { return out_; }
- size_t count() const { return count_; }
-};
-
-// An output iterator that truncates the output and counts the number of objects
-// written to it.
-template <typename OutputIt,
- typename Enable = typename std::is_void<
- typename std::iterator_traits<OutputIt>::value_type>::type>
-class truncating_iterator;
-
-template <typename OutputIt>
-class truncating_iterator<OutputIt, std::false_type>
- : public truncating_iterator_base<OutputIt> {
- mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
-
- public:
- using value_type = typename truncating_iterator_base<OutputIt>::value_type;
-
- truncating_iterator(OutputIt out, size_t limit)
- : truncating_iterator_base<OutputIt>(out, limit) {}
-
- truncating_iterator& operator++() {
- if (this->count_++ < this->limit_) ++this->out_;
- return *this;
+// <algorithm> is spectacularly slow to compile in C++20 so use a simple fill_n
+// instead (#1998).
+template <typename OutputIt, typename Size, typename T>
+FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value)
+ -> OutputIt {
+ for (Size i = 0; i < count; ++i) *out++ = value;
+ return out;
+}
+template <typename T, typename Size>
+FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* {
+ if (is_constant_evaluated()) {
+ return fill_n<T*, Size, T>(out, count, value);
}
+ std::memset(out, value, to_unsigned(count));
+ return out + count;
+}
- truncating_iterator operator++(int) {
- auto it = *this;
- ++*this;
- return it;
- }
+#ifdef __cpp_char8_t
+using char8_type = char8_t;
+#else
+enum char8_type : unsigned char {};
+#endif
- value_type& operator*() const {
- return this->count_ < this->limit_ ? *this->out_ : blackhole_;
+template <typename OutChar, typename InputIt, typename OutputIt>
+FMT_CONSTEXPR FMT_NOINLINE auto copy_str_noinline(InputIt begin, InputIt end,
+ OutputIt out) -> OutputIt {
+ return copy_str<OutChar>(begin, end, out);
+}
+
+// A public domain branchless UTF-8 decoder by Christopher Wellons:
+// https://github.com/skeeto/branchless-utf8
+/* Decode the next character, c, from s, reporting errors in e.
+ *
+ * Since this is a branchless decoder, four bytes will be read from the
+ * buffer regardless of the actual length of the next character. This
+ * means the buffer _must_ have at least three bytes of zero padding
+ * following the end of the data stream.
+ *
+ * Errors are reported in e, which will be non-zero if the parsed
+ * character was somehow invalid: invalid byte sequence, non-canonical
+ * encoding, or a surrogate half.
+ *
+ * The function returns a pointer to the next character. When an error
+ * occurs, this pointer will be a guess that depends on the particular
+ * error, but it will always advance at least one byte.
+ */
+FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e)
+ -> const char* {
+ constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
+ constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
+ constexpr const int shiftc[] = {0, 18, 12, 6, 0};
+ constexpr const int shifte[] = {0, 6, 4, 2, 0};
+
+ int len = code_point_length(s);
+ const char* next = s + len;
+
+ // Assume a four-byte character and load four bytes. Unused bits are
+ // shifted out.
+ *c = uint32_t(s[0] & masks[len]) << 18;
+ *c |= uint32_t(s[1] & 0x3f) << 12;
+ *c |= uint32_t(s[2] & 0x3f) << 6;
+ *c |= uint32_t(s[3] & 0x3f) << 0;
+ *c >>= shiftc[len];
+
+ // Accumulate the various error conditions.
+ using uchar = unsigned char;
+ *e = (*c < mins[len]) << 6; // non-canonical encoding
+ *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half?
+ *e |= (*c > 0x10FFFF) << 8; // out of range?
+ *e |= (uchar(s[1]) & 0xc0) >> 2;
+ *e |= (uchar(s[2]) & 0xc0) >> 4;
+ *e |= uchar(s[3]) >> 6;
+ *e ^= 0x2a; // top two bits of each tail byte correct?
+ *e >>= shifte[len];
+
+ return next;
+}
+
+constexpr uint32_t invalid_code_point = ~uint32_t();
+
+// Invokes f(cp, sv) for every code point cp in s with sv being the string view
+// corresponding to the code point. cp is invalid_code_point on error.
+template <typename F>
+FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) {
+ auto decode = [f](const char* buf_ptr, const char* ptr) {
+ auto cp = uint32_t();
+ auto error = 0;
+ auto end = utf8_decode(buf_ptr, &cp, &error);
+ bool result = f(error ? invalid_code_point : cp,
+ string_view(ptr, to_unsigned(end - buf_ptr)));
+ return result ? end : nullptr;
+ };
+ auto p = s.data();
+ const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars.
+ if (s.size() >= block_size) {
+ for (auto end = p + s.size() - block_size + 1; p < end;) {
+ p = decode(p, p);
+ if (!p) return;
+ }
}
-};
-
-template <typename OutputIt>
-class truncating_iterator<OutputIt, std::true_type>
- : public truncating_iterator_base<OutputIt> {
- public:
- truncating_iterator(OutputIt out, size_t limit)
- : truncating_iterator_base<OutputIt>(out, limit) {}
-
- template <typename T> truncating_iterator& operator=(T val) {
- if (this->count_++ < this->limit_) *this->out_++ = val;
- return *this;
+ if (auto num_chars_left = s.data() + s.size() - p) {
+ char buf[2 * block_size - 1] = {};
+ copy_str<char>(p, p + num_chars_left, buf);
+ const char* buf_ptr = buf;
+ do {
+ auto end = decode(buf_ptr, p);
+ if (!end) return;
+ p += end - buf_ptr;
+ buf_ptr = end;
+ } while (buf_ptr - buf < num_chars_left);
}
-
- truncating_iterator& operator++() { return *this; }
- truncating_iterator& operator++(int) { return *this; }
- truncating_iterator& operator*() { return *this; }
-};
+}
template <typename Char>
-inline size_t count_code_points(basic_string_view<Char> s) {
+inline auto compute_width(basic_string_view<Char> s) -> size_t {
return s.size();
}
-// Counts the number of code points in a UTF-8 string.
-inline size_t count_code_points(basic_string_view<char> s) {
- const char* data = s.data();
+// Computes approximate display width of a UTF-8 string.
+FMT_CONSTEXPR inline size_t compute_width(string_view s) {
size_t num_code_points = 0;
- for (size_t i = 0, size = s.size(); i != size; ++i) {
- if ((data[i] & 0xc0) != 0x80) ++num_code_points;
- }
+ // It is not a lambda for compatibility with C++14.
+ struct count_code_points {
+ size_t* count;
+ FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool {
+ *count += detail::to_unsigned(
+ 1 +
+ (cp >= 0x1100 &&
+ (cp <= 0x115f || // Hangul Jamo init. consonants
+ cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET
+ cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET
+ // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE:
+ (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) ||
+ (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables
+ (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs
+ (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms
+ (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms
+ (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms
+ (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms
+ (cp >= 0x20000 && cp <= 0x2fffd) || // CJK
+ (cp >= 0x30000 && cp <= 0x3fffd) ||
+ // Miscellaneous Symbols and Pictographs + Emoticons:
+ (cp >= 0x1f300 && cp <= 0x1f64f) ||
+ // Supplemental Symbols and Pictographs:
+ (cp >= 0x1f900 && cp <= 0x1f9ff))));
+ return true;
+ }
+ };
+ for_each_codepoint(s, count_code_points{&num_code_points});
return num_code_points;
}
-inline size_t count_code_points(basic_string_view<char8_type> s) {
- return count_code_points(basic_string_view<char>(
+inline auto compute_width(basic_string_view<char8_type> s) -> size_t {
+ return compute_width(basic_string_view<char>(
reinterpret_cast<const char*>(s.data()), s.size()));
}
template <typename Char>
-inline size_t code_point_index(basic_string_view<Char> s, size_t n) {
+inline auto code_point_index(basic_string_view<Char> s, size_t n) -> size_t {
size_t size = s.size();
return n < size ? n : size;
}
// Calculates the index of the nth code point in a UTF-8 string.
-inline size_t code_point_index(basic_string_view<char8_type> s, size_t n) {
+inline auto code_point_index(basic_string_view<char8_type> s, size_t n)
+ -> size_t {
const char8_type* data = s.data();
size_t num_code_points = 0;
for (size_t i = 0, size = s.size(); i != size; ++i) {
- if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) {
- return i;
- }
+ if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i;
}
return s.size();
}
-template <typename InputIt, typename OutChar>
-using needs_conversion = bool_constant<
- std::is_same<typename std::iterator_traits<InputIt>::value_type,
- char>::value &&
- std::is_same<OutChar, char8_type>::value>;
-
-template <typename OutChar, typename InputIt, typename OutputIt,
- FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)>
-OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) {
- return std::copy(begin, end, it);
-}
-
-template <typename OutChar, typename InputIt, typename OutputIt,
- FMT_ENABLE_IF(needs_conversion<InputIt, OutChar>::value)>
-OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) {
- return std::transform(begin, end, it,
- [](char c) { return static_cast<char8_type>(c); });
-}
-
-template <typename Char, typename InputIt>
-inline counting_iterator copy_str(InputIt begin, InputIt end,
- counting_iterator it) {
- return it + (end - begin);
-}
-
-template <typename T>
-using is_fast_float = bool_constant<std::numeric_limits<T>::is_iec559 &&
- sizeof(T) <= sizeof(double)>;
+template <typename T, bool = std::is_floating_point<T>::value>
+struct is_fast_float : bool_constant<std::numeric_limits<T>::is_iec559 &&
+ sizeof(T) <= sizeof(double)> {};
+template <typename T> struct is_fast_float<T, false> : std::false_type {};
#ifndef FMT_USE_FULL_CACHE_DRAGONBOX
# define FMT_USE_FULL_CACHE_DRAGONBOX 0
template <typename T>
template <typename U>
void buffer<T>::append(const U* begin, const U* end) {
- do {
+ while (begin != end) {
auto count = to_unsigned(end - begin);
try_reserve(size_ + count);
auto free_cap = capacity_ - size_;
std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count));
size_ += count;
begin += count;
- } while (begin != end);
+ }
}
-template <typename OutputIt, typename T, typename Traits>
-void iterator_buffer<OutputIt, T, Traits>::flush() {
- out_ = std::copy_n(data_, this->limit(this->size()), out_);
- this->clear();
-}
+template <typename T, typename Enable = void>
+struct is_locale : std::false_type {};
+template <typename T>
+struct is_locale<T, void_t<decltype(T::classic())>> : std::true_type {};
} // namespace detail
+FMT_MODULE_EXPORT_BEGIN
+
// The number of characters to store in the basic_memory_buffer object itself
// to avoid dynamic memory allocation.
enum { inline_buffer_size = 500 };
A dynamically growing memory buffer for trivially copyable/constructible types
with the first ``SIZE`` elements stored in the object itself.
- You can use one of the following type aliases for common character types:
-
- +----------------+------------------------------+
- | Type | Definition |
- +================+==============================+
- | memory_buffer | basic_memory_buffer<char> |
- +----------------+------------------------------+
- | wmemory_buffer | basic_memory_buffer<wchar_t> |
- +----------------+------------------------------+
+ You can use the ``memory_buffer`` type alias for ``char`` instead.
**Example**::
- fmt::memory_buffer out;
- format_to(out, "The answer is {}.", 42);
+ auto out = fmt::memory_buffer();
+ format_to(std::back_inserter(out), "The answer is {}.", 42);
This will append the following output to the ``out`` object:
Allocator alloc_;
// Deallocate memory allocated by the buffer.
- void deallocate() {
+ FMT_CONSTEXPR20 void deallocate() {
T* data = this->data();
if (data != store_) alloc_.deallocate(data, this->capacity());
}
protected:
- void grow(size_t size) final FMT_OVERRIDE;
+ FMT_CONSTEXPR20 void grow(size_t size) override;
public:
using value_type = T;
using const_reference = const T&;
- explicit basic_memory_buffer(const Allocator& alloc = Allocator())
+ FMT_CONSTEXPR20 explicit basic_memory_buffer(
+ const Allocator& alloc = Allocator())
: alloc_(alloc) {
this->set(store_, SIZE);
+ if (detail::is_constant_evaluated()) {
+ detail::fill_n(store_, SIZE, T{});
+ }
}
- ~basic_memory_buffer() { deallocate(); }
+ FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); }
private:
// Move data from other to this buffer.
- void move(basic_memory_buffer& other) {
+ FMT_CONSTEXPR20 void move(basic_memory_buffer& other) {
alloc_ = std::move(other.alloc_);
T* data = other.data();
size_t size = other.size(), capacity = other.capacity();
if (data == other.store_) {
this->set(store_, capacity);
- std::uninitialized_copy(other.store_, other.store_ + size,
- detail::make_checked(store_, capacity));
+ if (detail::is_constant_evaluated()) {
+ detail::copy_str<T>(other.store_, other.store_ + size,
+ detail::make_checked(store_, capacity));
+ } else {
+ std::uninitialized_copy(other.store_, other.store_ + size,
+ detail::make_checked(store_, capacity));
+ }
} else {
this->set(data, capacity);
// Set pointer to the inline array so that delete is not called
of the other object to it.
\endrst
*/
- basic_memory_buffer(basic_memory_buffer&& other) FMT_NOEXCEPT { move(other); }
+ FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other)
+ FMT_NOEXCEPT {
+ move(other);
+ }
/**
\rst
Moves the content of the other ``basic_memory_buffer`` object to this one.
\endrst
*/
- basic_memory_buffer& operator=(basic_memory_buffer&& other) FMT_NOEXCEPT {
+ auto operator=(basic_memory_buffer&& other) FMT_NOEXCEPT
+ -> basic_memory_buffer& {
FMT_ASSERT(this != &other, "");
deallocate();
move(other);
}
// Returns a copy of the allocator associated with this buffer.
- Allocator get_allocator() const { return alloc_; }
+ auto get_allocator() const -> Allocator { return alloc_; }
/**
Resizes the buffer to contain *count* elements. If T is a POD type new
elements may not be initialized.
*/
- void resize(size_t count) { this->try_resize(count); }
+ FMT_CONSTEXPR20 void resize(size_t count) { this->try_resize(count); }
/** Increases the buffer capacity to *new_capacity*. */
void reserve(size_t new_capacity) { this->try_reserve(new_capacity); }
};
template <typename T, size_t SIZE, typename Allocator>
-void basic_memory_buffer<T, SIZE, Allocator>::grow(size_t size) {
+FMT_CONSTEXPR20 void basic_memory_buffer<T, SIZE, Allocator>::grow(
+ size_t size) {
#ifdef FMT_FUZZ
if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much");
#endif
+ const size_t max_size = std::allocator_traits<Allocator>::max_size(alloc_);
size_t old_capacity = this->capacity();
size_t new_capacity = old_capacity + old_capacity / 2;
- if (size > new_capacity) new_capacity = size;
+ if (size > new_capacity)
+ new_capacity = size;
+ else if (new_capacity > max_size)
+ new_capacity = size > max_size ? size : max_size;
T* old_data = this->data();
T* new_data =
std::allocator_traits<Allocator>::allocate(alloc_, new_capacity);
}
using memory_buffer = basic_memory_buffer<char>;
-using wmemory_buffer = basic_memory_buffer<wchar_t>;
template <typename T, size_t SIZE, typename Allocator>
struct is_contiguous<basic_memory_buffer<T, SIZE, Allocator>> : std::true_type {
};
+namespace detail {
+FMT_API void print(std::FILE*, string_view);
+}
+
/** A formatting error such as invalid format string. */
FMT_CLASS_API
class FMT_API format_error : public std::runtime_error {
format_error& operator=(const format_error&) = default;
format_error(format_error&&) = default;
format_error& operator=(format_error&&) = default;
- ~format_error() FMT_NOEXCEPT FMT_OVERRIDE;
+ ~format_error() FMT_NOEXCEPT override FMT_MSC_DEFAULT;
};
-namespace detail {
+/**
+ \rst
+ Constructs a `~fmt::format_arg_store` object that contains references
+ to arguments and can be implicitly converted to `~fmt::format_args`.
+ If ``fmt`` is a compile-time string then `make_args_checked` checks
+ its validity at compile time.
+ \endrst
+ */
+template <typename... Args, typename S, typename Char = char_t<S>>
+FMT_INLINE auto make_args_checked(const S& fmt,
+ const remove_reference_t<Args>&... args)
+ -> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
+ static_assert(
+ detail::count<(
+ std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
+ std::is_reference<Args>::value)...>() == 0,
+ "passing views as lvalues is disallowed");
+ detail::check_format_string<Args...>(fmt);
+ return {args...};
+}
+
+// compile-time support
+namespace detail_exported {
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <typename Char, size_t N> struct fixed_string {
+ constexpr fixed_string(const Char (&str)[N]) {
+ detail::copy_str<Char, const Char*, Char*>(static_cast<const Char*>(str),
+ str + N, data);
+ }
+ Char data[N]{};
+};
+#endif
+
+// Converts a compile-time string to basic_string_view.
+template <typename Char, size_t N>
+constexpr auto compile_string_to_view(const Char (&s)[N])
+ -> basic_string_view<Char> {
+ // Remove trailing NUL character if needed. Won't be present if this is used
+ // with a raw character array (i.e. not defined as a string).
+ return {s, N - (std::char_traits<Char>::to_int_type(s[N - 1]) == 0 ? 1 : 0)};
+}
+template <typename Char>
+constexpr auto compile_string_to_view(detail::std_string_view<Char> s)
+ -> basic_string_view<Char> {
+ return {s.data(), s.size()};
+}
+} // namespace detail_exported
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+template <typename T> struct is_integral : std::is_integral<T> {};
+template <> struct is_integral<int128_t> : std::true_type {};
+template <> struct is_integral<uint128_t> : std::true_type {};
template <typename T>
using is_signed =
// Returns true if value is negative, false otherwise.
// Same as `value < 0` but doesn't produce warnings if T is an unsigned type.
template <typename T, FMT_ENABLE_IF(is_signed<T>::value)>
-FMT_CONSTEXPR bool is_negative(T value) {
+FMT_CONSTEXPR auto is_negative(T value) -> bool {
return value < 0;
}
template <typename T, FMT_ENABLE_IF(!is_signed<T>::value)>
-FMT_CONSTEXPR bool is_negative(T) {
+FMT_CONSTEXPR auto is_negative(T) -> bool {
return false;
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
-FMT_CONSTEXPR bool is_supported_floating_point(T) {
+FMT_CONSTEXPR auto is_supported_floating_point(T) -> uint16_t {
return (std::is_same<T, float>::value && FMT_USE_FLOAT) ||
(std::is_same<T, double>::value && FMT_USE_DOUBLE) ||
(std::is_same<T, long double>::value && FMT_USE_LONG_DOUBLE);
conditional_t<num_bits<T>() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS,
uint32_t,
conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>>;
-
-// 128-bit integer type used internally
-struct FMT_EXTERN_TEMPLATE_API uint128_wrapper {
- uint128_wrapper() = default;
-
-#if FMT_USE_INT128
- uint128_t internal_;
-
- uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT
- : internal_{static_cast<uint128_t>(low) |
- (static_cast<uint128_t>(high) << 64)} {}
-
- uint128_wrapper(uint128_t u) : internal_{u} {}
-
- uint64_t high() const FMT_NOEXCEPT { return uint64_t(internal_ >> 64); }
- uint64_t low() const FMT_NOEXCEPT { return uint64_t(internal_); }
-
- uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT {
- internal_ += n;
- return *this;
- }
-#else
- uint64_t high_;
- uint64_t low_;
-
- uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT : high_{high},
- low_{low} {}
-
- uint64_t high() const FMT_NOEXCEPT { return high_; }
- uint64_t low() const FMT_NOEXCEPT { return low_; }
-
- uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT {
-# if defined(_MSC_VER) && defined(_M_X64)
- unsigned char carry = _addcarry_u64(0, low_, n, &low_);
- _addcarry_u64(carry, high_, 0, &high_);
- return *this;
-# else
- uint64_t sum = low_ + n;
- high_ += (sum < low_ ? 1 : 0);
- low_ = sum;
- return *this;
-# endif
- }
-#endif
-};
-
-// Table entry type for divisibility test used internally
-template <typename T> struct FMT_EXTERN_TEMPLATE_API divtest_table_entry {
- T mod_inv;
- T max_quotient;
-};
-
-// Static data is placed in this class template for the header-only config.
-template <typename T = void> struct FMT_EXTERN_TEMPLATE_API basic_data {
- static const uint64_t powers_of_10_64[];
- static const uint32_t zero_or_powers_of_10_32_new[];
- static const uint64_t zero_or_powers_of_10_64_new[];
- static const uint64_t grisu_pow10_significands[];
- static const int16_t grisu_pow10_exponents[];
- static const divtest_table_entry<uint32_t> divtest_table_for_pow5_32[];
- static const divtest_table_entry<uint64_t> divtest_table_for_pow5_64[];
- static const uint64_t dragonbox_pow10_significands_64[];
- static const uint128_wrapper dragonbox_pow10_significands_128[];
- // log10(2) = 0x0.4d104d427de7fbcc...
- static const uint64_t log10_2_significand = 0x4d104d427de7fbcc;
-#if !FMT_USE_FULL_CACHE_DRAGONBOX
- static const uint64_t powers_of_5_64[];
- static const uint32_t dragonbox_pow10_recovery_errors[];
+template <typename T>
+using uint64_or_128_t = conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>;
+
+#define FMT_POWERS_OF_10(factor) \
+ factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \
+ (factor)*1000000, (factor)*10000000, (factor)*100000000, \
+ (factor)*1000000000
+
+// Converts value in the range [0, 100) to a string.
+constexpr const char* digits2(size_t value) {
+ // GCC generates slightly better code when value is pointer-size.
+ return &"0001020304050607080910111213141516171819"
+ "2021222324252627282930313233343536373839"
+ "4041424344454647484950515253545556575859"
+ "6061626364656667686970717273747576777879"
+ "8081828384858687888990919293949596979899"[value * 2];
+}
+
+// Sign is a template parameter to workaround a bug in gcc 4.8.
+template <typename Char, typename Sign> constexpr Char sign(Sign s) {
+#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604
+ static_assert(std::is_same<Sign, sign_t>::value, "");
#endif
- // GCC generates slightly better code for pairs than chars.
- using digit_pair = char[2];
- static const digit_pair digits[];
- static const char hex_digits[];
- static const char foreground_color[];
- static const char background_color[];
- static const char reset_color[5];
- static const wchar_t wreset_color[5];
- static const char signs[];
- static const char left_padding_shifts[5];
- static const char right_padding_shifts[5];
-
- // DEPRECATED! These are for ABI compatibility.
- static const uint32_t zero_or_powers_of_10_32[];
- static const uint64_t zero_or_powers_of_10_64[];
-};
-
-// Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)).
-// This is a function instead of an array to workaround a bug in GCC10 (#1810).
-FMT_INLINE uint16_t bsr2log10(int bsr) {
- static constexpr uint16_t data[] = {
- 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5,
- 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
- 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,
- 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};
- return data[bsr];
+ return static_cast<Char>("\0-+ "[s]);
}
-#ifndef FMT_EXPORTED
-FMT_EXTERN template struct basic_data<void>;
-#endif
-
-// This is a struct rather than an alias to avoid shadowing warnings in gcc.
-struct data : basic_data<> {};
-
-#ifdef FMT_BUILTIN_CLZLL
-// Returns the number of decimal digits in n. Leading zeros are not counted
-// except for n == 0 in which case count_digits returns 1.
-inline int count_digits(uint64_t n) {
- // https://github.com/fmtlib/format-benchmark/blob/master/digits10
- auto t = bsr2log10(FMT_BUILTIN_CLZLL(n | 1) ^ 63);
- return t - (n < data::zero_or_powers_of_10_64_new[t]);
-}
-#else
-// Fallback version of count_digits used when __builtin_clz is not available.
-inline int count_digits(uint64_t n) {
+template <typename T> FMT_CONSTEXPR auto count_digits_fallback(T n) -> int {
int count = 1;
for (;;) {
// Integer division is slow so do it for a group of four digits instead
count += 4;
}
}
-#endif
-
#if FMT_USE_INT128
-inline int count_digits(uint128_t n) {
- int count = 1;
- for (;;) {
- // Integer division is slow so do it for a group of four digits instead
- // of for every digit. The idea comes from the talk by Alexandrescu
- // "Three Optimization Tips for C++". See speed-test for a comparison.
- if (n < 10) return count;
- if (n < 100) return count + 1;
- if (n < 1000) return count + 2;
- if (n < 10000) return count + 3;
- n /= 10000U;
- count += 4;
- }
+FMT_CONSTEXPR inline auto count_digits(uint128_t n) -> int {
+ return count_digits_fallback(n);
}
#endif
-// Counts the number of digits in n. BITS = log2(radix).
-template <unsigned BITS, typename UInt> inline int count_digits(UInt n) {
- int num_digits = 0;
- do {
- ++num_digits;
- } while ((n >>= BITS) != 0);
- return num_digits;
+#ifdef FMT_BUILTIN_CLZLL
+// It is a separate function rather than a part of count_digits to workaround
+// the lack of static constexpr in constexpr functions.
+inline auto do_count_digits(uint64_t n) -> int {
+ // This has comparable performance to the version by Kendall Willets
+ // (https://github.com/fmtlib/format-benchmark/blob/master/digits10)
+ // but uses smaller tables.
+ // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)).
+ static constexpr uint8_t bsr2log10[] = {
+ 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5,
+ 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
+ 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,
+ 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};
+ auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63];
+ static constexpr const uint64_t zero_or_powers_of_10[] = {
+ 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL),
+ 10000000000000000000ULL};
+ return t - (n < zero_or_powers_of_10[t]);
}
+#endif
-template <> int count_digits<4>(detail::fallback_uintptr n);
-
-#if FMT_GCC_VERSION || FMT_CLANG_VERSION
-# define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
-#elif FMT_MSC_VER
-# define FMT_ALWAYS_INLINE __forceinline
-#else
-# define FMT_ALWAYS_INLINE inline
+// Returns the number of decimal digits in n. Leading zeros are not counted
+// except for n == 0 in which case count_digits returns 1.
+FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int {
+#ifdef FMT_BUILTIN_CLZLL
+ if (!is_constant_evaluated()) {
+ return do_count_digits(n);
+ }
#endif
+ return count_digits_fallback(n);
+}
-// To suppress unnecessary security cookie checks
-#if FMT_MSC_VER && !FMT_CLANG_VERSION
-# define FMT_SAFEBUFFERS __declspec(safebuffers)
-#else
-# define FMT_SAFEBUFFERS
+// Counts the number of digits in n. BITS = log2(radix).
+template <int BITS, typename UInt>
+FMT_CONSTEXPR auto count_digits(UInt n) -> int {
+#ifdef FMT_BUILTIN_CLZ
+ if (num_bits<UInt>() == 32)
+ return (FMT_BUILTIN_CLZ(static_cast<uint32_t>(n) | 1) ^ 31) / BITS + 1;
#endif
+ // Lambda avoids unreachable code warnings from NVHPC.
+ return [](UInt m) {
+ int num_digits = 0;
+ do {
+ ++num_digits;
+ } while ((m >>= BITS) != 0);
+ return num_digits;
+ }(n);
+}
+
+template <> auto count_digits<4>(detail::fallback_uintptr n) -> int;
#ifdef FMT_BUILTIN_CLZ
-// Optional version of count_digits for better performance on 32-bit platforms.
-inline int count_digits(uint32_t n) {
- auto t = bsr2log10(FMT_BUILTIN_CLZ(n | 1) ^ 31);
- return t - (n < data::zero_or_powers_of_10_32_new[t]);
+// It is a separate function rather than a part of count_digits to workaround
+// the lack of static constexpr in constexpr functions.
+FMT_INLINE auto do_count_digits(uint32_t n) -> int {
+// An optimization by Kendall Willets from https://bit.ly/3uOIQrB.
+// This increments the upper 32 bits (log10(T) - 1) when >= T is added.
+# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T)
+ static constexpr uint64_t table[] = {
+ FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8
+ FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64
+ FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512
+ FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096
+ FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k
+ FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k
+ FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k
+ FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M
+ FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M
+ FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M
+ FMT_INC(1000000000), FMT_INC(1000000000) // 4B
+ };
+ auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31];
+ return static_cast<int>((n + inc) >> 32);
}
#endif
-template <typename Int> constexpr int digits10() FMT_NOEXCEPT {
- return std::numeric_limits<Int>::digits10;
+// Optional version of count_digits for better performance on 32-bit platforms.
+FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int {
+#ifdef FMT_BUILTIN_CLZ
+ if (!is_constant_evaluated()) {
+ return do_count_digits(n);
+ }
+#endif
+ return count_digits_fallback(n);
}
-template <> constexpr int digits10<int128_t>() FMT_NOEXCEPT { return 38; }
-template <> constexpr int digits10<uint128_t>() FMT_NOEXCEPT { return 38; }
-template <typename Char> FMT_API std::string grouping_impl(locale_ref loc);
-template <typename Char> inline std::string grouping(locale_ref loc) {
- return grouping_impl<char>(loc);
+template <typename Int> constexpr auto digits10() FMT_NOEXCEPT -> int {
+ return std::numeric_limits<Int>::digits10;
+}
+template <> constexpr auto digits10<int128_t>() FMT_NOEXCEPT -> int {
+ return 38;
}
-template <> inline std::string grouping<wchar_t>(locale_ref loc) {
- return grouping_impl<wchar_t>(loc);
+template <> constexpr auto digits10<uint128_t>() FMT_NOEXCEPT -> int {
+ return 38;
}
-template <typename Char> FMT_API Char thousands_sep_impl(locale_ref loc);
-template <typename Char> inline Char thousands_sep(locale_ref loc) {
- return Char(thousands_sep_impl<char>(loc));
+template <typename Char> struct thousands_sep_result {
+ std::string grouping;
+ Char thousands_sep;
+};
+
+template <typename Char>
+FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char>;
+template <typename Char>
+inline auto thousands_sep(locale_ref loc) -> thousands_sep_result<Char> {
+ auto result = thousands_sep_impl<char>(loc);
+ return {result.grouping, Char(result.thousands_sep)};
}
-template <> inline wchar_t thousands_sep(locale_ref loc) {
+template <>
+inline auto thousands_sep(locale_ref loc) -> thousands_sep_result<wchar_t> {
return thousands_sep_impl<wchar_t>(loc);
}
-template <typename Char> FMT_API Char decimal_point_impl(locale_ref loc);
-template <typename Char> inline Char decimal_point(locale_ref loc) {
+template <typename Char>
+FMT_API auto decimal_point_impl(locale_ref loc) -> Char;
+template <typename Char> inline auto decimal_point(locale_ref loc) -> Char {
return Char(decimal_point_impl<char>(loc));
}
-template <> inline wchar_t decimal_point(locale_ref loc) {
+template <> inline auto decimal_point(locale_ref loc) -> wchar_t {
return decimal_point_impl<wchar_t>(loc);
}
// Compares two characters for equality.
-template <typename Char> bool equal2(const Char* lhs, const char* rhs) {
- return lhs[0] == rhs[0] && lhs[1] == rhs[1];
+template <typename Char> auto equal2(const Char* lhs, const char* rhs) -> bool {
+ return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]);
}
-inline bool equal2(const char* lhs, const char* rhs) {
+inline auto equal2(const char* lhs, const char* rhs) -> bool {
return memcmp(lhs, rhs, 2) == 0;
}
// Copies two characters from src to dst.
-template <typename Char> void copy2(Char* dst, const char* src) {
+template <typename Char>
+FMT_CONSTEXPR20 FMT_INLINE void copy2(Char* dst, const char* src) {
+ if (!is_constant_evaluated() && sizeof(Char) == sizeof(char)) {
+ memcpy(dst, src, 2);
+ return;
+ }
*dst++ = static_cast<Char>(*src++);
*dst = static_cast<Char>(*src);
}
-FMT_INLINE void copy2(char* dst, const char* src) { memcpy(dst, src, 2); }
template <typename Iterator> struct format_decimal_result {
Iterator begin;
// buffer of specified size. The caller must ensure that the buffer is large
// enough.
template <typename Char, typename UInt>
-inline format_decimal_result<Char*> format_decimal(Char* out, UInt value,
- int size) {
+FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size)
+ -> format_decimal_result<Char*> {
FMT_ASSERT(size >= count_digits(value), "invalid digit count");
out += size;
Char* end = out;
// of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison.
out -= 2;
- copy2(out, data::digits[value % 100]);
+ copy2(out, digits2(static_cast<size_t>(value % 100)));
value /= 100;
}
if (value < 10) {
return {out, end};
}
out -= 2;
- copy2(out, data::digits[value]);
+ copy2(out, digits2(static_cast<size_t>(value)));
return {out, end};
}
template <typename Char, typename UInt, typename Iterator,
FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<Iterator>>::value)>
-inline format_decimal_result<Iterator> format_decimal(Iterator out, UInt value,
- int size) {
+inline auto format_decimal(Iterator out, UInt value, int size)
+ -> format_decimal_result<Iterator> {
// Buffer is large enough to hold all digits (digits10 + 1).
Char buffer[digits10<UInt>() + 1];
auto end = format_decimal(buffer, value, size).end;
- return {out, detail::copy_str<Char>(buffer, end, out)};
+ return {out, detail::copy_str_noinline<Char>(buffer, end, out)};
}
template <unsigned BASE_BITS, typename Char, typename UInt>
-inline Char* format_uint(Char* buffer, UInt value, int num_digits,
- bool upper = false) {
+FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits,
+ bool upper = false) -> Char* {
buffer += num_digits;
Char* end = buffer;
do {
- const char* digits = upper ? "0123456789ABCDEF" : data::hex_digits;
+ const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef";
unsigned digit = (value & ((1 << BASE_BITS) - 1));
*--buffer = static_cast<Char>(BASE_BITS < 4 ? static_cast<char>('0' + digit)
: digits[digit]);
}
template <unsigned BASE_BITS, typename Char>
-Char* format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits,
- bool = false) {
+auto format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits,
+ bool = false) -> Char* {
auto char_digits = std::numeric_limits<unsigned char>::digits / 4;
int start = (num_digits + char_digits - 1) / char_digits - 1;
if (int start_digits = num_digits % char_digits) {
auto p = buffer;
for (int i = 0; i < char_digits; ++i) {
unsigned digit = (value & ((1 << BASE_BITS) - 1));
- *--p = static_cast<Char>(data::hex_digits[digit]);
+ *--p = static_cast<Char>("0123456789abcdef"[digit]);
value >>= BASE_BITS;
}
}
}
template <unsigned BASE_BITS, typename Char, typename It, typename UInt>
-inline It format_uint(It out, UInt value, int num_digits, bool upper = false) {
+inline auto format_uint(It out, UInt value, int num_digits, bool upper = false)
+ -> It {
if (auto ptr = to_pointer<Char>(out, to_unsigned(num_digits))) {
format_uint<BASE_BITS>(ptr, value, num_digits, upper);
return out;
// Buffer should be large enough to hold all digits (digits / BASE_BITS + 1).
char buffer[num_bits<UInt>() / BASE_BITS + 1];
format_uint<BASE_BITS>(buffer, value, num_digits, upper);
- return detail::copy_str<Char>(buffer, buffer + num_digits, out);
+ return detail::copy_str_noinline<Char>(buffer, buffer + num_digits, out);
}
// A converter from UTF-8 to UTF-16.
class utf8_to_utf16 {
private:
- wmemory_buffer buffer_;
+ basic_memory_buffer<wchar_t> buffer_;
public:
FMT_API explicit utf8_to_utf16(string_view s);
- operator wstring_view() const { return {&buffer_[0], size()}; }
- size_t size() const { return buffer_.size() - 1; }
- const wchar_t* c_str() const { return &buffer_[0]; }
- std::wstring str() const { return {&buffer_[0], size()}; }
+ operator basic_string_view<wchar_t>() const { return {&buffer_[0], size()}; }
+ auto size() const -> size_t { return buffer_.size() - 1; }
+ auto c_str() const -> const wchar_t* { return &buffer_[0]; }
+ auto str() const -> std::wstring { return {&buffer_[0], size()}; }
};
-template <typename T = void> struct null {};
+namespace dragonbox {
-// Workaround an array initialization issue in gcc 4.8.
-template <typename Char> struct fill_t {
- private:
- enum { max_size = 4 };
- Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)};
- unsigned char size_ = 1;
-
- public:
- FMT_CONSTEXPR void operator=(basic_string_view<Char> s) {
- auto size = s.size();
- if (size > max_size) {
- FMT_THROW(format_error("invalid fill"));
- return;
- }
- for (size_t i = 0; i < size; ++i) data_[i] = s[i];
- size_ = static_cast<unsigned char>(size);
- }
-
- size_t size() const { return size_; }
- const Char* data() const { return data_; }
-
- FMT_CONSTEXPR Char& operator[](size_t index) { return data_[index]; }
- FMT_CONSTEXPR const Char& operator[](size_t index) const {
- return data_[index];
- }
-};
-} // namespace detail
-
-// We cannot use enum classes as bit fields because of a gcc bug
-// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414.
-namespace align {
-enum type { none, left, right, center, numeric };
-}
-using align_t = align::type;
-
-namespace sign {
-enum type { none, minus, plus, space };
-}
-using sign_t = sign::type;
-
-// Format specifiers for built-in and string types.
-template <typename Char> struct basic_format_specs {
- int width;
- int precision;
- char type;
- align_t align : 4;
- sign_t sign : 3;
- bool alt : 1; // Alternate form ('#').
- detail::fill_t<Char> fill;
-
- constexpr basic_format_specs()
- : width(0),
- precision(-1),
- type(0),
- align(align::none),
- sign(sign::none),
- alt(false) {}
-};
-
-using format_specs = basic_format_specs<char>;
-
-namespace detail {
-namespace dragonbox {
-
-// Type-specific information that Dragonbox uses.
-template <class T> struct float_info;
+// Type-specific information that Dragonbox uses.
+template <class T> struct float_info;
template <> struct float_info<float> {
using carrier_uint = uint32_t;
int exponent;
};
-template <typename T> FMT_API decimal_fp<T> to_decimal(T x) FMT_NOEXCEPT;
+template <typename T>
+FMT_API auto to_decimal(T x) FMT_NOEXCEPT -> decimal_fp<T>;
} // namespace dragonbox
template <typename T>
-constexpr typename dragonbox::float_info<T>::carrier_uint exponent_mask() {
+constexpr auto exponent_mask() ->
+ typename dragonbox::float_info<T>::carrier_uint {
using uint = typename dragonbox::float_info<T>::carrier_uint;
return ((uint(1) << dragonbox::float_info<T>::exponent_bits) - 1)
<< dragonbox::float_info<T>::significand_bits;
}
-// A floating-point presentation format.
-enum class float_format : unsigned char {
- general, // General: exponent notation or fixed point based on magnitude.
- exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3.
- fixed, // Fixed point with the default precision of 6, e.g. 0.0012.
- hex
-};
-
-struct float_specs {
- int precision;
- float_format format : 8;
- sign_t sign : 8;
- bool upper : 1;
- bool locale : 1;
- bool binary32 : 1;
- bool use_grisu : 1;
- bool showpoint : 1;
-};
-
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
-template <typename Char, typename It> It write_exponent(int exp, It it) {
+template <typename Char, typename It>
+FMT_CONSTEXPR auto write_exponent(int exp, It it) -> It {
FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range");
if (exp < 0) {
*it++ = static_cast<Char>('-');
*it++ = static_cast<Char>('+');
}
if (exp >= 100) {
- const char* top = data::digits[exp / 100];
+ const char* top = digits2(to_unsigned(exp / 100));
if (exp >= 1000) *it++ = static_cast<Char>(top[0]);
*it++ = static_cast<Char>(top[1]);
exp %= 100;
}
- const char* d = data::digits[exp];
+ const char* d = digits2(to_unsigned(exp));
*it++ = static_cast<Char>(d[0]);
*it++ = static_cast<Char>(d[1]);
return it;
}
template <typename T>
-int format_float(T value, int precision, float_specs specs, buffer<char>& buf);
+FMT_HEADER_ONLY_CONSTEXPR20 auto format_float(T value, int precision,
+ float_specs specs,
+ buffer<char>& buf) -> int;
// Formats a floating-point number with snprintf.
template <typename T>
-int snprintf_float(T value, int precision, float_specs specs,
- buffer<char>& buf);
-
-template <typename T> T promote_float(T value) { return value; }
-inline double promote_float(float value) { return static_cast<double>(value); }
-
-template <typename Handler>
-FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler&& handler) {
- switch (spec) {
- case 0:
- case 'd':
- handler.on_dec();
- break;
- case 'x':
- case 'X':
- handler.on_hex();
- break;
- case 'b':
- case 'B':
- handler.on_bin();
- break;
- case 'o':
- handler.on_oct();
- break;
-#ifdef FMT_DEPRECATED_N_SPECIFIER
- case 'n':
-#endif
- case 'L':
- handler.on_num();
- break;
- case 'c':
- handler.on_chr();
- break;
- default:
- handler.on_error();
- }
-}
-
-template <typename ErrorHandler = error_handler, typename Char>
-FMT_CONSTEXPR float_specs parse_float_type_spec(
- const basic_format_specs<Char>& specs, ErrorHandler&& eh = {}) {
- auto result = float_specs();
- result.showpoint = specs.alt;
- switch (specs.type) {
- case 0:
- result.format = float_format::general;
- result.showpoint |= specs.precision > 0;
- break;
- case 'G':
- result.upper = true;
- FMT_FALLTHROUGH;
- case 'g':
- result.format = float_format::general;
- break;
- case 'E':
- result.upper = true;
- FMT_FALLTHROUGH;
- case 'e':
- result.format = float_format::exp;
- result.showpoint |= specs.precision != 0;
- break;
- case 'F':
- result.upper = true;
- FMT_FALLTHROUGH;
- case 'f':
- result.format = float_format::fixed;
- result.showpoint |= specs.precision != 0;
- break;
- case 'A':
- result.upper = true;
- FMT_FALLTHROUGH;
- case 'a':
- result.format = float_format::hex;
- break;
-#ifdef FMT_DEPRECATED_N_SPECIFIER
- case 'n':
-#endif
- case 'L':
- result.locale = true;
- break;
- default:
- eh.on_error("invalid type specifier");
- break;
- }
- return result;
-}
-
-template <typename Char, typename Handler>
-FMT_CONSTEXPR void handle_char_specs(const basic_format_specs<Char>* specs,
- Handler&& handler) {
- if (!specs) return handler.on_char();
- if (specs->type && specs->type != 'c') return handler.on_int();
- if (specs->align == align::numeric || specs->sign != sign::none || specs->alt)
- handler.on_error("invalid format specifier for char");
- handler.on_char();
-}
-
-template <typename Char, typename Handler>
-FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler&& handler) {
- if (spec == 0 || spec == 's')
- handler.on_string();
- else if (spec == 'p')
- handler.on_pointer();
- else
- handler.on_error("invalid type specifier");
-}
+auto snprintf_float(T value, int precision, float_specs specs,
+ buffer<char>& buf) -> int;
-template <typename Char, typename ErrorHandler>
-FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler&& eh) {
- if (spec != 0 && spec != 's') eh.on_error("invalid type specifier");
+template <typename T> constexpr auto promote_float(T value) -> T {
+ return value;
}
-
-template <typename Char, typename ErrorHandler>
-FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler&& eh) {
- if (spec != 0 && spec != 'p') eh.on_error("invalid type specifier");
+constexpr auto promote_float(float value) -> double {
+ return static_cast<double>(value);
}
-template <typename ErrorHandler> class int_type_checker : private ErrorHandler {
- public:
- FMT_CONSTEXPR explicit int_type_checker(ErrorHandler eh) : ErrorHandler(eh) {}
-
- FMT_CONSTEXPR void on_dec() {}
- FMT_CONSTEXPR void on_hex() {}
- FMT_CONSTEXPR void on_bin() {}
- FMT_CONSTEXPR void on_oct() {}
- FMT_CONSTEXPR void on_num() {}
- FMT_CONSTEXPR void on_chr() {}
-
- FMT_CONSTEXPR void on_error() {
- ErrorHandler::on_error("invalid type specifier");
- }
-};
-
-template <typename ErrorHandler>
-class char_specs_checker : public ErrorHandler {
- private:
- char type_;
-
- public:
- FMT_CONSTEXPR char_specs_checker(char type, ErrorHandler eh)
- : ErrorHandler(eh), type_(type) {}
-
- FMT_CONSTEXPR void on_int() {
- handle_int_type_spec(type_, int_type_checker<ErrorHandler>(*this));
- }
- FMT_CONSTEXPR void on_char() {}
-};
-
-template <typename ErrorHandler>
-class cstring_type_checker : public ErrorHandler {
- public:
- FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh)
- : ErrorHandler(eh) {}
-
- FMT_CONSTEXPR void on_string() {}
- FMT_CONSTEXPR void on_pointer() {}
-};
-
template <typename OutputIt, typename Char>
-FMT_NOINLINE OutputIt fill(OutputIt it, size_t n, const fill_t<Char>& fill) {
+FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n,
+ const fill_t<Char>& fill) -> OutputIt {
auto fill_size = fill.size();
- if (fill_size == 1) return std::fill_n(it, n, fill[0]);
- for (size_t i = 0; i < n; ++i) it = std::copy_n(fill.data(), fill_size, it);
+ if (fill_size == 1) return detail::fill_n(it, n, fill[0]);
+ auto data = fill.data();
+ for (size_t i = 0; i < n; ++i)
+ it = copy_str<Char>(data, data + fill_size, it);
return it;
}
// width: output display width in (terminal) column positions.
template <align::type align = align::left, typename OutputIt, typename Char,
typename F>
-inline OutputIt write_padded(OutputIt out,
- const basic_format_specs<Char>& specs, size_t size,
- size_t width, F&& f) {
+FMT_CONSTEXPR auto write_padded(OutputIt out,
+ const basic_format_specs<Char>& specs,
+ size_t size, size_t width, F&& f) -> OutputIt {
static_assert(align == align::left || align == align::right, "");
unsigned spec_width = to_unsigned(specs.width);
size_t padding = spec_width > width ? spec_width - width : 0;
- auto* shifts = align == align::left ? data::left_padding_shifts
- : data::right_padding_shifts;
+ // Shifts are encoded as string literals because static constexpr is not
+ // supported in constexpr functions.
+ auto* shifts = align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01";
size_t left_padding = padding >> shifts[specs.align];
+ size_t right_padding = padding - left_padding;
auto it = reserve(out, size + padding * specs.fill.size());
- it = fill(it, left_padding, specs.fill);
+ if (left_padding != 0) it = fill(it, left_padding, specs.fill);
it = f(it);
- it = fill(it, padding - left_padding, specs.fill);
+ if (right_padding != 0) it = fill(it, right_padding, specs.fill);
return base_iterator(out, it);
}
template <align::type align = align::left, typename OutputIt, typename Char,
typename F>
-inline OutputIt write_padded(OutputIt out,
- const basic_format_specs<Char>& specs, size_t size,
- F&& f) {
+constexpr auto write_padded(OutputIt out, const basic_format_specs<Char>& specs,
+ size_t size, F&& f) -> OutputIt {
return write_padded<align>(out, specs, size, size, f);
}
+template <align::type align = align::left, typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes,
+ const basic_format_specs<Char>& specs)
+ -> OutputIt {
+ return write_padded<align>(
+ out, specs, bytes.size(), [bytes](reserve_iterator<OutputIt> it) {
+ const char* data = bytes.data();
+ return copy_str<Char>(data, data + bytes.size(), it);
+ });
+}
+
+template <typename Char, typename OutputIt, typename UIntPtr>
+auto write_ptr(OutputIt out, UIntPtr value,
+ const basic_format_specs<Char>* specs) -> OutputIt {
+ int num_digits = count_digits<4>(value);
+ auto size = to_unsigned(num_digits) + size_t(2);
+ auto write = [=](reserve_iterator<OutputIt> it) {
+ *it++ = static_cast<Char>('0');
+ *it++ = static_cast<Char>('x');
+ return format_uint<4, Char>(it, value, num_digits);
+ };
+ return specs ? write_padded<align::right>(out, *specs, size, write)
+ : base_iterator(out, write(reserve(out, size)));
+}
+
template <typename Char, typename OutputIt>
-OutputIt write_bytes(OutputIt out, string_view bytes,
- const basic_format_specs<Char>& specs) {
- using iterator = remove_reference_t<decltype(reserve(out, 0))>;
- return write_padded(out, specs, bytes.size(), [bytes](iterator it) {
- const char* data = bytes.data();
- return copy_str<Char>(data, data + bytes.size(), it);
+FMT_CONSTEXPR auto write_char(OutputIt out, Char value,
+ const basic_format_specs<Char>& specs)
+ -> OutputIt {
+ return write_padded(out, specs, 1, [=](reserve_iterator<OutputIt> it) {
+ *it++ = value;
+ return it;
});
}
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write(OutputIt out, Char value,
+ const basic_format_specs<Char>& specs,
+ locale_ref loc = {}) -> OutputIt {
+ return check_char_specs(specs)
+ ? write_char(out, value, specs)
+ : write(out, static_cast<int>(value), specs, loc);
+}
// Data for write_int that doesn't depend on output iterator type. It is used to
// avoid template code bloat.
size_t size;
size_t padding;
- write_int_data(int num_digits, string_view prefix,
- const basic_format_specs<Char>& specs)
- : size(prefix.size() + to_unsigned(num_digits)), padding(0) {
+ FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix,
+ const basic_format_specs<Char>& specs)
+ : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) {
if (specs.align == align::numeric) {
auto width = to_unsigned(specs.width);
if (width > size) {
size = width;
}
} else if (specs.precision > num_digits) {
- size = prefix.size() + to_unsigned(specs.precision);
+ size = (prefix >> 24) + to_unsigned(specs.precision);
padding = to_unsigned(specs.precision - num_digits);
}
}
// Writes an integer in the format
// <left-padding><prefix><numeric-padding><digits><right-padding>
-// where <digits> are written by f(it).
-template <typename OutputIt, typename Char, typename F>
-OutputIt write_int(OutputIt out, int num_digits, string_view prefix,
- const basic_format_specs<Char>& specs, F f) {
+// where <digits> are written by write_digits(it).
+// prefix contains chars in three lower bytes and the size in the fourth byte.
+template <typename OutputIt, typename Char, typename W>
+FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits,
+ unsigned prefix,
+ const basic_format_specs<Char>& specs,
+ W write_digits) -> OutputIt {
+ // Slightly faster check for specs.width == 0 && specs.precision == -1.
+ if ((specs.width | (specs.precision + 1)) == 0) {
+ auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24));
+ if (prefix != 0) {
+ for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8)
+ *it++ = static_cast<Char>(p & 0xff);
+ }
+ return base_iterator(out, write_digits(it));
+ }
auto data = write_int_data<Char>(num_digits, prefix, specs);
- using iterator = remove_reference_t<decltype(reserve(out, 0))>;
- return write_padded<align::right>(out, specs, data.size, [=](iterator it) {
- if (prefix.size() != 0)
- it = copy_str<Char>(prefix.begin(), prefix.end(), it);
- it = std::fill_n(it, data.padding, static_cast<Char>('0'));
- return f(it);
- });
+ return write_padded<align::right>(
+ out, specs, data.size, [=](reserve_iterator<OutputIt> it) {
+ for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8)
+ *it++ = static_cast<Char>(p & 0xff);
+ it = detail::fill_n(it, data.padding, static_cast<Char>('0'));
+ return write_digits(it);
+ });
}
-template <typename StrChar, typename Char, typename OutputIt>
-OutputIt write(OutputIt out, basic_string_view<StrChar> s,
- const basic_format_specs<Char>& specs) {
- auto data = s.data();
- auto size = s.size();
- if (specs.precision >= 0 && to_unsigned(specs.precision) < size)
- size = code_point_index(s, to_unsigned(specs.precision));
- auto width = specs.width != 0
- ? count_code_points(basic_string_view<StrChar>(data, size))
- : 0;
- using iterator = remove_reference_t<decltype(reserve(out, 0))>;
- return write_padded(out, specs, size, width, [=](iterator it) {
- return copy_str<Char>(data, data + size, it);
- });
-}
+template <typename Char> class digit_grouping {
+ private:
+ thousands_sep_result<Char> sep_;
-// The handle_int_type_spec handler that writes an integer.
-template <typename OutputIt, typename Char, typename UInt> struct int_writer {
- OutputIt out;
- locale_ref locale;
- const basic_format_specs<Char>& specs;
- UInt abs_value;
- char prefix[4];
- unsigned prefix_size;
-
- using iterator =
- remove_reference_t<decltype(reserve(std::declval<OutputIt&>(), 0))>;
-
- string_view get_prefix() const { return string_view(prefix, prefix_size); }
-
- template <typename Int>
- int_writer(OutputIt output, locale_ref loc, Int value,
- const basic_format_specs<Char>& s)
- : out(output),
- locale(loc),
- specs(s),
- abs_value(static_cast<UInt>(value)),
- prefix_size(0) {
- static_assert(std::is_same<uint32_or_64_or_128_t<Int>, UInt>::value, "");
- if (is_negative(value)) {
- prefix[0] = '-';
- ++prefix_size;
- abs_value = 0 - abs_value;
- } else if (specs.sign != sign::none && specs.sign != sign::minus) {
- prefix[0] = specs.sign == sign::plus ? '+' : ' ';
- ++prefix_size;
- }
- }
+ struct next_state {
+ std::string::const_iterator group;
+ int pos;
+ };
+ next_state initial_state() const { return {sep_.grouping.begin(), 0}; }
- void on_dec() {
- auto num_digits = count_digits(abs_value);
- out = write_int(
- out, num_digits, get_prefix(), specs, [this, num_digits](iterator it) {
- return format_decimal<Char>(it, abs_value, num_digits).end;
- });
+ // Returns the next digit group separator position.
+ int next(next_state& state) const {
+ if (!sep_.thousands_sep) return max_value<int>();
+ if (state.group == sep_.grouping.end())
+ return state.pos += sep_.grouping.back();
+ if (*state.group <= 0 || *state.group == max_value<char>())
+ return max_value<int>();
+ state.pos += *state.group++;
+ return state.pos;
}
- void on_hex() {
- if (specs.alt) {
- prefix[prefix_size++] = '0';
- prefix[prefix_size++] = specs.type;
- }
- int num_digits = count_digits<4>(abs_value);
- out = write_int(out, num_digits, get_prefix(), specs,
- [this, num_digits](iterator it) {
- return format_uint<4, Char>(it, abs_value, num_digits,
- specs.type != 'x');
- });
+ public:
+ explicit digit_grouping(locale_ref loc, bool localized = true) {
+ if (localized)
+ sep_ = thousands_sep<Char>(loc);
+ else
+ sep_.thousands_sep = Char();
}
+ explicit digit_grouping(thousands_sep_result<Char> sep) : sep_(sep) {}
- void on_bin() {
- if (specs.alt) {
- prefix[prefix_size++] = '0';
- prefix[prefix_size++] = static_cast<char>(specs.type);
- }
- int num_digits = count_digits<1>(abs_value);
- out = write_int(out, num_digits, get_prefix(), specs,
- [this, num_digits](iterator it) {
- return format_uint<1, Char>(it, abs_value, num_digits);
- });
+ Char separator() const { return sep_.thousands_sep; }
+
+ int count_separators(int num_digits) const {
+ int count = 0;
+ auto state = initial_state();
+ while (num_digits > next(state)) ++count;
+ return count;
}
- void on_oct() {
- int num_digits = count_digits<3>(abs_value);
- if (specs.alt && specs.precision <= num_digits && abs_value != 0) {
- // Octal prefix '0' is counted as a digit, so only add it if precision
- // is not greater than the number of digits.
- prefix[prefix_size++] = '0';
- }
- out = write_int(out, num_digits, get_prefix(), specs,
- [this, num_digits](iterator it) {
- return format_uint<3, Char>(it, abs_value, num_digits);
- });
- }
-
- enum { sep_size = 1 };
-
- void on_num() {
- std::string groups = grouping<Char>(locale);
- if (groups.empty()) return on_dec();
- auto sep = thousands_sep<Char>(locale);
- if (!sep) return on_dec();
- int num_digits = count_digits(abs_value);
- int size = num_digits, n = num_digits;
- std::string::const_iterator group = groups.cbegin();
- while (group != groups.cend() && n > *group && *group > 0 &&
- *group != max_value<char>()) {
- size += sep_size;
- n -= *group;
- ++group;
+ // Applies grouping to digits and write the output to out.
+ template <typename Out, typename C>
+ Out apply(Out out, basic_string_view<C> digits) const {
+ auto num_digits = static_cast<int>(digits.size());
+ auto separators = basic_memory_buffer<int>();
+ separators.push_back(0);
+ auto state = initial_state();
+ while (int i = next(state)) {
+ if (i >= num_digits) break;
+ separators.push_back(i);
}
- if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back());
- char digits[40];
- format_decimal(digits, abs_value, num_digits);
- basic_memory_buffer<Char> buffer;
- size += static_cast<int>(prefix_size);
- const auto usize = to_unsigned(size);
- buffer.resize(usize);
- basic_string_view<Char> s(&sep, sep_size);
- // Index of a decimal digit with the least significant digit having index 0.
- int digit_index = 0;
- group = groups.cbegin();
- auto p = buffer.data() + size - 1;
- for (int i = num_digits - 1; i > 0; --i) {
- *p-- = static_cast<Char>(digits[i]);
- if (*group <= 0 || ++digit_index % *group != 0 ||
- *group == max_value<char>())
- continue;
- if (group + 1 != groups.cend()) {
- digit_index = 0;
- ++group;
+ for (int i = 0, sep_index = static_cast<int>(separators.size() - 1);
+ i < num_digits; ++i) {
+ if (num_digits - i == separators[sep_index]) {
+ *out++ = separator();
+ --sep_index;
}
- std::uninitialized_copy(s.data(), s.data() + s.size(),
- make_checked(p, s.size()));
- p -= s.size();
+ *out++ = static_cast<Char>(digits[to_unsigned(i)]);
}
- *p-- = static_cast<Char>(*digits);
- if (prefix_size != 0) *p = static_cast<Char>('-');
- auto data = buffer.data();
- out = write_padded<align::right>(
- out, specs, usize, usize,
- [=](iterator it) { return copy_str<Char>(data, data + size, it); });
+ return out;
}
+};
- void on_chr() { *out++ = static_cast<Char>(abs_value); }
+template <typename OutputIt, typename UInt, typename Char>
+auto write_int_localized(OutputIt out, UInt value, unsigned prefix,
+ const basic_format_specs<Char>& specs,
+ const digit_grouping<Char>& grouping) -> OutputIt {
+ static_assert(std::is_same<uint64_or_128_t<UInt>, UInt>::value, "");
+ int num_digits = count_digits(value);
+ char digits[40];
+ format_decimal(digits, value, num_digits);
+ unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits +
+ grouping.count_separators(num_digits));
+ return write_padded<align::right>(
+ out, specs, size, size, [&](reserve_iterator<OutputIt> it) {
+ if (prefix != 0) *it++ = static_cast<Char>(prefix);
+ return grouping.apply(it, string_view(digits, to_unsigned(num_digits)));
+ });
+}
+
+template <typename OutputIt, typename UInt, typename Char>
+auto write_int_localized(OutputIt& out, UInt value, unsigned prefix,
+ const basic_format_specs<Char>& specs, locale_ref loc)
+ -> bool {
+ auto grouping = digit_grouping<Char>(loc);
+ out = write_int_localized(out, value, prefix, specs, grouping);
+ return true;
+}
+
+FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) {
+ prefix |= prefix != 0 ? value << 8 : value;
+ prefix += (1u + (value > 0xff ? 1 : 0)) << 24;
+}
+
+template <typename UInt> struct write_int_arg {
+ UInt abs_value;
+ unsigned prefix;
+};
- FMT_NORETURN void on_error() {
- FMT_THROW(format_error("invalid type specifier"));
+template <typename T>
+FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign)
+ -> write_int_arg<uint32_or_64_or_128_t<T>> {
+ auto prefix = 0u;
+ auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value);
+ if (is_negative(value)) {
+ prefix = 0x01000000 | '-';
+ abs_value = 0 - abs_value;
+ } else {
+ constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
+ 0x1000000u | ' '};
+ prefix = prefixes[sign];
}
-};
+ return {abs_value, prefix};
+}
+
+template <typename Char, typename OutputIt, typename T>
+FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg,
+ const basic_format_specs<Char>& specs,
+ locale_ref loc) -> OutputIt {
+ static_assert(std::is_same<T, uint32_or_64_or_128_t<T>>::value, "");
+ auto abs_value = arg.abs_value;
+ auto prefix = arg.prefix;
+ switch (specs.type) {
+ case presentation_type::none:
+ case presentation_type::dec: {
+ if (specs.localized &&
+ write_int_localized(out, static_cast<uint64_or_128_t<T>>(abs_value),
+ prefix, specs, loc)) {
+ return out;
+ }
+ auto num_digits = count_digits(abs_value);
+ return write_int(
+ out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
+ return format_decimal<Char>(it, abs_value, num_digits).end;
+ });
+ }
+ case presentation_type::hex_lower:
+ case presentation_type::hex_upper: {
+ bool upper = specs.type == presentation_type::hex_upper;
+ if (specs.alt)
+ prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0');
+ int num_digits = count_digits<4>(abs_value);
+ return write_int(
+ out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
+ return format_uint<4, Char>(it, abs_value, num_digits, upper);
+ });
+ }
+ case presentation_type::bin_lower:
+ case presentation_type::bin_upper: {
+ bool upper = specs.type == presentation_type::bin_upper;
+ if (specs.alt)
+ prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0');
+ int num_digits = count_digits<1>(abs_value);
+ return write_int(out, num_digits, prefix, specs,
+ [=](reserve_iterator<OutputIt> it) {
+ return format_uint<1, Char>(it, abs_value, num_digits);
+ });
+ }
+ case presentation_type::oct: {
+ int num_digits = count_digits<3>(abs_value);
+ // Octal prefix '0' is counted as a digit, so only add it if precision
+ // is not greater than the number of digits.
+ if (specs.alt && specs.precision <= num_digits && abs_value != 0)
+ prefix_append(prefix, '0');
+ return write_int(out, num_digits, prefix, specs,
+ [=](reserve_iterator<OutputIt> it) {
+ return format_uint<3, Char>(it, abs_value, num_digits);
+ });
+ }
+ case presentation_type::chr:
+ return write_char(out, static_cast<Char>(abs_value), specs);
+ default:
+ throw_format_error("invalid type specifier");
+ }
+ return out;
+}
+template <typename Char, typename OutputIt, typename T>
+FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(
+ OutputIt out, write_int_arg<T> arg, const basic_format_specs<Char>& specs,
+ locale_ref loc) -> OutputIt {
+ return write_int(out, arg, specs, loc);
+}
+template <typename Char, typename OutputIt, typename T,
+ FMT_ENABLE_IF(is_integral<T>::value &&
+ !std::is_same<T, bool>::value &&
+ std::is_same<OutputIt, buffer_appender<Char>>::value)>
+FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value,
+ const basic_format_specs<Char>& specs,
+ locale_ref loc) -> OutputIt {
+ return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs,
+ loc);
+}
+// An inlined version of write used in format string compilation.
+template <typename Char, typename OutputIt, typename T,
+ FMT_ENABLE_IF(is_integral<T>::value &&
+ !std::is_same<T, bool>::value &&
+ !std::is_same<OutputIt, buffer_appender<Char>>::value)>
+FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value,
+ const basic_format_specs<Char>& specs,
+ locale_ref loc) -> OutputIt {
+ return write_int(out, make_write_int_arg(value, specs.sign), specs, loc);
+}
template <typename Char, typename OutputIt>
-OutputIt write_nonfinite(OutputIt out, bool isinf,
- const basic_format_specs<Char>& specs,
- const float_specs& fspecs) {
+FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,
+ const basic_format_specs<Char>& specs) -> OutputIt {
+ auto data = s.data();
+ auto size = s.size();
+ if (specs.precision >= 0 && to_unsigned(specs.precision) < size)
+ size = code_point_index(s, to_unsigned(specs.precision));
+ auto width =
+ specs.width != 0 ? compute_width(basic_string_view<Char>(data, size)) : 0;
+ return write_padded(out, specs, size, width,
+ [=](reserve_iterator<OutputIt> it) {
+ return copy_str<Char>(data, data + size, it);
+ });
+}
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write(OutputIt out,
+ basic_string_view<type_identity_t<Char>> s,
+ const basic_format_specs<Char>& specs, locale_ref)
+ -> OutputIt {
+ check_string_type_spec(specs.type);
+ return write(out, s, specs);
+}
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR auto write(OutputIt out, const Char* s,
+ const basic_format_specs<Char>& specs, locale_ref)
+ -> OutputIt {
+ return check_cstring_type_spec(specs.type)
+ ? write(out, basic_string_view<Char>(s), specs, {})
+ : write_ptr<Char>(out, to_uintptr(s), &specs);
+}
+
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isinf,
+ basic_format_specs<Char> specs,
+ const float_specs& fspecs) -> OutputIt {
auto str =
isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan");
constexpr size_t str_size = 3;
auto sign = fspecs.sign;
auto size = str_size + (sign ? 1 : 0);
- using iterator = remove_reference_t<decltype(reserve(out, 0))>;
- return write_padded(out, specs, size, [=](iterator it) {
- if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+ // Replace '0'-padding with space for non-finite values.
+ const bool is_zero_fill =
+ specs.fill.size() == 1 && *specs.fill.data() == static_cast<Char>('0');
+ if (is_zero_fill) specs.fill[0] = static_cast<Char>(' ');
+ return write_padded(out, specs, size, [=](reserve_iterator<OutputIt> it) {
+ if (sign) *it++ = detail::sign<Char>(sign);
return copy_str<Char>(str, str + str_size, it);
});
}
int exponent;
};
-inline int get_significand_size(const big_decimal_fp& fp) {
+constexpr auto get_significand_size(const big_decimal_fp& fp) -> int {
return fp.significand_size;
}
template <typename T>
-inline int get_significand_size(const dragonbox::decimal_fp<T>& fp) {
+inline auto get_significand_size(const dragonbox::decimal_fp<T>& fp) -> int {
return count_digits(fp.significand);
}
template <typename Char, typename OutputIt>
-inline OutputIt write_significand(OutputIt out, const char* significand,
- int& significand_size) {
+constexpr auto write_significand(OutputIt out, const char* significand,
+ int significand_size) -> OutputIt {
return copy_str<Char>(significand, significand + significand_size, out);
}
template <typename Char, typename OutputIt, typename UInt>
-inline OutputIt write_significand(OutputIt out, UInt significand,
- int significand_size) {
+inline auto write_significand(OutputIt out, UInt significand,
+ int significand_size) -> OutputIt {
return format_decimal<Char>(out, significand, significand_size).end;
}
+template <typename Char, typename OutputIt, typename T, typename Grouping>
+FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,
+ int significand_size, int exponent,
+ const Grouping& grouping) -> OutputIt {
+ if (!grouping.separator()) {
+ out = write_significand<Char>(out, significand, significand_size);
+ return detail::fill_n(out, exponent, static_cast<Char>('0'));
+ }
+ auto buffer = memory_buffer();
+ write_significand<char>(appender(buffer), significand, significand_size);
+ detail::fill_n(appender(buffer), exponent, '0');
+ return grouping.apply(out, string_view(buffer.data(), buffer.size()));
+}
template <typename Char, typename UInt,
FMT_ENABLE_IF(std::is_integral<UInt>::value)>
-inline Char* write_significand(Char* out, UInt significand,
- int significand_size, int integral_size,
- Char decimal_point) {
+inline auto write_significand(Char* out, UInt significand, int significand_size,
+ int integral_size, Char decimal_point) -> Char* {
if (!decimal_point)
return format_decimal(out, significand, significand_size).end;
- auto end = format_decimal(out + 1, significand, significand_size).end;
- if (integral_size == 1)
- out[0] = out[1];
- else
- std::copy_n(out + 1, integral_size, out);
- out[integral_size] = decimal_point;
+ out += significand_size + 1;
+ Char* end = out;
+ int floating_size = significand_size - integral_size;
+ for (int i = floating_size / 2; i > 0; --i) {
+ out -= 2;
+ copy2(out, digits2(significand % 100));
+ significand /= 100;
+ }
+ if (floating_size % 2 != 0) {
+ *--out = static_cast<Char>('0' + significand % 10);
+ significand /= 10;
+ }
+ *--out = decimal_point;
+ format_decimal(out - integral_size, significand, integral_size);
return end;
}
template <typename OutputIt, typename UInt, typename Char,
FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<OutputIt>>::value)>
-inline OutputIt write_significand(OutputIt out, UInt significand,
- int significand_size, int integral_size,
- Char decimal_point) {
+inline auto write_significand(OutputIt out, UInt significand,
+ int significand_size, int integral_size,
+ Char decimal_point) -> OutputIt {
// Buffer is large enough to hold digits (digits10 + 1) and a decimal point.
Char buffer[digits10<UInt>() + 2];
auto end = write_significand(buffer, significand, significand_size,
integral_size, decimal_point);
- return detail::copy_str<Char>(buffer, end, out);
+ return detail::copy_str_noinline<Char>(buffer, end, out);
}
template <typename OutputIt, typename Char>
-inline OutputIt write_significand(OutputIt out, const char* significand,
- int significand_size, int integral_size,
- Char decimal_point) {
- out = detail::copy_str<Char>(significand, significand + integral_size, out);
+FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand,
+ int significand_size, int integral_size,
+ Char decimal_point) -> OutputIt {
+ out = detail::copy_str_noinline<Char>(significand,
+ significand + integral_size, out);
if (!decimal_point) return out;
*out++ = decimal_point;
- return detail::copy_str<Char>(significand + integral_size,
- significand + significand_size, out);
+ return detail::copy_str_noinline<Char>(significand + integral_size,
+ significand + significand_size, out);
}
-template <typename OutputIt, typename DecimalFP, typename Char>
-OutputIt write_float(OutputIt out, const DecimalFP& fp,
- const basic_format_specs<Char>& specs, float_specs fspecs,
- Char decimal_point) {
+template <typename OutputIt, typename Char, typename T, typename Grouping>
+FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,
+ int significand_size, int integral_size,
+ Char decimal_point,
+ const Grouping& grouping) -> OutputIt {
+ if (!grouping.separator()) {
+ return write_significand(out, significand, significand_size, integral_size,
+ decimal_point);
+ }
+ auto buffer = basic_memory_buffer<Char>();
+ write_significand(buffer_appender<Char>(buffer), significand,
+ significand_size, integral_size, decimal_point);
+ grouping.apply(
+ out, basic_string_view<Char>(buffer.data(), to_unsigned(integral_size)));
+ return detail::copy_str_noinline<Char>(buffer.data() + integral_size,
+ buffer.end(), out);
+}
+
+template <typename OutputIt, typename DecimalFP, typename Char,
+ typename Grouping = digit_grouping<Char>>
+FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp,
+ const basic_format_specs<Char>& specs,
+ float_specs fspecs, locale_ref loc)
+ -> OutputIt {
auto significand = fp.significand;
int significand_size = get_significand_size(fp);
- static const Char zero = static_cast<Char>('0');
+ constexpr Char zero = static_cast<Char>('0');
auto sign = fspecs.sign;
size_t size = to_unsigned(significand_size) + (sign ? 1 : 0);
- using iterator = remove_reference_t<decltype(reserve(out, 0))>;
+ using iterator = reserve_iterator<OutputIt>;
+
+ Char decimal_point =
+ fspecs.locale ? detail::decimal_point<Char>(loc) : static_cast<Char>('.');
int output_exp = fp.exponent + significand_size - 1;
auto use_exp_format = [=]() {
if (use_exp_format()) {
int num_zeros = 0;
if (fspecs.showpoint) {
- num_zeros = (std::max)(fspecs.precision - significand_size, 0);
+ num_zeros = fspecs.precision - significand_size;
+ if (num_zeros < 0) num_zeros = 0;
size += to_unsigned(num_zeros);
} else if (significand_size == 1) {
decimal_point = Char();
size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits);
char exp_char = fspecs.upper ? 'E' : 'e';
auto write = [=](iterator it) {
- if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+ if (sign) *it++ = detail::sign<Char>(sign);
// Insert a decimal point after the first digit and add an exponent.
it = write_significand(it, significand, significand_size, 1,
decimal_point);
- if (num_zeros > 0) it = std::fill_n(it, num_zeros, zero);
+ if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero);
*it++ = static_cast<Char>(exp_char);
return write_exponent<Char>(output_exp, it);
};
#endif
if (fspecs.showpoint) {
if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1;
- if (num_zeros > 0) size += to_unsigned(num_zeros);
+ if (num_zeros > 0) size += to_unsigned(num_zeros) + 1;
}
+ auto grouping = Grouping(loc, fspecs.locale);
+ size += to_unsigned(grouping.count_separators(significand_size));
return write_padded<align::right>(out, specs, size, [&](iterator it) {
- if (sign) *it++ = static_cast<Char>(data::signs[sign]);
- it = write_significand<Char>(it, significand, significand_size);
- it = std::fill_n(it, fp.exponent, zero);
+ if (sign) *it++ = detail::sign<Char>(sign);
+ it = write_significand<Char>(it, significand, significand_size,
+ fp.exponent, grouping);
if (!fspecs.showpoint) return it;
*it++ = decimal_point;
- return num_zeros > 0 ? std::fill_n(it, num_zeros, zero) : it;
+ return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
});
} else if (exp > 0) {
// 1234e-2 -> 12.34[0+]
int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0;
size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0);
+ auto grouping = Grouping(loc, fspecs.locale);
+ size += to_unsigned(grouping.count_separators(significand_size));
return write_padded<align::right>(out, specs, size, [&](iterator it) {
- if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+ if (sign) *it++ = detail::sign<Char>(sign);
it = write_significand(it, significand, significand_size, exp,
- decimal_point);
- return num_zeros > 0 ? std::fill_n(it, num_zeros, zero) : it;
+ decimal_point, grouping);
+ return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
});
}
// 1234e-6 -> 0.001234
fspecs.precision < num_zeros) {
num_zeros = fspecs.precision;
}
- size += 2 + to_unsigned(num_zeros);
+ bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint;
+ size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros);
return write_padded<align::right>(out, specs, size, [&](iterator it) {
- if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+ if (sign) *it++ = detail::sign<Char>(sign);
*it++ = zero;
- if (num_zeros == 0 && significand_size == 0 && !fspecs.showpoint) return it;
+ if (!pointy) return it;
*it++ = decimal_point;
- it = std::fill_n(it, num_zeros, zero);
+ it = detail::fill_n(it, num_zeros, zero);
return write_significand<Char>(it, significand, significand_size);
});
}
+template <typename Char> class fallback_digit_grouping {
+ public:
+ constexpr fallback_digit_grouping(locale_ref, bool) {}
+
+ constexpr Char separator() const { return Char(); }
+
+ constexpr int count_separators(int) const { return 0; }
+
+ template <typename Out, typename C>
+ constexpr Out apply(Out out, basic_string_view<C>) const {
+ return out;
+ }
+};
+
+template <typename OutputIt, typename DecimalFP, typename Char>
+FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& fp,
+ const basic_format_specs<Char>& specs,
+ float_specs fspecs, locale_ref loc)
+ -> OutputIt {
+ if (is_constant_evaluated()) {
+ return do_write_float<OutputIt, DecimalFP, Char,
+ fallback_digit_grouping<Char>>(out, fp, specs, fspecs,
+ loc);
+ } else {
+ return do_write_float(out, fp, specs, fspecs, loc);
+ }
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+FMT_CONSTEXPR20 bool isinf(T value) {
+ if (is_constant_evaluated()) {
+#if defined(__cpp_if_constexpr)
+ if constexpr (std::numeric_limits<double>::is_iec559) {
+ auto bits = detail::bit_cast<uint64_t>(static_cast<double>(value));
+ constexpr auto significand_bits =
+ dragonbox::float_info<double>::significand_bits;
+ return (bits & exponent_mask<double>()) &&
+ !(bits & ((uint64_t(1) << significand_bits) - 1));
+ }
+#endif
+ }
+ return std::isinf(value);
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+FMT_CONSTEXPR20 bool isfinite(T value) {
+ if (is_constant_evaluated()) {
+#if defined(__cpp_if_constexpr)
+ if constexpr (std::numeric_limits<double>::is_iec559) {
+ auto bits = detail::bit_cast<uint64_t>(static_cast<double>(value));
+ return (bits & exponent_mask<double>()) != exponent_mask<double>();
+ }
+#endif
+ }
+ return std::isfinite(value);
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+FMT_INLINE FMT_CONSTEXPR bool signbit(T value) {
+ if (is_constant_evaluated()) {
+#ifdef __cpp_if_constexpr
+ if constexpr (std::numeric_limits<double>::is_iec559) {
+ auto bits = detail::bit_cast<uint64_t>(static_cast<double>(value));
+ return (bits & (uint64_t(1) << (num_bits<uint64_t>() - 1))) != 0;
+ }
+#endif
+ }
+ return std::signbit(value);
+}
+
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(std::is_floating_point<T>::value)>
-OutputIt write(OutputIt out, T value, basic_format_specs<Char> specs,
- locale_ref loc = {}) {
+FMT_CONSTEXPR20 auto write(OutputIt out, T value,
+ basic_format_specs<Char> specs, locale_ref loc = {})
+ -> OutputIt {
if (const_check(!is_supported_floating_point(value))) return out;
float_specs fspecs = parse_float_type_spec(specs);
fspecs.sign = specs.sign;
- if (std::signbit(value)) { // value < 0 is false for NaN so use signbit.
+ if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit.
fspecs.sign = sign::minus;
value = -value;
} else if (fspecs.sign == sign::minus) {
fspecs.sign = sign::none;
}
- if (!std::isfinite(value))
- return write_nonfinite(out, std::isinf(value), specs, fspecs);
+ if (!detail::isfinite(value))
+ return write_nonfinite(out, detail::isinf(value), specs, fspecs);
if (specs.align == align::numeric && fspecs.sign) {
auto it = reserve(out, 1);
- *it++ = static_cast<Char>(data::signs[fspecs.sign]);
+ *it++ = detail::sign<Char>(fspecs.sign);
out = base_iterator(out, it);
fspecs.sign = sign::none;
if (specs.width != 0) --specs.width;
memory_buffer buffer;
if (fspecs.format == float_format::hex) {
- if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]);
+ if (fspecs.sign) buffer.push_back(detail::sign<char>(fspecs.sign));
snprintf_float(promote_float(value), specs.precision, fspecs, buffer);
- return write_bytes(out, {buffer.data(), buffer.size()}, specs);
+ return write_bytes<align::right>(out, {buffer.data(), buffer.size()},
+ specs);
}
- int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6;
+ int precision = specs.precision >= 0 || specs.type == presentation_type::none
+ ? specs.precision
+ : 6;
if (fspecs.format == float_format::exp) {
if (precision == max_value<int>())
- FMT_THROW(format_error("number is too big"));
+ throw_format_error("number is too big");
else
++precision;
}
if (const_check(std::is_same<T, float>())) fspecs.binary32 = true;
- fspecs.use_grisu = is_fast_float<T>();
+ if (!is_fast_float<T>()) fspecs.fallback = true;
int exp = format_float(promote_float(value), precision, fspecs, buffer);
fspecs.precision = precision;
- Char point =
- fspecs.locale ? decimal_point<Char>(loc) : static_cast<Char>('.');
auto fp = big_decimal_fp{buffer.data(), static_cast<int>(buffer.size()), exp};
- return write_float(out, fp, specs, fspecs, point);
+ return write_float(out, fp, specs, fspecs, loc);
}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(is_fast_float<T>::value)>
-OutputIt write(OutputIt out, T value) {
+FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt {
+ if (is_constant_evaluated()) {
+ return write(out, value, basic_format_specs<Char>());
+ }
+
if (const_check(!is_supported_floating_point(value))) return out;
using floaty = conditional_t<std::is_same<T, long double>::value, double, T>;
auto bits = bit_cast<uint>(value);
auto fspecs = float_specs();
- auto sign_bit = bits & (uint(1) << (num_bits<uint>() - 1));
- if (sign_bit != 0) {
+ if (detail::signbit(value)) {
fspecs.sign = sign::minus;
value = -value;
}
- static const auto specs = basic_format_specs<Char>();
+ constexpr auto specs = basic_format_specs<Char>();
uint mask = exponent_mask<floaty>();
if ((bits & mask) == mask)
return write_nonfinite(out, std::isinf(value), specs, fspecs);
auto dec = dragonbox::to_decimal(static_cast<floaty>(value));
- return write_float(out, dec, specs, fspecs, static_cast<Char>('.'));
+ return write_float(out, dec, specs, fspecs, {});
}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(std::is_floating_point<T>::value &&
!is_fast_float<T>::value)>
-inline OutputIt write(OutputIt out, T value) {
+inline auto write(OutputIt out, T value) -> OutputIt {
return write(out, value, basic_format_specs<Char>());
}
template <typename Char, typename OutputIt>
-OutputIt write_char(OutputIt out, Char value,
- const basic_format_specs<Char>& specs) {
- using iterator = remove_reference_t<decltype(reserve(out, 0))>;
- return write_padded(out, specs, 1, [=](iterator it) {
- *it++ = value;
- return it;
- });
-}
-
-template <typename Char, typename OutputIt, typename UIntPtr>
-OutputIt write_ptr(OutputIt out, UIntPtr value,
- const basic_format_specs<Char>* specs) {
- int num_digits = count_digits<4>(value);
- auto size = to_unsigned(num_digits) + size_t(2);
- using iterator = remove_reference_t<decltype(reserve(out, 0))>;
- auto write = [=](iterator it) {
- *it++ = static_cast<Char>('0');
- *it++ = static_cast<Char>('x');
- return format_uint<4, Char>(it, value, num_digits);
- };
- return specs ? write_padded<align::right>(out, *specs, size, write)
- : base_iterator(out, write(reserve(out, size)));
-}
-
-template <typename T> struct is_integral : std::is_integral<T> {};
-template <> struct is_integral<int128_t> : std::true_type {};
-template <> struct is_integral<uint128_t> : std::true_type {};
-
-template <typename Char, typename OutputIt>
-OutputIt write(OutputIt out, monostate) {
+auto write(OutputIt out, monostate, basic_format_specs<Char> = {},
+ locale_ref = {}) -> OutputIt {
FMT_ASSERT(false, "");
return out;
}
-template <typename Char, typename OutputIt,
- FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
-OutputIt write(OutputIt out, string_view value) {
- auto it = reserve(out, value.size());
- it = copy_str<Char>(value.begin(), value.end(), it);
- return base_iterator(out, it);
-}
-
template <typename Char, typename OutputIt>
-OutputIt write(OutputIt out, basic_string_view<Char> value) {
+FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> value)
+ -> OutputIt {
auto it = reserve(out, value.size());
- it = std::copy(value.begin(), value.end(), it);
+ it = copy_str_noinline<Char>(value.begin(), value.end(), it);
return base_iterator(out, it);
}
-template <typename Char>
-buffer_appender<Char> write(buffer_appender<Char> out,
- basic_string_view<Char> value) {
- get_container(out).append(value.begin(), value.end());
- return out;
+template <typename Char, typename OutputIt, typename T,
+ FMT_ENABLE_IF(is_string<T>::value)>
+constexpr auto write(OutputIt out, const T& value) -> OutputIt {
+ return write<Char>(out, to_string_view(value));
}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(is_integral<T>::value &&
!std::is_same<T, bool>::value &&
!std::is_same<T, Char>::value)>
-OutputIt write(OutputIt out, T value) {
+FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt {
auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value);
bool negative = is_negative(value);
// Don't do -abs_value since it trips unsigned-integer-overflow sanitizer.
return base_iterator(out, it);
}
-template <typename Char, typename OutputIt>
-OutputIt write(OutputIt out, bool value) {
- return write<Char>(out, string_view(value ? "true" : "false"));
+// FMT_ENABLE_IF() condition separated to workaround an MSVC bug.
+template <
+ typename Char, typename OutputIt, typename T,
+ bool check =
+ std::is_enum<T>::value && !std::is_same<T, Char>::value &&
+ mapped_type_constant<T, basic_format_context<OutputIt, Char>>::value !=
+ type::custom_type,
+ FMT_ENABLE_IF(check)>
+FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt {
+ return write<Char>(
+ out, static_cast<typename std::underlying_type<T>::type>(value));
+}
+
+template <typename Char, typename OutputIt, typename T,
+ FMT_ENABLE_IF(std::is_same<T, bool>::value)>
+FMT_CONSTEXPR auto write(OutputIt out, T value,
+ const basic_format_specs<Char>& specs = {},
+ locale_ref = {}) -> OutputIt {
+ return specs.type != presentation_type::none &&
+ specs.type != presentation_type::string
+ ? write(out, value ? 1 : 0, specs, {})
+ : write_bytes(out, value ? "true" : "false", specs);
}
template <typename Char, typename OutputIt>
-OutputIt write(OutputIt out, Char value) {
+FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt {
auto it = reserve(out, 1);
*it++ = value;
return base_iterator(out, it);
}
template <typename Char, typename OutputIt>
-OutputIt write(OutputIt out, const Char* value) {
+FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value)
+ -> OutputIt {
if (!value) {
- FMT_THROW(format_error("string pointer is null"));
+ throw_format_error("string pointer is null");
} else {
- auto length = std::char_traits<Char>::length(value);
- out = write(out, basic_string_view<Char>(value, length));
+ out = write(out, basic_string_view<Char>(value));
}
return out;
}
-template <typename Char, typename OutputIt>
-OutputIt write(OutputIt out, const void* value) {
- return write_ptr<Char>(out, to_uintptr(value), nullptr);
+template <typename Char, typename OutputIt, typename T,
+ FMT_ENABLE_IF(std::is_same<T, void>::value)>
+auto write(OutputIt out, const T* value,
+ const basic_format_specs<Char>& specs = {}, locale_ref = {})
+ -> OutputIt {
+ check_pointer_type_spec(specs.type, error_handler());
+ return write_ptr<Char>(out, to_uintptr(value), &specs);
}
-template <typename Char, typename OutputIt, typename T>
-auto write(OutputIt out, const T& value) -> typename std::enable_if<
- mapped_type_constant<T, basic_format_context<OutputIt, Char>>::value ==
- type::custom_type,
- OutputIt>::type {
- using context_type = basic_format_context<OutputIt, Char>;
+// A write overload that handles implicit conversions.
+template <typename Char, typename OutputIt, typename T,
+ typename Context = basic_format_context<OutputIt, Char>>
+FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> enable_if_t<
+ std::is_class<T>::value && !is_string<T>::value &&
+ !std::is_same<T, Char>::value &&
+ !std::is_same<const T&,
+ decltype(arg_mapper<Context>().map(value))>::value,
+ OutputIt> {
+ return write<Char>(out, arg_mapper<Context>().map(value));
+}
+
+template <typename Char, typename OutputIt, typename T,
+ typename Context = basic_format_context<OutputIt, Char>>
+FMT_CONSTEXPR auto write(OutputIt out, const T& value)
+ -> enable_if_t<mapped_type_constant<T, Context>::value == type::custom_type,
+ OutputIt> {
using formatter_type =
- conditional_t<has_formatter<T, context_type>::value,
- typename context_type::template formatter_type<T>,
+ conditional_t<has_formatter<T, Context>::value,
+ typename Context::template formatter_type<T>,
fallback_formatter<T, Char>>;
- context_type ctx(out, {}, {});
+ auto ctx = Context(out, {}, {});
return formatter_type().format(value, ctx);
}
// An argument visitor that formats the argument and writes it via the output
// iterator. It's a class and not a generic lambda for compatibility with C++11.
-template <typename OutputIt, typename Char> struct default_arg_formatter {
- using context = basic_format_context<OutputIt, Char>;
+template <typename Char> struct default_arg_formatter {
+ using iterator = buffer_appender<Char>;
+ using context = buffer_context<Char>;
- OutputIt out;
+ iterator out;
basic_format_args<context> args;
locale_ref loc;
- template <typename T> OutputIt operator()(T value) {
+ template <typename T> auto operator()(T value) -> iterator {
return write<Char>(out, value);
}
-
- OutputIt operator()(typename basic_format_arg<context>::handle handle) {
+ auto operator()(typename basic_format_arg<context>::handle h) -> iterator {
basic_format_parse_context<Char> parse_ctx({});
- basic_format_context<OutputIt, Char> format_ctx(out, args, loc);
- handle.format(parse_ctx, format_ctx);
+ context format_ctx(out, args, loc);
+ h.format(parse_ctx, format_ctx);
return format_ctx.out();
}
};
-template <typename OutputIt, typename Char,
- typename ErrorHandler = error_handler>
-class arg_formatter_base {
- public:
- using iterator = OutputIt;
- using char_type = Char;
- using format_specs = basic_format_specs<Char>;
+template <typename Char> struct arg_formatter {
+ using iterator = buffer_appender<Char>;
+ using context = buffer_context<Char>;
- private:
- iterator out_;
- locale_ref locale_;
- format_specs* specs_;
+ iterator out;
+ const basic_format_specs<Char>& specs;
+ locale_ref locale;
- // Attempts to reserve space for n extra characters in the output range.
- // Returns a pointer to the reserved range or a reference to out_.
- auto reserve(size_t n) -> decltype(detail::reserve(out_, n)) {
- return detail::reserve(out_, n);
+ template <typename T>
+ FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator {
+ return detail::write(out, value, specs, locale);
}
-
- using reserve_iterator = remove_reference_t<decltype(
- detail::reserve(std::declval<iterator&>(), 0))>;
-
- template <typename T> void write_int(T value, const format_specs& spec) {
- using uint_type = uint32_or_64_or_128_t<T>;
- int_writer<iterator, Char, uint_type> w(out_, locale_, value, spec);
- handle_int_type_spec(spec.type, w);
- out_ = w.out;
- }
-
- void write(char value) {
- auto&& it = reserve(1);
- *it++ = value;
- }
-
- template <typename Ch, FMT_ENABLE_IF(std::is_same<Ch, Char>::value)>
- void write(Ch value) {
- out_ = detail::write<Char>(out_, value);
- }
-
- void write(string_view value) {
- auto&& it = reserve(value.size());
- it = copy_str<Char>(value.begin(), value.end(), it);
- }
- void write(wstring_view value) {
- static_assert(std::is_same<Char, wchar_t>::value, "");
- auto&& it = reserve(value.size());
- it = std::copy(value.begin(), value.end(), it);
- }
-
- template <typename Ch>
- void write(const Ch* s, size_t size, const format_specs& specs) {
- auto width = specs.width != 0
- ? count_code_points(basic_string_view<Ch>(s, size))
- : 0;
- out_ = write_padded(out_, specs, size, width, [=](reserve_iterator it) {
- return copy_str<Char>(s, s + size, it);
- });
- }
-
- template <typename Ch>
- void write(basic_string_view<Ch> s, const format_specs& specs = {}) {
- out_ = detail::write(out_, s, specs);
- }
-
- void write_pointer(const void* p) {
- out_ = write_ptr<char_type>(out_, to_uintptr(p), specs_);
- }
-
- struct char_spec_handler : ErrorHandler {
- arg_formatter_base& formatter;
- Char value;
-
- char_spec_handler(arg_formatter_base& f, Char val)
- : formatter(f), value(val) {}
-
- void on_int() {
- // char is only formatted as int if there are specs.
- formatter.write_int(static_cast<int>(value), *formatter.specs_);
- }
- void on_char() {
- if (formatter.specs_)
- formatter.out_ = write_char(formatter.out_, value, *formatter.specs_);
- else
- formatter.write(value);
- }
- };
-
- struct cstring_spec_handler : error_handler {
- arg_formatter_base& formatter;
- const Char* value;
-
- cstring_spec_handler(arg_formatter_base& f, const Char* val)
- : formatter(f), value(val) {}
-
- void on_string() { formatter.write(value); }
- void on_pointer() { formatter.write_pointer(value); }
- };
-
- protected:
- iterator out() { return out_; }
- format_specs* specs() { return specs_; }
-
- void write(bool value) {
- if (specs_)
- write(string_view(value ? "true" : "false"), *specs_);
- else
- out_ = detail::write<Char>(out_, value);
- }
-
- void write(const Char* value) {
- if (!value) {
- FMT_THROW(format_error("string pointer is null"));
- } else {
- auto length = std::char_traits<char_type>::length(value);
- basic_string_view<char_type> sv(value, length);
- specs_ ? write(sv, *specs_) : write(sv);
- }
- }
-
- public:
- arg_formatter_base(OutputIt out, format_specs* s, locale_ref loc)
- : out_(out), locale_(loc), specs_(s) {}
-
- iterator operator()(monostate) {
- FMT_ASSERT(false, "invalid argument type");
- return out_;
- }
-
- template <typename T, FMT_ENABLE_IF(is_integral<T>::value)>
- FMT_INLINE iterator operator()(T value) {
- if (specs_)
- write_int(value, *specs_);
- else
- out_ = detail::write<Char>(out_, value);
- return out_;
- }
-
- iterator operator()(Char value) {
- handle_char_specs(specs_,
- char_spec_handler(*this, static_cast<Char>(value)));
- return out_;
- }
-
- iterator operator()(bool value) {
- if (specs_ && specs_->type) return (*this)(value ? 1 : 0);
- write(value != 0);
- return out_;
- }
-
- template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
- iterator operator()(T value) {
- auto specs = specs_ ? *specs_ : format_specs();
- if (const_check(is_supported_floating_point(value)))
- out_ = detail::write(out_, value, specs, locale_);
- else
- FMT_ASSERT(false, "unsupported float argument type");
- return out_;
- }
-
- iterator operator()(const Char* value) {
- if (!specs_) return write(value), out_;
- handle_cstring_type_spec(specs_->type, cstring_spec_handler(*this, value));
- return out_;
- }
-
- iterator operator()(basic_string_view<Char> value) {
- if (specs_) {
- check_string_type_spec(specs_->type, error_handler());
- write(value, *specs_);
- } else {
- write(value);
- }
- return out_;
- }
-
- iterator operator()(const void* value) {
- if (specs_) check_pointer_type_spec(specs_->type, error_handler());
- write_pointer(value);
- return out_;
+ auto operator()(typename basic_format_arg<context>::handle) -> iterator {
+ // User-defined types are handled separately because they require access
+ // to the parse context.
+ return out;
}
};
-/** The default argument formatter. */
-template <typename OutputIt, typename Char>
-class arg_formatter : public arg_formatter_base<OutputIt, Char> {
- private:
- using char_type = Char;
- using base = arg_formatter_base<OutputIt, Char>;
- using context_type = basic_format_context<OutputIt, Char>;
+template <typename Char> struct custom_formatter {
+ basic_format_parse_context<Char>& parse_ctx;
+ buffer_context<Char>& ctx;
- context_type& ctx_;
- basic_format_parse_context<char_type>* parse_ctx_;
- const Char* ptr_;
-
- public:
- using iterator = typename base::iterator;
- using format_specs = typename base::format_specs;
-
- /**
- \rst
- Constructs an argument formatter object.
- *ctx* is a reference to the formatting context,
- *specs* contains format specifier information for standard argument types.
- \endrst
- */
- explicit arg_formatter(
- context_type& ctx,
- basic_format_parse_context<char_type>* parse_ctx = nullptr,
- format_specs* specs = nullptr, const Char* ptr = nullptr)
- : base(ctx.out(), specs, ctx.locale()),
- ctx_(ctx),
- parse_ctx_(parse_ctx),
- ptr_(ptr) {}
-
- using base::operator();
-
- /** Formats an argument of a user-defined type. */
- iterator operator()(typename basic_format_arg<context_type>::handle handle) {
- if (ptr_) advance_to(*parse_ctx_, ptr_);
- handle.format(*parse_ctx_, ctx_);
- return ctx_.out();
+ void operator()(
+ typename basic_format_arg<buffer_context<Char>>::handle h) const {
+ h.format(parse_ctx, ctx);
}
-};
-
-template <typename Char> FMT_CONSTEXPR bool is_name_start(Char c) {
- return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c;
-}
-
-// Parses the range [begin, end) as an unsigned integer. This function assumes
-// that the range is non-empty and the first character is a digit.
-template <typename Char, typename ErrorHandler>
-FMT_CONSTEXPR int parse_nonnegative_int(const Char*& begin, const Char* end,
- ErrorHandler&& eh) {
- FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', "");
- unsigned value = 0;
- // Convert to unsigned to prevent a warning.
- constexpr unsigned max_int = max_value<int>();
- unsigned big = max_int / 10;
- do {
- // Check for overflow.
- if (value > big) {
- value = max_int + 1;
- break;
- }
- value = value * 10 + unsigned(*begin - '0');
- ++begin;
- } while (begin != end && '0' <= *begin && *begin <= '9');
- if (value > max_int) eh.on_error("number is too big");
- return static_cast<int>(value);
-}
-
-template <typename Context> class custom_formatter {
- private:
- using char_type = typename Context::char_type;
-
- basic_format_parse_context<char_type>& parse_ctx_;
- Context& ctx_;
-
- public:
- explicit custom_formatter(basic_format_parse_context<char_type>& parse_ctx,
- Context& ctx)
- : parse_ctx_(parse_ctx), ctx_(ctx) {}
-
- void operator()(typename basic_format_arg<Context>::handle h) const {
- h.format(parse_ctx_, ctx_);
- }
-
template <typename T> void operator()(T) const {}
};
explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {}
template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>
- FMT_CONSTEXPR unsigned long long operator()(T value) {
+ FMT_CONSTEXPR auto operator()(T value) -> unsigned long long {
if (is_negative(value)) handler_.on_error("negative width");
return static_cast<unsigned long long>(value);
}
template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)>
- FMT_CONSTEXPR unsigned long long operator()(T) {
+ FMT_CONSTEXPR auto operator()(T) -> unsigned long long {
handler_.on_error("width is not integer");
return 0;
}
explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {}
template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>
- FMT_CONSTEXPR unsigned long long operator()(T value) {
+ FMT_CONSTEXPR auto operator()(T value) -> unsigned long long {
if (is_negative(value)) handler_.on_error("negative precision");
return static_cast<unsigned long long>(value);
}
template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)>
- FMT_CONSTEXPR unsigned long long operator()(T) {
+ FMT_CONSTEXPR auto operator()(T) -> unsigned long long {
handler_.on_error("precision is not integer");
return 0;
}
ErrorHandler& handler_;
};
-// A format specifier handler that sets fields in basic_format_specs.
-template <typename Char> class specs_setter {
- public:
- explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char>& specs)
- : specs_(specs) {}
-
- FMT_CONSTEXPR specs_setter(const specs_setter& other)
- : specs_(other.specs_) {}
-
- FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; }
- FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
- specs_.fill = fill;
- }
- FMT_CONSTEXPR void on_plus() { specs_.sign = sign::plus; }
- FMT_CONSTEXPR void on_minus() { specs_.sign = sign::minus; }
- FMT_CONSTEXPR void on_space() { specs_.sign = sign::space; }
- FMT_CONSTEXPR void on_hash() { specs_.alt = true; }
-
- FMT_CONSTEXPR void on_zero() {
- specs_.align = align::numeric;
- specs_.fill[0] = Char('0');
- }
-
- FMT_CONSTEXPR void on_width(int width) { specs_.width = width; }
- FMT_CONSTEXPR void on_precision(int precision) {
- specs_.precision = precision;
- }
- FMT_CONSTEXPR void end_precision() {}
-
- FMT_CONSTEXPR void on_type(Char type) {
- specs_.type = static_cast<char>(type);
- }
-
- protected:
- basic_format_specs<Char>& specs_;
-};
-
-template <typename ErrorHandler> class numeric_specs_checker {
- public:
- FMT_CONSTEXPR numeric_specs_checker(ErrorHandler& eh, detail::type arg_type)
- : error_handler_(eh), arg_type_(arg_type) {}
-
- FMT_CONSTEXPR void require_numeric_argument() {
- if (!is_arithmetic_type(arg_type_))
- error_handler_.on_error("format specifier requires numeric argument");
- }
-
- FMT_CONSTEXPR void check_sign() {
- require_numeric_argument();
- if (is_integral_type(arg_type_) && arg_type_ != type::int_type &&
- arg_type_ != type::long_long_type && arg_type_ != type::char_type) {
- error_handler_.on_error("format specifier requires signed argument");
- }
- }
-
- FMT_CONSTEXPR void check_precision() {
- if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type)
- error_handler_.on_error("precision not allowed for this argument type");
- }
-
- private:
- ErrorHandler& error_handler_;
- detail::type arg_type_;
-};
-
-// A format specifier handler that checks if specifiers are consistent with the
-// argument type.
-template <typename Handler> class specs_checker : public Handler {
- private:
- numeric_specs_checker<Handler> checker_;
-
- // Suppress an MSVC warning about using this in initializer list.
- FMT_CONSTEXPR Handler& error_handler() { return *this; }
-
- public:
- FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type)
- : Handler(handler), checker_(error_handler(), arg_type) {}
-
- FMT_CONSTEXPR specs_checker(const specs_checker& other)
- : Handler(other), checker_(error_handler(), other.arg_type_) {}
-
- FMT_CONSTEXPR void on_align(align_t align) {
- if (align == align::numeric) checker_.require_numeric_argument();
- Handler::on_align(align);
- }
-
- FMT_CONSTEXPR void on_plus() {
- checker_.check_sign();
- Handler::on_plus();
- }
-
- FMT_CONSTEXPR void on_minus() {
- checker_.check_sign();
- Handler::on_minus();
- }
-
- FMT_CONSTEXPR void on_space() {
- checker_.check_sign();
- Handler::on_space();
- }
-
- FMT_CONSTEXPR void on_hash() {
- checker_.require_numeric_argument();
- Handler::on_hash();
- }
-
- FMT_CONSTEXPR void on_zero() {
- checker_.require_numeric_argument();
- Handler::on_zero();
- }
-
- FMT_CONSTEXPR void end_precision() { checker_.check_precision(); }
-};
-
template <template <typename> class Handler, typename FormatArg,
typename ErrorHandler>
-FMT_CONSTEXPR int get_dynamic_spec(FormatArg arg, ErrorHandler eh) {
+FMT_CONSTEXPR auto get_dynamic_spec(FormatArg arg, ErrorHandler eh) -> int {
unsigned long long value = visit_format_arg(Handler<ErrorHandler>(eh), arg);
if (value > to_unsigned(max_value<int>())) eh.on_error("number is too big");
return static_cast<int>(value);
}
-struct auto_id {};
-
template <typename Context, typename ID>
-FMT_CONSTEXPR typename Context::format_arg get_arg(Context& ctx, ID id) {
+FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) ->
+ typename Context::format_arg {
auto arg = ctx.arg(id);
if (!arg) ctx.on_error("argument not found");
return arg;
}
// The standard format specifier handler with checking.
-template <typename ParseContext, typename Context>
-class specs_handler : public specs_setter<typename Context::char_type> {
- public:
- using char_type = typename Context::char_type;
-
- FMT_CONSTEXPR specs_handler(basic_format_specs<char_type>& specs,
- ParseContext& parse_ctx, Context& ctx)
- : specs_setter<char_type>(specs),
- parse_context_(parse_ctx),
- context_(ctx) {}
-
- template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
- this->specs_.width = get_dynamic_spec<width_checker>(
- get_arg(arg_id), context_.error_handler());
- }
-
- template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
- this->specs_.precision = get_dynamic_spec<precision_checker>(
- get_arg(arg_id), context_.error_handler());
- }
-
- void on_error(const char* message) { context_.on_error(message); }
-
+template <typename Char> class specs_handler : public specs_setter<Char> {
private:
+ basic_format_parse_context<Char>& parse_context_;
+ buffer_context<Char>& context_;
+
// This is only needed for compatibility with gcc 4.4.
- using format_arg = typename Context::format_arg;
+ using format_arg = basic_format_arg<buffer_context<Char>>;
- FMT_CONSTEXPR format_arg get_arg(auto_id) {
+ FMT_CONSTEXPR auto get_arg(auto_id) -> format_arg {
return detail::get_arg(context_, parse_context_.next_arg_id());
}
- FMT_CONSTEXPR format_arg get_arg(int arg_id) {
+ FMT_CONSTEXPR auto get_arg(int arg_id) -> format_arg {
parse_context_.check_arg_id(arg_id);
return detail::get_arg(context_, arg_id);
}
- FMT_CONSTEXPR format_arg get_arg(basic_string_view<char_type> arg_id) {
+ FMT_CONSTEXPR auto get_arg(basic_string_view<Char> arg_id) -> format_arg {
parse_context_.check_arg_id(arg_id);
return detail::get_arg(context_, arg_id);
}
- ParseContext& parse_context_;
- Context& context_;
-};
-
-enum class arg_id_kind { none, index, name };
-
-// An argument reference.
-template <typename Char> struct arg_ref {
- FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}
-
- FMT_CONSTEXPR explicit arg_ref(int index)
- : kind(arg_id_kind::index), val(index) {}
- FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name)
- : kind(arg_id_kind::name), val(name) {}
-
- FMT_CONSTEXPR arg_ref& operator=(int idx) {
- kind = arg_id_kind::index;
- val.index = idx;
- return *this;
- }
-
- arg_id_kind kind;
- union value {
- FMT_CONSTEXPR value(int id = 0) : index{id} {}
- FMT_CONSTEXPR value(basic_string_view<Char> n) : name(n) {}
-
- int index;
- basic_string_view<Char> name;
- } val;
-};
-
-// Format specifiers with width and precision resolved at formatting rather
-// than parsing time to allow re-using the same parsed specifiers with
-// different sets of arguments (precompilation of format strings).
-template <typename Char>
-struct dynamic_format_specs : basic_format_specs<Char> {
- arg_ref<Char> width_ref;
- arg_ref<Char> precision_ref;
-};
-
-// Format spec handler that saves references to arguments representing dynamic
-// width and precision to be resolved at formatting time.
-template <typename ParseContext>
-class dynamic_specs_handler
- : public specs_setter<typename ParseContext::char_type> {
public:
- using char_type = typename ParseContext::char_type;
-
- FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs<char_type>& specs,
- ParseContext& ctx)
- : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {}
-
- FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other)
- : specs_setter<char_type>(other),
- specs_(other.specs_),
- context_(other.context_) {}
+ FMT_CONSTEXPR specs_handler(basic_format_specs<Char>& specs,
+ basic_format_parse_context<Char>& parse_ctx,
+ buffer_context<Char>& ctx)
+ : specs_setter<Char>(specs), parse_context_(parse_ctx), context_(ctx) {}
template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
- specs_.width_ref = make_arg_ref(arg_id);
+ this->specs_.width = get_dynamic_spec<width_checker>(
+ get_arg(arg_id), context_.error_handler());
}
template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
- specs_.precision_ref = make_arg_ref(arg_id);
- }
-
- FMT_CONSTEXPR void on_error(const char* message) {
- context_.on_error(message);
- }
-
- private:
- using arg_ref_type = arg_ref<char_type>;
-
- FMT_CONSTEXPR arg_ref_type make_arg_ref(int arg_id) {
- context_.check_arg_id(arg_id);
- return arg_ref_type(arg_id);
- }
-
- FMT_CONSTEXPR arg_ref_type make_arg_ref(auto_id) {
- return arg_ref_type(context_.next_arg_id());
- }
-
- FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<char_type> arg_id) {
- context_.check_arg_id(arg_id);
- basic_string_view<char_type> format_str(
- context_.begin(), to_unsigned(context_.end() - context_.begin()));
- return arg_ref_type(arg_id);
- }
-
- dynamic_format_specs<char_type>& specs_;
- ParseContext& context_;
-};
-
-template <typename Char, typename IDHandler>
-FMT_CONSTEXPR const Char* parse_arg_id(const Char* begin, const Char* end,
- IDHandler&& handler) {
- FMT_ASSERT(begin != end, "");
- Char c = *begin;
- if (c == '}' || c == ':') {
- handler();
- return begin;
- }
- if (c >= '0' && c <= '9') {
- int index = 0;
- if (c != '0')
- index = parse_nonnegative_int(begin, end, handler);
- else
- ++begin;
- if (begin == end || (*begin != '}' && *begin != ':'))
- handler.on_error("invalid format string");
- else
- handler(index);
- return begin;
- }
- if (!is_name_start(c)) {
- handler.on_error("invalid format string");
- return begin;
- }
- auto it = begin;
- do {
- ++it;
- } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9')));
- handler(basic_string_view<Char>(begin, to_unsigned(it - begin)));
- return it;
-}
-
-// Adapts SpecHandler to IDHandler API for dynamic width.
-template <typename SpecHandler, typename Char> struct width_adapter {
- explicit FMT_CONSTEXPR width_adapter(SpecHandler& h) : handler(h) {}
-
- FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); }
- FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); }
- FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
- handler.on_dynamic_width(id);
- }
-
- FMT_CONSTEXPR void on_error(const char* message) {
- handler.on_error(message);
- }
-
- SpecHandler& handler;
-};
-
-// Adapts SpecHandler to IDHandler API for dynamic precision.
-template <typename SpecHandler, typename Char> struct precision_adapter {
- explicit FMT_CONSTEXPR precision_adapter(SpecHandler& h) : handler(h) {}
-
- FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); }
- FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); }
- FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
- handler.on_dynamic_precision(id);
- }
-
- FMT_CONSTEXPR void on_error(const char* message) {
- handler.on_error(message);
+ this->specs_.precision = get_dynamic_spec<precision_checker>(
+ get_arg(arg_id), context_.error_handler());
}
- SpecHandler& handler;
+ void on_error(const char* message) { context_.on_error(message); }
};
-template <typename Char>
-FMT_CONSTEXPR int code_point_length(const Char* begin) {
- if (const_check(sizeof(Char) != 1)) return 1;
- constexpr char lengths[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0};
- int len = lengths[static_cast<unsigned char>(*begin) >> 3];
-
- // Compute the pointer to the next character early so that the next
- // iteration can start working on the next character. Neither Clang
- // nor GCC figure out this reordering on their own.
- return len + !len;
-}
-
-template <typename Char> constexpr bool is_ascii_letter(Char c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
-}
-
-// Converts a character to ASCII. Returns a number > 127 on conversion failure.
-template <typename Char, FMT_ENABLE_IF(std::is_integral<Char>::value)>
-constexpr Char to_ascii(Char value) {
- return value;
-}
-template <typename Char, FMT_ENABLE_IF(std::is_enum<Char>::value)>
-constexpr typename std::underlying_type<Char>::type to_ascii(Char value) {
- return value;
-}
-
-// Parses fill and alignment.
-template <typename Char, typename Handler>
-FMT_CONSTEXPR const Char* parse_align(const Char* begin, const Char* end,
- Handler&& handler) {
- FMT_ASSERT(begin != end, "");
- auto align = align::none;
- auto p = begin + code_point_length(begin);
- if (p >= end) p = begin;
- for (;;) {
- switch (to_ascii(*p)) {
- case '<':
- align = align::left;
- break;
- case '>':
- align = align::right;
- break;
-#if FMT_DEPRECATED_NUMERIC_ALIGN
- case '=':
- align = align::numeric;
- break;
-#endif
- case '^':
- align = align::center;
- break;
- }
- if (align != align::none) {
- if (p != begin) {
- auto c = *begin;
- if (c == '{')
- return handler.on_error("invalid fill character '{'"), begin;
- handler.on_fill(basic_string_view<Char>(begin, to_unsigned(p - begin)));
- begin = p + 1;
- } else
- ++begin;
- handler.on_align(align);
- break;
- } else if (p == begin) {
- break;
- }
- p = begin;
- }
- return begin;
-}
-
-template <typename Char, typename Handler>
-FMT_CONSTEXPR const Char* parse_width(const Char* begin, const Char* end,
- Handler&& handler) {
- FMT_ASSERT(begin != end, "");
- if ('0' <= *begin && *begin <= '9') {
- handler.on_width(parse_nonnegative_int(begin, end, handler));
- } else if (*begin == '{') {
- ++begin;
- if (begin != end)
- begin = parse_arg_id(begin, end, width_adapter<Handler, Char>(handler));
- if (begin == end || *begin != '}')
- return handler.on_error("invalid format string"), begin;
- ++begin;
- }
- return begin;
-}
-
-template <typename Char, typename Handler>
-FMT_CONSTEXPR const Char* parse_precision(const Char* begin, const Char* end,
- Handler&& handler) {
- ++begin;
- auto c = begin != end ? *begin : Char();
- if ('0' <= c && c <= '9') {
- handler.on_precision(parse_nonnegative_int(begin, end, handler));
- } else if (c == '{') {
- ++begin;
- if (begin != end) {
- begin =
- parse_arg_id(begin, end, precision_adapter<Handler, Char>(handler));
- }
- if (begin == end || *begin++ != '}')
- return handler.on_error("invalid format string"), begin;
- } else {
- return handler.on_error("missing precision specifier"), begin;
- }
- handler.end_precision();
- return begin;
-}
-
-// Parses standard format specifiers and sends notifications about parsed
-// components to handler.
-template <typename Char, typename SpecHandler>
-FMT_CONSTEXPR const Char* parse_format_specs(const Char* begin, const Char* end,
- SpecHandler&& handler) {
- if (begin == end) return begin;
-
- begin = parse_align(begin, end, handler);
- if (begin == end) return begin;
-
- // Parse sign.
- switch (to_ascii(*begin)) {
- case '+':
- handler.on_plus();
- ++begin;
- break;
- case '-':
- handler.on_minus();
- ++begin;
+template <template <typename> class Handler, typename Context>
+FMT_CONSTEXPR void handle_dynamic_spec(int& value,
+ arg_ref<typename Context::char_type> ref,
+ Context& ctx) {
+ switch (ref.kind) {
+ case arg_id_kind::none:
break;
- case ' ':
- handler.on_space();
- ++begin;
+ case arg_id_kind::index:
+ value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.index),
+ ctx.error_handler());
+ break;
+ case arg_id_kind::name:
+ value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.name),
+ ctx.error_handler());
break;
}
- if (begin == end) return begin;
-
- if (*begin == '#') {
- handler.on_hash();
- if (++begin == end) return begin;
- }
-
- // Parse zero flag.
- if (*begin == '0') {
- handler.on_zero();
- if (++begin == end) return begin;
- }
-
- begin = parse_width(begin, end, handler);
- if (begin == end) return begin;
-
- // Parse precision.
- if (*begin == '.') {
- begin = parse_precision(begin, end, handler);
- }
-
- // Parse type.
- if (begin != end && *begin != '}') handler.on_type(*begin++);
- return begin;
-}
-
-// Return the result via the out param to workaround gcc bug 77539.
-template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
-FMT_CONSTEXPR bool find(Ptr first, Ptr last, T value, Ptr& out) {
- for (out = first; out != last; ++out) {
- if (*out == value) return true;
- }
- return false;
-}
-
-template <>
-inline bool find<false, char>(const char* first, const char* last, char value,
- const char*& out) {
- out = static_cast<const char*>(
- std::memchr(first, value, detail::to_unsigned(last - first)));
- return out != nullptr;
-}
-
-template <typename Handler, typename Char> struct id_adapter {
- Handler& handler;
- int arg_id;
-
- FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); }
- FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); }
- FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
- arg_id = handler.on_arg_id(id);
- }
- FMT_CONSTEXPR void on_error(const char* message) {
- handler.on_error(message);
- }
-};
-
-template <typename Char, typename Handler>
-FMT_CONSTEXPR const Char* parse_replacement_field(const Char* begin,
- const Char* end,
- Handler&& handler) {
- ++begin;
- if (begin == end) return handler.on_error("invalid format string"), end;
- if (*begin == '}') {
- handler.on_replacement_field(handler.on_arg_id(), begin);
- } else if (*begin == '{') {
- handler.on_text(begin, begin + 1);
- } else {
- auto adapter = id_adapter<Handler, Char>{handler, 0};
- begin = parse_arg_id(begin, end, adapter);
- Char c = begin != end ? *begin : Char();
- if (c == '}') {
- handler.on_replacement_field(adapter.arg_id, begin);
- } else if (c == ':') {
- begin = handler.on_format_specs(adapter.arg_id, begin + 1, end);
- if (begin == end || *begin != '}')
- return handler.on_error("unknown format specifier"), end;
- } else {
- return handler.on_error("missing '}' in format string"), end;
- }
- }
- return begin + 1;
-}
-
-template <bool IS_CONSTEXPR, typename Char, typename Handler>
-FMT_CONSTEXPR_DECL FMT_INLINE void parse_format_string(
- basic_string_view<Char> format_str, Handler&& handler) {
- auto begin = format_str.data();
- auto end = begin + format_str.size();
- if (end - begin < 32) {
- // Use a simple loop instead of memchr for small strings.
- const Char* p = begin;
- while (p != end) {
- auto c = *p++;
- if (c == '{') {
- handler.on_text(begin, p - 1);
- begin = p = parse_replacement_field(p - 1, end, handler);
- } else if (c == '}') {
- if (p == end || *p != '}')
- return handler.on_error("unmatched '}' in format string");
- handler.on_text(begin, p);
- begin = ++p;
- }
- }
- handler.on_text(begin, end);
- return;
- }
- struct writer {
- FMT_CONSTEXPR void operator()(const Char* pbegin, const Char* pend) {
- if (pbegin == pend) return;
- for (;;) {
- const Char* p = nullptr;
- if (!find<IS_CONSTEXPR>(pbegin, pend, '}', p))
- return handler_.on_text(pbegin, pend);
- ++p;
- if (p == pend || *p != '}')
- return handler_.on_error("unmatched '}' in format string");
- handler_.on_text(pbegin, p);
- pbegin = p + 1;
- }
- }
- Handler& handler_;
- } write{handler};
- while (begin != end) {
- // Doing two passes with memchr (one for '{' and another for '}') is up to
- // 2.5x faster than the naive one-pass implementation on big format strings.
- const Char* p = begin;
- if (*begin != '{' && !find<IS_CONSTEXPR>(begin + 1, end, '{', p))
- return write(begin, end);
- write(begin, p);
- begin = parse_replacement_field(p, end, handler);
- }
-}
-
-template <typename T, typename ParseContext>
-FMT_CONSTEXPR const typename ParseContext::char_type* parse_format_specs(
- ParseContext& ctx) {
- using char_type = typename ParseContext::char_type;
- using context = buffer_context<char_type>;
- using mapped_type =
- conditional_t<detail::mapped_type_constant<T, context>::value !=
- type::custom_type,
- decltype(arg_mapper<context>().map(std::declval<T>())), T>;
- auto f = conditional_t<has_formatter<mapped_type, context>::value,
- formatter<mapped_type, char_type>,
- detail::fallback_formatter<T, char_type>>();
- return f.parse(ctx);
-}
-
-template <typename OutputIt, typename Char, typename Context>
-struct format_handler : detail::error_handler {
- basic_format_parse_context<Char> parse_context;
- Context context;
-
- format_handler(OutputIt out, basic_string_view<Char> str,
- basic_format_args<Context> format_args, detail::locale_ref loc)
- : parse_context(str), context(out, format_args, loc) {}
-
- void on_text(const Char* begin, const Char* end) {
- auto size = to_unsigned(end - begin);
- auto out = context.out();
- auto&& it = reserve(out, size);
- it = std::copy_n(begin, size, it);
- context.advance_to(out);
- }
-
- int on_arg_id() { return parse_context.next_arg_id(); }
- int on_arg_id(int id) { return parse_context.check_arg_id(id), id; }
- int on_arg_id(basic_string_view<Char> id) {
- int arg_id = context.arg_id(id);
- if (arg_id < 0) on_error("argument not found");
- return arg_id;
- }
-
- FMT_INLINE void on_replacement_field(int id, const Char*) {
- auto arg = get_arg(context, id);
- context.advance_to(visit_format_arg(
- default_arg_formatter<OutputIt, Char>{context.out(), context.args(),
- context.locale()},
- arg));
- }
-
- const Char* on_format_specs(int id, const Char* begin, const Char* end) {
- auto arg = get_arg(context, id);
- if (arg.type() == type::custom_type) {
- advance_to(parse_context, begin);
- visit_format_arg(custom_formatter<Context>(parse_context, context), arg);
- return parse_context.begin();
- }
- auto specs = basic_format_specs<Char>();
- if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin)) {
- specs.type = static_cast<char>(*begin++);
- } else {
- using parse_context_t = basic_format_parse_context<Char>;
- specs_checker<specs_handler<parse_context_t, Context>> handler(
- specs_handler<parse_context_t, Context>(specs, parse_context,
- context),
- arg.type());
- begin = parse_format_specs(begin, end, handler);
- if (begin == end || *begin != '}')
- on_error("missing '}' in format string");
- }
- context.advance_to(visit_format_arg(
- arg_formatter<OutputIt, Char>(context, &parse_context, &specs), arg));
- return begin;
- }
-};
-
-// A parse context with extra argument id checks. It is only used at compile
-// time because adding checks at runtime would introduce substantial overhead
-// and would be redundant since argument ids are checked when arguments are
-// retrieved anyway.
-template <typename Char, typename ErrorHandler = error_handler>
-class compile_parse_context
- : public basic_format_parse_context<Char, ErrorHandler> {
- private:
- int num_args_;
- using base = basic_format_parse_context<Char, ErrorHandler>;
-
- public:
- explicit FMT_CONSTEXPR compile_parse_context(
- basic_string_view<Char> format_str, int num_args = max_value<int>(),
- ErrorHandler eh = {})
- : base(format_str, eh), num_args_(num_args) {}
-
- FMT_CONSTEXPR int next_arg_id() {
- int id = base::next_arg_id();
- if (id >= num_args_) this->on_error("argument not found");
- return id;
- }
-
- FMT_CONSTEXPR void check_arg_id(int id) {
- base::check_arg_id(id);
- if (id >= num_args_) this->on_error("argument not found");
- }
- using base::check_arg_id;
-};
-
-template <typename Char, typename ErrorHandler, typename... Args>
-class format_string_checker {
- public:
- explicit FMT_CONSTEXPR format_string_checker(
- basic_string_view<Char> format_str, ErrorHandler eh)
- : context_(format_str, num_args, eh),
- parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {}
-
- FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
-
- FMT_CONSTEXPR int on_arg_id() { return context_.next_arg_id(); }
- FMT_CONSTEXPR int on_arg_id(int id) { return context_.check_arg_id(id), id; }
- FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) {
- on_error("compile-time checks don't support named arguments");
- return 0;
- }
-
- FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
-
- FMT_CONSTEXPR const Char* on_format_specs(int id, const Char* begin,
- const Char*) {
- advance_to(context_, begin);
- return id < num_args ? parse_funcs_[id](context_) : begin;
- }
-
- FMT_CONSTEXPR void on_error(const char* message) {
- context_.on_error(message);
- }
-
- private:
- using parse_context_type = compile_parse_context<Char, ErrorHandler>;
- enum { num_args = sizeof...(Args) };
-
- // Format specifier parsing function.
- using parse_func = const Char* (*)(parse_context_type&);
-
- parse_context_type context_;
- parse_func parse_funcs_[num_args > 0 ? num_args : 1];
-};
-
-// Converts string literals to basic_string_view.
-template <typename Char, size_t N>
-FMT_CONSTEXPR basic_string_view<Char> compile_string_to_view(
- const Char (&s)[N]) {
- // Remove trailing null character if needed. Won't be present if this is used
- // with raw character array (i.e. not defined as a string).
- return {s,
- N - ((std::char_traits<Char>::to_int_type(s[N - 1]) == 0) ? 1 : 0)};
}
-// Converts string_view to basic_string_view.
-template <typename Char>
-FMT_CONSTEXPR basic_string_view<Char> compile_string_to_view(
- const std_string_view<Char>& s) {
- return {s.data(), s.size()};
-}
-
-#define FMT_STRING_IMPL(s, base) \
- [] { \
- /* Use a macro-like name to avoid shadowing warnings. */ \
- struct FMT_COMPILE_STRING : base { \
- using char_type = fmt::remove_cvref_t<decltype(s[0])>; \
- FMT_MAYBE_UNUSED FMT_CONSTEXPR \
- operator fmt::basic_string_view<char_type>() const { \
- return fmt::detail::compile_string_to_view<char_type>(s); \
- } \
- }; \
- return FMT_COMPILE_STRING(); \
+#define FMT_STRING_IMPL(s, base, explicit) \
+ [] { \
+ /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
+ /* Use a macro-like name to avoid shadowing warnings. */ \
+ struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \
+ using char_type = fmt::remove_cvref_t<decltype(s[0])>; \
+ FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \
+ operator fmt::basic_string_view<char_type>() const { \
+ return fmt::detail_exported::compile_string_to_view<char_type>(s); \
+ } \
+ }; \
+ return FMT_COMPILE_STRING(); \
}()
/**
std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
\endrst
*/
-#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string)
+#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string, )
-template <typename... Args, typename S,
- enable_if_t<(is_compile_string<S>::value), int>>
-void check_format_string(S format_str) {
- FMT_CONSTEXPR_DECL auto s = to_string_view(format_str);
- using checker = format_string_checker<typename S::char_type, error_handler,
- remove_cvref_t<Args>...>;
- FMT_CONSTEXPR_DECL bool invalid_format =
- (parse_format_string<true>(s, checker(s, {})), true);
- (void)invalid_format;
-}
+#if FMT_USE_USER_DEFINED_LITERALS
+template <typename Char> struct udl_formatter {
+ basic_string_view<Char> str;
-template <template <typename> class Handler, typename Context>
-void handle_dynamic_spec(int& value, arg_ref<typename Context::char_type> ref,
- Context& ctx) {
- switch (ref.kind) {
- case arg_id_kind::none:
- break;
- case arg_id_kind::index:
- value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.index),
- ctx.error_handler());
- break;
- case arg_id_kind::name:
- value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.name),
- ctx.error_handler());
- break;
+ template <typename... T>
+ auto operator()(T&&... args) const -> std::basic_string<Char> {
+ return vformat(str, fmt::make_args_checked<T...>(str, args...));
+ }
+};
+
+# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <typename T, typename Char, size_t N,
+ fmt::detail_exported::fixed_string<Char, N> Str>
+struct statically_named_arg : view {
+ static constexpr auto name = Str.data;
+
+ const T& value;
+ statically_named_arg(const T& v) : value(v) {}
+};
+
+template <typename T, typename Char, size_t N,
+ fmt::detail_exported::fixed_string<Char, N> Str>
+struct is_named_arg<statically_named_arg<T, Char, N, Str>> : std::true_type {};
+
+template <typename T, typename Char, size_t N,
+ fmt::detail_exported::fixed_string<Char, N> Str>
+struct is_statically_named_arg<statically_named_arg<T, Char, N, Str>>
+ : std::true_type {};
+
+template <typename Char, size_t N,
+ fmt::detail_exported::fixed_string<Char, N> Str>
+struct udl_arg {
+ template <typename T> auto operator=(T&& value) const {
+ return statically_named_arg<T, Char, N, Str>(std::forward<T>(value));
+ }
+};
+# else
+template <typename Char> struct udl_arg {
+ const Char* str;
+
+ template <typename T> auto operator=(T&& value) const -> named_arg<Char, T> {
+ return {str, std::forward<T>(value)};
}
+};
+# endif
+#endif // FMT_USE_USER_DEFINED_LITERALS
+
+template <typename Locale, typename Char>
+auto vformat(const Locale& loc, basic_string_view<Char> format_str,
+ basic_format_args<buffer_context<type_identity_t<Char>>> args)
+ -> std::basic_string<Char> {
+ basic_memory_buffer<Char> buffer;
+ detail::vformat_to(buffer, format_str, args, detail::locale_ref(loc));
+ return {buffer.data(), buffer.size()};
}
-using format_func = void (*)(detail::buffer<char>&, int, string_view);
+using format_func = void (*)(detail::buffer<char>&, int, const char*);
FMT_API void format_error_code(buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT;
FMT_API void report_error(format_func func, int error_code,
- string_view message) FMT_NOEXCEPT;
-} // namespace detail
+ const char* message) FMT_NOEXCEPT;
+FMT_END_DETAIL_NAMESPACE
-template <typename OutputIt, typename Char>
-using arg_formatter FMT_DEPRECATED_ALIAS =
- detail::arg_formatter<OutputIt, Char>;
+FMT_API auto vsystem_error(int error_code, string_view format_str,
+ format_args args) -> std::system_error;
/**
- An error returned by an operating system or a language runtime,
- for example a file opening error.
-*/
-FMT_CLASS_API
-class FMT_API system_error : public std::runtime_error {
- private:
- void init(int err_code, string_view format_str, format_args args);
-
- protected:
- int error_code_;
+ \rst
+ Constructs :class:`std::system_error` with a message formatted with
+ ``fmt::format(fmt, args...)``.
+ *error_code* is a system error code as given by ``errno``.
- system_error() : std::runtime_error(""), error_code_(0) {}
+ **Example**::
- public:
- /**
- \rst
- Constructs a :class:`fmt::system_error` object with a description
- formatted with `fmt::format_system_error`. *message* and additional
- arguments passed into the constructor are formatted similarly to
- `fmt::format`.
-
- **Example**::
-
- // This throws a system_error with the description
- // cannot open file 'madeup': No such file or directory
- // or similar (system message may vary).
- const char *filename = "madeup";
- std::FILE *file = std::fopen(filename, "r");
- if (!file)
- throw fmt::system_error(errno, "cannot open file '{}'", filename);
- \endrst
- */
- template <typename... Args>
- system_error(int error_code, string_view message, const Args&... args)
- : std::runtime_error("") {
- init(error_code, message, make_format_args(args...));
- }
- system_error(const system_error&) = default;
- system_error& operator=(const system_error&) = default;
- system_error(system_error&&) = default;
- system_error& operator=(system_error&&) = default;
- ~system_error() FMT_NOEXCEPT FMT_OVERRIDE;
-
- int error_code() const { return error_code_; }
-};
+ // This throws std::system_error with the description
+ // cannot open file 'madeup': No such file or directory
+ // or similar (system message may vary).
+ const char* filename = "madeup";
+ std::FILE* file = std::fopen(filename, "r");
+ if (!file)
+ throw fmt::system_error(errno, "cannot open file '{}'", filename);
+ \endrst
+*/
+template <typename... T>
+auto system_error(int error_code, format_string<T...> fmt, T&&... args)
+ -> std::system_error {
+ return vsystem_error(error_code, fmt, fmt::make_format_args(args...));
+}
/**
\rst
- Formats an error returned by an operating system or a language runtime,
- for example a file opening error, and writes it to *out* in the following
- form:
+ Formats an error message for an error returned by an operating system or a
+ language runtime, for example a file opening error, and writes it to *out*.
+ The format is the same as the one used by ``std::system_error(ec, message)``
+ where ``ec`` is ``std::error_code(error_code, std::generic_category()})``.
+ It is implementation-defined but normally looks like:
.. parsed-literal::
*<message>*: *<system-message>*
- where *<message>* is the passed message and *<system-message>* is
- the system message corresponding to the error code.
+ where *<message>* is the passed message and *<system-message>* is the system
+ message corresponding to the error code.
*error_code* is a system error code as given by ``errno``.
- If *error_code* is not a valid error code such as -1, the system message
- may look like "Unknown error -1" and is platform-dependent.
\endrst
*/
FMT_API void format_system_error(detail::buffer<char>& out, int error_code,
- string_view message) FMT_NOEXCEPT;
+ const char* message) FMT_NOEXCEPT;
// Reports a system error without throwing an exception.
// Can be used to report errors from destructors.
FMT_API void report_system_error(int error_code,
- string_view message) FMT_NOEXCEPT;
+ const char* message) FMT_NOEXCEPT;
/** Fast integer formatter. */
class format_int {
mutable char buffer_[buffer_size];
char* str_;
- template <typename UInt> char* format_unsigned(UInt value) {
+ template <typename UInt> auto format_unsigned(UInt value) -> char* {
auto n = static_cast<detail::uint32_or_64_or_128_t<UInt>>(value);
return detail::format_decimal(buffer_, n, buffer_size - 1).begin;
}
- template <typename Int> char* format_signed(Int value) {
+ template <typename Int> auto format_signed(Int value) -> char* {
auto abs_value = static_cast<detail::uint32_or_64_or_128_t<Int>>(value);
bool negative = value < 0;
if (negative) abs_value = 0 - abs_value;
: str_(format_unsigned(value)) {}
/** Returns the number of characters written to the output buffer. */
- size_t size() const {
+ auto size() const -> size_t {
return detail::to_unsigned(buffer_ - str_ + buffer_size - 1);
}
Returns a pointer to the output buffer content. No terminating null
character is appended.
*/
- const char* data() const { return str_; }
+ auto data() const -> const char* { return str_; }
/**
Returns a pointer to the output buffer content with terminating null
character appended.
*/
- const char* c_str() const {
+ auto c_str() const -> const char* {
buffer_[buffer_size - 1] = '\0';
return str_;
}
Returns the content of the output buffer as an ``std::string``.
\endrst
*/
- std::string str() const { return std::string(str_, size()); }
+ auto str() const -> std::string { return std::string(str_, size()); }
};
-// A formatter specialization for the core types corresponding to detail::type
-// constants.
template <typename T, typename Char>
-struct formatter<T, Char,
- enable_if_t<detail::type_constant<T, Char>::value !=
- detail::type::custom_type>> {
- FMT_CONSTEXPR formatter() = default;
-
- // Parses format specifiers stopping either at the end of the range or at the
- // terminating '}'.
- template <typename ParseContext>
- FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
- using handler_type = detail::dynamic_specs_handler<ParseContext>;
- auto type = detail::type_constant<T, Char>::value;
- detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
- type);
- auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
- auto eh = ctx.error_handler();
- switch (type) {
- case detail::type::none_type:
- FMT_ASSERT(false, "invalid argument type");
- break;
- case detail::type::int_type:
- case detail::type::uint_type:
- case detail::type::long_long_type:
- case detail::type::ulong_long_type:
- case detail::type::int128_type:
- case detail::type::uint128_type:
- case detail::type::bool_type:
- handle_int_type_spec(specs_.type,
- detail::int_type_checker<decltype(eh)>(eh));
- break;
- case detail::type::char_type:
- handle_char_specs(
- &specs_, detail::char_specs_checker<decltype(eh)>(specs_.type, eh));
- break;
- case detail::type::float_type:
- if (detail::const_check(FMT_USE_FLOAT))
- detail::parse_float_type_spec(specs_, eh);
- else
- FMT_ASSERT(false, "float support disabled");
- break;
- case detail::type::double_type:
- if (detail::const_check(FMT_USE_DOUBLE))
- detail::parse_float_type_spec(specs_, eh);
- else
- FMT_ASSERT(false, "double support disabled");
- break;
- case detail::type::long_double_type:
- if (detail::const_check(FMT_USE_LONG_DOUBLE))
- detail::parse_float_type_spec(specs_, eh);
- else
- FMT_ASSERT(false, "long double support disabled");
- break;
- case detail::type::cstring_type:
- detail::handle_cstring_type_spec(
- specs_.type, detail::cstring_type_checker<decltype(eh)>(eh));
- break;
- case detail::type::string_type:
- detail::check_string_type_spec(specs_.type, eh);
- break;
- case detail::type::pointer_type:
- detail::check_pointer_type_spec(specs_.type, eh);
- break;
- case detail::type::custom_type:
- // Custom format specifiers should be checked in parse functions of
- // formatter specializations.
- break;
- }
- return it;
- }
-
- template <typename FormatContext>
- auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
- detail::handle_dynamic_spec<detail::width_checker>(specs_.width,
- specs_.width_ref, ctx);
+template <typename FormatContext>
+FMT_CONSTEXPR FMT_INLINE auto
+formatter<T, Char,
+ enable_if_t<detail::type_constant<T, Char>::value !=
+ detail::type::custom_type>>::format(const T& val,
+ FormatContext& ctx)
+ const -> decltype(ctx.out()) {
+ if (specs_.width_ref.kind != detail::arg_id_kind::none ||
+ specs_.precision_ref.kind != detail::arg_id_kind::none) {
+ auto specs = specs_;
+ detail::handle_dynamic_spec<detail::width_checker>(specs.width,
+ specs.width_ref, ctx);
detail::handle_dynamic_spec<detail::precision_checker>(
- specs_.precision, specs_.precision_ref, ctx);
- using af = detail::arg_formatter<typename FormatContext::iterator,
- typename FormatContext::char_type>;
- return visit_format_arg(af(ctx, nullptr, &specs_),
- detail::make_arg<FormatContext>(val));
+ specs.precision, specs.precision_ref, ctx);
+ return detail::write<Char>(ctx.out(), val, specs, ctx.locale());
}
+ return detail::write<Char>(ctx.out(), val, specs_, ctx.locale());
+}
- private:
- detail::dynamic_format_specs<Char> specs_;
-};
-
-#define FMT_FORMAT_AS(Type, Base) \
- template <typename Char> \
- struct formatter<Type, Char> : formatter<Base, Char> { \
- template <typename FormatContext> \
- auto format(Type const& val, FormatContext& ctx) -> decltype(ctx.out()) { \
- return formatter<Base, Char>::format(val, ctx); \
- } \
+#define FMT_FORMAT_AS(Type, Base) \
+ template <typename Char> \
+ struct formatter<Type, Char> : formatter<Base, Char> { \
+ template <typename FormatContext> \
+ auto format(Type const& val, FormatContext& ctx) const \
+ -> decltype(ctx.out()) { \
+ return formatter<Base, Char>::format(static_cast<Base>(val), ctx); \
+ } \
}
FMT_FORMAT_AS(signed char, int);
FMT_FORMAT_AS(Char*, const Char*);
FMT_FORMAT_AS(std::basic_string<Char>, basic_string_view<Char>);
FMT_FORMAT_AS(std::nullptr_t, const void*);
+FMT_FORMAT_AS(detail::byte, unsigned char);
FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>);
template <typename Char>
struct formatter<void*, Char> : formatter<const void*, Char> {
template <typename FormatContext>
- auto format(void* val, FormatContext& ctx) -> decltype(ctx.out()) {
+ auto format(void* val, FormatContext& ctx) const -> decltype(ctx.out()) {
return formatter<const void*, Char>::format(val, ctx);
}
};
template <typename Char, size_t N>
struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {
template <typename FormatContext>
- auto format(const Char* val, FormatContext& ctx) -> decltype(ctx.out()) {
+ FMT_CONSTEXPR auto format(const Char* val, FormatContext& ctx) const
+ -> decltype(ctx.out()) {
return formatter<basic_string_view<Char>, Char>::format(val, ctx);
}
};
// };
template <typename Char = char> class dynamic_formatter {
private:
+ detail::dynamic_format_specs<Char> specs_;
+ const Char* format_str_;
+
struct null_handler : detail::error_handler {
void on_align(align_t) {}
- void on_plus() {}
- void on_minus() {}
- void on_space() {}
+ void on_sign(sign_t) {}
void on_hash() {}
};
+ template <typename Context> void handle_specs(Context& ctx) {
+ detail::handle_dynamic_spec<detail::width_checker>(specs_.width,
+ specs_.width_ref, ctx);
+ detail::handle_dynamic_spec<detail::precision_checker>(
+ specs_.precision, specs_.precision_ref, ctx);
+ }
+
public:
template <typename ParseContext>
- auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+ FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
format_str_ = ctx.begin();
// Checks are deferred to formatting time when the argument type is known.
detail::dynamic_specs_handler<ParseContext> handler(specs_, ctx);
- return parse_format_specs(ctx.begin(), ctx.end(), handler);
+ return detail::parse_format_specs(ctx.begin(), ctx.end(), handler);
}
template <typename T, typename FormatContext>
detail::specs_checker<null_handler> checker(
null_handler(), detail::mapped_type_constant<T, FormatContext>::value);
checker.on_align(specs_.align);
- switch (specs_.sign) {
- case sign::none:
- break;
- case sign::plus:
- checker.on_plus();
- break;
- case sign::minus:
- checker.on_minus();
- break;
- case sign::space:
- checker.on_space();
- break;
- }
+ if (specs_.sign != sign::none) checker.on_sign(specs_.sign);
if (specs_.alt) checker.on_hash();
if (specs_.precision >= 0) checker.end_precision();
- using af = detail::arg_formatter<typename FormatContext::iterator,
- typename FormatContext::char_type>;
- visit_format_arg(af(ctx, nullptr, &specs_),
- detail::make_arg<FormatContext>(val));
- return ctx.out();
- }
-
- private:
- template <typename Context> void handle_specs(Context& ctx) {
- detail::handle_dynamic_spec<detail::width_checker>(specs_.width,
- specs_.width_ref, ctx);
- detail::handle_dynamic_spec<detail::precision_checker>(
- specs_.precision, specs_.precision_ref, ctx);
+ return detail::write<Char>(ctx.out(), val, specs_, ctx.locale());
}
-
- detail::dynamic_format_specs<Char> specs_;
- const Char* format_str_;
};
-template <typename Char, typename ErrorHandler>
-FMT_CONSTEXPR void advance_to(
- basic_format_parse_context<Char, ErrorHandler>& ctx, const Char* p) {
- ctx.advance_to(ctx.begin() + (p - &*ctx.begin()));
-}
-
/**
\rst
Converts ``p`` to ``const void*`` for pointer formatting.
auto s = fmt::format("{}", fmt::ptr(p));
\endrst
*/
-template <typename T> inline const void* ptr(const T* p) { return p; }
-template <typename T> inline const void* ptr(const std::unique_ptr<T>& p) {
+template <typename T> auto ptr(T p) -> const void* {
+ static_assert(std::is_pointer<T>::value, "");
+ return detail::bit_cast<const void*>(p);
+}
+template <typename T> auto ptr(const std::unique_ptr<T>& p) -> const void* {
return p.get();
}
-template <typename T> inline const void* ptr(const std::shared_ptr<T>& p) {
+template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
}
};
-template <typename It, typename Sentinel, typename Char>
-struct arg_join : detail::view {
+// group_digits_view is not derived from view because it copies the argument.
+template <typename T> struct group_digits_view { T value; };
+
+/**
+ \rst
+ Returns a view that formats an integer value using ',' as a locale-independent
+ thousands separator.
+
+ **Example**::
+
+ fmt::print("{}", fmt::group_digits(12345));
+ // Output: "12,345"
+ \endrst
+ */
+template <typename T> auto group_digits(T value) -> group_digits_view<T> {
+ return {value};
+}
+
+template <typename T> struct formatter<group_digits_view<T>> : formatter<T> {
+ private:
+ detail::dynamic_format_specs<char> specs_;
+
+ public:
+ template <typename ParseContext>
+ FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+ using handler_type = detail::dynamic_specs_handler<ParseContext>;
+ detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
+ detail::type::int_type);
+ auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
+ detail::check_string_type_spec(specs_.type, ctx.error_handler());
+ return it;
+ }
+
+ template <typename FormatContext>
+ auto format(group_digits_view<T> t, FormatContext& ctx)
+ -> decltype(ctx.out()) {
+ detail::handle_dynamic_spec<detail::width_checker>(specs_.width,
+ specs_.width_ref, ctx);
+ detail::handle_dynamic_spec<detail::precision_checker>(
+ specs_.precision, specs_.precision_ref, ctx);
+ return detail::write_int_localized(
+ ctx.out(), static_cast<detail::uint64_or_128_t<T>>(t.value), 0, specs_,
+ detail::digit_grouping<char>({"\3", ','}));
+ }
+};
+
+template <typename It, typename Sentinel, typename Char = char>
+struct join_view : detail::view {
It begin;
Sentinel end;
basic_string_view<Char> sep;
- arg_join(It b, Sentinel e, basic_string_view<Char> s)
+ join_view(It b, Sentinel e, basic_string_view<Char> s)
: begin(b), end(e), sep(s) {}
};
template <typename It, typename Sentinel, typename Char>
-struct formatter<arg_join<It, Sentinel, Char>, Char>
- : formatter<typename std::iterator_traits<It>::value_type, Char> {
+using arg_join FMT_DEPRECATED_ALIAS = join_view<It, Sentinel, Char>;
+
+template <typename It, typename Sentinel, typename Char>
+struct formatter<join_view<It, Sentinel, Char>, Char> {
+ private:
+ using value_type =
+#ifdef __cpp_lib_ranges
+ std::iter_value_t<It>;
+#else
+ typename std::iterator_traits<It>::value_type;
+#endif
+ using context = buffer_context<Char>;
+ using mapper = detail::arg_mapper<context>;
+
+ template <typename T, FMT_ENABLE_IF(has_formatter<T, context>::value)>
+ static auto map(const T& value) -> const T& {
+ return value;
+ }
+ template <typename T, FMT_ENABLE_IF(!has_formatter<T, context>::value)>
+ static auto map(const T& value) -> decltype(mapper().map(value)) {
+ return mapper().map(value);
+ }
+
+ using formatter_type =
+ conditional_t<is_formattable<value_type, Char>::value,
+ formatter<remove_cvref_t<decltype(map(
+ std::declval<const value_type&>()))>,
+ Char>,
+ detail::fallback_formatter<value_type, Char>>;
+
+ formatter_type value_formatter_;
+
+ public:
+ template <typename ParseContext>
+ FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+ return value_formatter_.parse(ctx);
+ }
+
template <typename FormatContext>
- auto format(const arg_join<It, Sentinel, Char>& value, FormatContext& ctx)
+ auto format(const join_view<It, Sentinel, Char>& value, FormatContext& ctx)
-> decltype(ctx.out()) {
- using base = formatter<typename std::iterator_traits<It>::value_type, Char>;
auto it = value.begin;
auto out = ctx.out();
if (it != value.end) {
- out = base::format(*it++, ctx);
+ out = value_formatter_.format(map(*it), ctx);
+ ++it;
while (it != value.end) {
- out = std::copy(value.sep.begin(), value.sep.end(), out);
+ out = detail::copy_str<Char>(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out);
- out = base::format(*it++, ctx);
+ out = value_formatter_.format(map(*it), ctx);
+ ++it;
}
}
return out;
};
/**
- Returns an object that formats the iterator range `[begin, end)` with elements
+ Returns a view that formats the iterator range `[begin, end)` with elements
separated by `sep`.
*/
template <typename It, typename Sentinel>
-arg_join<It, Sentinel, char> join(It begin, Sentinel end, string_view sep) {
- return {begin, end, sep};
-}
-
-template <typename It, typename Sentinel>
-arg_join<It, Sentinel, wchar_t> join(It begin, Sentinel end, wstring_view sep) {
+auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
return {begin, end, sep};
}
/**
\rst
- Returns an object that formats `range` with elements separated by `sep`.
+ Returns a view that formats `range` with elements separated by `sep`.
**Example**::
\endrst
*/
template <typename Range>
-arg_join<detail::iterator_t<Range>, detail::sentinel_t<Range>, char> join(
- Range&& range, string_view sep) {
- return join(std::begin(range), std::end(range), sep);
-}
-
-template <typename Range>
-arg_join<detail::iterator_t<Range>, detail::sentinel_t<Range>, wchar_t> join(
- Range&& range, wstring_view sep) {
+auto join(Range&& range, string_view sep)
+ -> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>> {
return join(std::begin(range), std::end(range), sep);
}
\endrst
*/
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
-inline std::string to_string(const T& value) {
- std::string result;
+inline auto to_string(const T& value) -> std::string {
+ auto result = std::string();
detail::write<char>(std::back_inserter(result), value);
return result;
}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
-inline std::string to_string(T value) {
- // The buffer should be large enough to store the number including the sign or
- // "false" for bool.
+FMT_NODISCARD inline auto to_string(T value) -> std::string {
+ // The buffer should be large enough to store the number including the sign
+ // or "false" for bool.
constexpr int max_size = detail::digits10<T>() + 2;
char buffer[max_size > 5 ? static_cast<unsigned>(max_size) : 5];
char* begin = buffer;
return std::string(begin, detail::write<char>(begin, value));
}
-/**
- Converts *value* to ``std::wstring`` using the default format for type *T*.
- */
-template <typename T> inline std::wstring to_wstring(const T& value) {
- return format(L"{}", value);
-}
-
template <typename Char, size_t SIZE>
-std::basic_string<Char> to_string(const basic_memory_buffer<Char, SIZE>& buf) {
+FMT_NODISCARD auto to_string(const basic_memory_buffer<Char, SIZE>& buf)
+ -> std::basic_string<Char> {
auto size = buf.size();
detail::assume(size < std::basic_string<Char>().max_size());
return std::basic_string<Char>(buf.data(), size);
}
+FMT_BEGIN_DETAIL_NAMESPACE
+
template <typename Char>
-void detail::vformat_to(
- detail::buffer<Char>& buf, basic_string_view<Char> format_str,
- basic_format_args<buffer_context<type_identity_t<Char>>> args,
- detail::locale_ref loc) {
- using iterator = typename buffer_context<Char>::iterator;
+void vformat_to(
+ buffer<Char>& buf, basic_string_view<Char> fmt,
+ basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
+ locale_ref loc) {
+ // workaround for msvc bug regarding name-lookup in module
+ // link names into function scope
+ using detail::arg_formatter;
+ using detail::buffer_appender;
+ using detail::custom_formatter;
+ using detail::default_arg_formatter;
+ using detail::get_arg;
+ using detail::locale_ref;
+ using detail::parse_format_specs;
+ using detail::specs_checker;
+ using detail::specs_handler;
+ using detail::to_unsigned;
+ using detail::type;
+ using detail::write;
auto out = buffer_appender<Char>(buf);
- if (format_str.size() == 2 && equal2(format_str.data(), "{}")) {
+ if (fmt.size() == 2 && equal2(fmt.data(), "{}")) {
auto arg = args.get(0);
if (!arg) error_handler().on_error("argument not found");
- visit_format_arg(default_arg_formatter<iterator, Char>{out, args, loc},
- arg);
+ visit_format_arg(default_arg_formatter<Char>{out, args, loc}, arg);
return;
}
- format_handler<iterator, Char, buffer_context<Char>> h(out, format_str, args,
- loc);
- parse_format_string<false>(format_str, h);
-}
-
-#ifndef FMT_HEADER_ONLY
-extern template void detail::vformat_to(detail::buffer<char>&, string_view,
- basic_format_args<format_context>,
- detail::locale_ref);
-namespace detail {
-extern template FMT_API std::string grouping_impl<char>(locale_ref loc);
-extern template FMT_API std::string grouping_impl<wchar_t>(locale_ref loc);
-extern template FMT_API char thousands_sep_impl<char>(locale_ref loc);
-extern template FMT_API wchar_t thousands_sep_impl<wchar_t>(locale_ref loc);
-extern template FMT_API char decimal_point_impl(locale_ref loc);
-extern template FMT_API wchar_t decimal_point_impl(locale_ref loc);
-extern template int format_float<double>(double value, int precision,
- float_specs specs, buffer<char>& buf);
-extern template int format_float<long double>(long double value, int precision,
- float_specs specs,
- buffer<char>& buf);
-int snprintf_float(float value, int precision, float_specs specs,
- buffer<char>& buf) = delete;
-extern template int snprintf_float<double>(double value, int precision,
- float_specs specs,
- buffer<char>& buf);
-extern template int snprintf_float<long double>(long double value,
- int precision,
- float_specs specs,
- buffer<char>& buf);
-} // namespace detail
-#endif
-
-template <typename S, typename Char = char_t<S>,
- FMT_ENABLE_IF(detail::is_string<S>::value)>
-inline void vformat_to(
- detail::buffer<Char>& buf, const S& format_str,
- basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args) {
- return detail::vformat_to(buf, to_string_view(format_str), args);
-}
-
-template <typename S, typename... Args, size_t SIZE = inline_buffer_size,
- typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
-inline typename buffer_context<Char>::iterator format_to(
- basic_memory_buffer<Char, SIZE>& buf, const S& format_str, Args&&... args) {
- const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
- detail::vformat_to(buf, to_string_view(format_str), vargs);
- return detail::buffer_appender<Char>(buf);
-}
-
-template <typename OutputIt, typename Char = char>
-using format_context_t = basic_format_context<OutputIt, Char>;
+ struct format_handler : error_handler {
+ basic_format_parse_context<Char> parse_context;
+ buffer_context<Char> context;
-template <typename OutputIt, typename Char = char>
-using format_args_t = basic_format_args<format_context_t<OutputIt, Char>>;
+ format_handler(buffer_appender<Char> out, basic_string_view<Char> str,
+ basic_format_args<buffer_context<Char>> args, locale_ref loc)
+ : parse_context(str), context(out, args, loc) {}
-template <typename OutputIt, typename Char = typename OutputIt::value_type>
-using format_to_n_context FMT_DEPRECATED_ALIAS = buffer_context<Char>;
-
-template <typename OutputIt, typename Char = typename OutputIt::value_type>
-using format_to_n_args FMT_DEPRECATED_ALIAS =
- basic_format_args<buffer_context<Char>>;
+ void on_text(const Char* begin, const Char* end) {
+ auto text = basic_string_view<Char>(begin, to_unsigned(end - begin));
+ context.advance_to(write<Char>(context.out(), text));
+ }
-template <typename OutputIt, typename Char, typename... Args>
-FMT_DEPRECATED format_arg_store<buffer_context<Char>, Args...>
-make_format_to_n_args(const Args&... args) {
- return format_arg_store<buffer_context<Char>, Args...>(args...);
-}
+ FMT_CONSTEXPR auto on_arg_id() -> int {
+ return parse_context.next_arg_id();
+ }
+ FMT_CONSTEXPR auto on_arg_id(int id) -> int {
+ return parse_context.check_arg_id(id), id;
+ }
+ FMT_CONSTEXPR auto on_arg_id(basic_string_view<Char> id) -> int {
+ int arg_id = context.arg_id(id);
+ if (arg_id < 0) on_error("argument not found");
+ return arg_id;
+ }
-template <typename Char, enable_if_t<(!std::is_same<Char, char>::value), int>>
-std::basic_string<Char> detail::vformat(
- basic_string_view<Char> format_str,
- basic_format_args<buffer_context<type_identity_t<Char>>> args) {
- basic_memory_buffer<Char> buffer;
- detail::vformat_to(buffer, format_str, args);
- return to_string(buffer);
-}
+ FMT_INLINE void on_replacement_field(int id, const Char*) {
+ auto arg = get_arg(context, id);
+ context.advance_to(visit_format_arg(
+ default_arg_formatter<Char>{context.out(), context.args(),
+ context.locale()},
+ arg));
+ }
-template <typename Char, FMT_ENABLE_IF(std::is_same<Char, wchar_t>::value)>
-void vprint(std::FILE* f, basic_string_view<Char> format_str,
- wformat_args args) {
- wmemory_buffer buffer;
- detail::vformat_to(buffer, format_str, args);
- buffer.push_back(L'\0');
- if (std::fputws(buffer.data(), f) == -1)
- FMT_THROW(system_error(errno, "cannot write to file"));
+ auto on_format_specs(int id, const Char* begin, const Char* end)
+ -> const Char* {
+ auto arg = get_arg(context, id);
+ if (arg.type() == type::custom_type) {
+ parse_context.advance_to(parse_context.begin() +
+ (begin - &*parse_context.begin()));
+ visit_format_arg(custom_formatter<Char>{parse_context, context}, arg);
+ return parse_context.begin();
+ }
+ auto specs = basic_format_specs<Char>();
+ specs_checker<specs_handler<Char>> handler(
+ specs_handler<Char>(specs, parse_context, context), arg.type());
+ begin = parse_format_specs(begin, end, handler);
+ if (begin == end || *begin != '}')
+ on_error("missing '}' in format string");
+ auto f = arg_formatter<Char>{context.out(), specs, context.locale()};
+ context.advance_to(visit_format_arg(f, arg));
+ return begin;
+ }
+ };
+ detail::parse_format_string<false>(fmt, format_handler(out, fmt, args, loc));
}
-template <typename Char, FMT_ENABLE_IF(std::is_same<Char, wchar_t>::value)>
-void vprint(basic_string_view<Char> format_str, wformat_args args) {
- vprint(stdout, format_str, args);
-}
+#ifndef FMT_HEADER_ONLY
+extern template FMT_API auto thousands_sep_impl<char>(locale_ref)
+ -> thousands_sep_result<char>;
+extern template FMT_API auto thousands_sep_impl<wchar_t>(locale_ref)
+ -> thousands_sep_result<wchar_t>;
+extern template FMT_API auto decimal_point_impl(locale_ref) -> char;
+extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
+extern template auto format_float<double>(double value, int precision,
+ float_specs specs, buffer<char>& buf)
+ -> int;
+extern template auto format_float<long double>(long double value, int precision,
+ float_specs specs,
+ buffer<char>& buf) -> int;
+void snprintf_float(float, int, float_specs, buffer<char>&) = delete;
+extern template auto snprintf_float<double>(double value, int precision,
+ float_specs specs,
+ buffer<char>& buf) -> int;
+extern template auto snprintf_float<long double>(long double value,
+ int precision,
+ float_specs specs,
+ buffer<char>& buf) -> int;
+#endif // FMT_HEADER_ONLY
+
+FMT_END_DETAIL_NAMESPACE
#if FMT_USE_USER_DEFINED_LITERALS
-namespace detail {
-
-# if FMT_USE_UDL_TEMPLATE
-template <typename Char, Char... CHARS> class udl_formatter {
- public:
- template <typename... Args>
- std::basic_string<Char> operator()(Args&&... args) const {
- static FMT_CONSTEXPR_DECL Char s[] = {CHARS..., '\0'};
- return format(FMT_STRING(s), std::forward<Args>(args)...);
- }
-};
-# else
-template <typename Char> struct udl_formatter {
- basic_string_view<Char> str;
-
- template <typename... Args>
- std::basic_string<Char> operator()(Args&&... args) const {
- return format(str, std::forward<Args>(args)...);
- }
-};
-# endif // FMT_USE_UDL_TEMPLATE
-
-template <typename Char> struct udl_arg {
- const Char* str;
-
- template <typename T> named_arg<Char, T> operator=(T&& value) const {
- return {str, std::forward<T>(value)};
- }
-};
-} // namespace detail
-
inline namespace literals {
-# if FMT_USE_UDL_TEMPLATE
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wpedantic"
-# if FMT_CLANG_VERSION
-# pragma GCC diagnostic ignored "-Wgnu-string-literal-operator-template"
-# endif
-template <typename Char, Char... CHARS>
-FMT_CONSTEXPR detail::udl_formatter<Char, CHARS...> operator""_format() {
- return {};
-}
-# pragma GCC diagnostic pop
-# else
/**
\rst
- User-defined literal equivalent of :func:`fmt::format`.
+ User-defined literal equivalent of :func:`fmt::arg`.
**Example**::
using namespace fmt::literals;
- std::string message = "The answer is {}"_format(42);
+ fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23);
\endrst
*/
-FMT_CONSTEXPR detail::udl_formatter<char> operator"" _format(const char* s,
- size_t n) {
- return {{s, n}};
+# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <detail_exported::fixed_string Str>
+constexpr auto operator""_a()
+ -> detail::udl_arg<remove_cvref_t<decltype(Str.data[0])>,
+ sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str> {
+ return {};
+}
+# else
+constexpr auto operator"" _a(const char* s, size_t) -> detail::udl_arg<char> {
+ return {s};
}
-FMT_CONSTEXPR detail::udl_formatter<wchar_t> operator"" _format(
- const wchar_t* s, size_t n) {
+# endif
+
+// DEPRECATED!
+// User-defined literal equivalent of fmt::format.
+FMT_DEPRECATED constexpr auto operator"" _format(const char* s, size_t n)
+ -> detail::udl_formatter<char> {
return {{s, n}};
}
-# endif // FMT_USE_UDL_TEMPLATE
+} // namespace literals
+#endif // FMT_USE_USER_DEFINED_LITERALS
-/**
- \rst
- User-defined literal equivalent of :func:`fmt::arg`.
+template <typename Locale, FMT_ENABLE_IF(detail::is_locale<Locale>::value)>
+inline auto vformat(const Locale& loc, string_view fmt, format_args args)
+ -> std::string {
+ return detail::vformat(loc, fmt, args);
+}
- **Example**::
+template <typename Locale, typename... T,
+ FMT_ENABLE_IF(detail::is_locale<Locale>::value)>
+inline auto format(const Locale& loc, format_string<T...> fmt, T&&... args)
+ -> std::string {
+ return vformat(loc, string_view(fmt), fmt::make_format_args(args...));
+}
- using namespace fmt::literals;
- fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23);
- \endrst
- */
-FMT_CONSTEXPR detail::udl_arg<char> operator"" _a(const char* s, size_t) {
- return {s};
+template <typename... T, size_t SIZE, typename Allocator>
+FMT_DEPRECATED auto format_to(basic_memory_buffer<char, SIZE, Allocator>& buf,
+ format_string<T...> fmt, T&&... args)
+ -> appender {
+ detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...));
+ return appender(buf);
}
-FMT_CONSTEXPR detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
- return {s};
+
+template <typename OutputIt, typename Locale,
+ FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value&&
+ detail::is_locale<Locale>::value)>
+auto vformat_to(OutputIt out, const Locale& loc, string_view fmt,
+ format_args args) -> OutputIt {
+ using detail::get_buffer;
+ auto&& buf = get_buffer<char>(out);
+ detail::vformat_to(buf, fmt, args, detail::locale_ref(loc));
+ return detail::get_iterator(buf);
}
-} // namespace literals
-#endif // FMT_USE_USER_DEFINED_LITERALS
+
+template <typename OutputIt, typename Locale, typename... T,
+ FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value&&
+ detail::is_locale<Locale>::value)>
+FMT_INLINE auto format_to(OutputIt out, const Locale& loc,
+ format_string<T...> fmt, T&&... args) -> OutputIt {
+ return vformat_to(out, loc, fmt, fmt::make_format_args(args...));
+}
+
+FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE
+#ifdef FMT_DEPRECATED_INCLUDE_XCHAR
+# include "xchar.h"
+#endif
+
#ifdef FMT_HEADER_ONLY
# define FMT_FUNC inline
# include "format-inl.h"
FMT_BEGIN_NAMESPACE
namespace detail {
+// DEPRECATED!
+template <typename T = void> struct basic_data {
+ FMT_API static constexpr const char digits[100][2] = {
+ {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
+ {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
+ {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
+ {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
+ {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
+ {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
+ {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
+ {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
+ {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
+ {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
+ {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
+ {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
+ {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
+ {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
+ {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
+ {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
+ {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
+ FMT_API static constexpr const char hex_digits[] = "0123456789abcdef";
+ FMT_API static constexpr const char signs[4] = {0, '-', '+', ' '};
+ FMT_API static constexpr const char left_padding_shifts[5] = {31, 31, 0, 1,
+ 0};
+ FMT_API static constexpr const char right_padding_shifts[5] = {0, 31, 0, 1,
+ 0};
+ FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
+ 0x1000000u | ' '};
+};
+
+#ifdef FMT_SHARED
+// Required for -flto, -fivisibility=hidden and -shared to work
+extern template struct basic_data<void>;
+#endif
+
+#if __cplusplus < 201703L
+// DEPRECATED! These are here only for ABI compatiblity.
+template <typename T> constexpr const char basic_data<T>::digits[][2];
+template <typename T> constexpr const char basic_data<T>::hex_digits[];
+template <typename T> constexpr const char basic_data<T>::signs[];
+template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
+template <typename T>
+constexpr const char basic_data<T>::right_padding_shifts[];
+template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
+#endif
+
template <typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision,
T value) {
FMT_NOEXCEPT;
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
FMT_NOEXCEPT;
-
-// DEPRECATED! This function exists for ABI compatibility.
-template <typename Char>
-typename basic_format_context<std::back_insert_iterator<buffer<Char>>,
- Char>::iterator
-vformat_to(buffer<Char>& buf, basic_string_view<Char> format_str,
- basic_format_args<basic_format_context<
- std::back_insert_iterator<buffer<type_identity_t<Char>>>,
- type_identity_t<Char>>>
- args) {
- using iterator = std::back_insert_iterator<buffer<char>>;
- using context = basic_format_context<
- std::back_insert_iterator<buffer<type_identity_t<Char>>>,
- type_identity_t<Char>>;
- auto out = iterator(buf);
- format_handler<iterator, Char, context> h(out, format_str, args, {});
- parse_format_string<false>(format_str, h);
- return out;
-}
-template basic_format_context<std::back_insert_iterator<buffer<char>>,
- char>::iterator
-vformat_to(buffer<char>&, string_view,
- basic_format_args<basic_format_context<
- std::back_insert_iterator<buffer<type_identity_t<char>>>,
- type_identity_t<char>>>);
} // namespace detail
-template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
-
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
int (*instantiate_format_float)(double, int, detail::float_specs,
detail::buffer<char>&) = detail::format_float;
// Explicit instantiations for char.
-template FMT_API std::string detail::grouping_impl<char>(locale_ref);
-template FMT_API char detail::thousands_sep_impl(locale_ref);
+template FMT_API auto detail::thousands_sep_impl(locale_ref)
+ -> thousands_sep_result<char>;
template FMT_API char detail::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<char>::append(const char*, const char*);
+// DEPRECATED!
+// There is no correspondent extern template in format.h because of
+// incompatibility between clang and gcc (#2377).
template FMT_API void detail::vformat_to(
detail::buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
// Explicit instantiations for wchar_t.
-template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref);
-template FMT_API wchar_t detail::thousands_sep_impl(locale_ref);
+template FMT_API auto detail::thousands_sep_impl(locale_ref)
+ -> thousands_sep_result<wchar_t>;
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
const wchar_t*);
+
+template struct detail::basic_data<void>;
+
FMT_END_NAMESPACE
--- /dev/null
+#include "httplib.h"
+namespace httplib {
+
+/*
+ * Implementation that will be part of the .cc file if split into .h + .cc.
+ */
+
+namespace detail {
+
+bool is_hex(char c, int &v) {
+ if (0x20 <= c && isdigit(c)) {
+ v = c - '0';
+ return true;
+ } else if ('A' <= c && c <= 'F') {
+ v = c - 'A' + 10;
+ return true;
+ } else if ('a' <= c && c <= 'f') {
+ v = c - 'a' + 10;
+ return true;
+ }
+ return false;
+}
+
+bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
+ int &val) {
+ if (i >= s.size()) { return false; }
+
+ val = 0;
+ for (; cnt; i++, cnt--) {
+ if (!s[i]) { return false; }
+ int v = 0;
+ if (is_hex(s[i], v)) {
+ val = val * 16 + v;
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+std::string from_i_to_hex(size_t n) {
+ const char *charset = "0123456789abcdef";
+ std::string ret;
+ do {
+ ret = charset[n & 15] + ret;
+ n >>= 4;
+ } while (n > 0);
+ return ret;
+}
+
+size_t to_utf8(int code, char *buff) {
+ if (code < 0x0080) {
+ buff[0] = (code & 0x7F);
+ return 1;
+ } else if (code < 0x0800) {
+ buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
+ buff[1] = static_cast<char>(0x80 | (code & 0x3F));
+ return 2;
+ } else if (code < 0xD800) {
+ buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
+ buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+ buff[2] = static_cast<char>(0x80 | (code & 0x3F));
+ return 3;
+ } else if (code < 0xE000) { // D800 - DFFF is invalid...
+ return 0;
+ } else if (code < 0x10000) {
+ buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
+ buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+ buff[2] = static_cast<char>(0x80 | (code & 0x3F));
+ return 3;
+ } else if (code < 0x110000) {
+ buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
+ buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
+ buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+ buff[3] = static_cast<char>(0x80 | (code & 0x3F));
+ return 4;
+ }
+
+ // NOTREACHED
+ return 0;
+}
+
+// NOTE: This code came up with the following stackoverflow post:
+// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
+std::string base64_encode(const std::string &in) {
+ static const auto lookup =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ std::string out;
+ out.reserve(in.size());
+
+ int val = 0;
+ int valb = -6;
+
+ for (auto c : in) {
+ val = (val << 8) + static_cast<uint8_t>(c);
+ valb += 8;
+ while (valb >= 0) {
+ out.push_back(lookup[(val >> valb) & 0x3F]);
+ valb -= 6;
+ }
+ }
+
+ if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
+
+ while (out.size() % 4) {
+ out.push_back('=');
+ }
+
+ return out;
+}
+
+bool is_file(const std::string &path) {
+#ifdef _WIN32
+ return _access_s(path.c_str(), 0) == 0;
+#else
+ struct stat st;
+ return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
+#endif
+}
+
+bool is_dir(const std::string &path) {
+ struct stat st;
+ return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
+}
+
+bool is_valid_path(const std::string &path) {
+ size_t level = 0;
+ size_t i = 0;
+
+ // Skip slash
+ while (i < path.size() && path[i] == '/') {
+ i++;
+ }
+
+ while (i < path.size()) {
+ // Read component
+ auto beg = i;
+ while (i < path.size() && path[i] != '/') {
+ i++;
+ }
+
+ auto len = i - beg;
+ assert(len > 0);
+
+ if (!path.compare(beg, len, ".")) {
+ ;
+ } else if (!path.compare(beg, len, "..")) {
+ if (level == 0) { return false; }
+ level--;
+ } else {
+ level++;
+ }
+
+ // Skip slash
+ while (i < path.size() && path[i] == '/') {
+ i++;
+ }
+ }
+
+ return true;
+}
+
+std::string encode_query_param(const std::string &value) {
+ std::ostringstream escaped;
+ escaped.fill('0');
+ escaped << std::hex;
+
+ for (auto c : value) {
+ if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
+ c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
+ c == ')') {
+ escaped << c;
+ } else {
+ escaped << std::uppercase;
+ escaped << '%' << std::setw(2)
+ << static_cast<int>(static_cast<unsigned char>(c));
+ escaped << std::nouppercase;
+ }
+ }
+
+ return escaped.str();
+}
+
+std::string encode_url(const std::string &s) {
+ std::string result;
+ result.reserve(s.size());
+
+ for (size_t i = 0; s[i]; i++) {
+ switch (s[i]) {
+ case ' ': result += "%20"; break;
+ case '+': result += "%2B"; break;
+ case '\r': result += "%0D"; break;
+ case '\n': result += "%0A"; break;
+ case '\'': result += "%27"; break;
+ case ',': result += "%2C"; break;
+ // case ':': result += "%3A"; break; // ok? probably...
+ case ';': result += "%3B"; break;
+ default:
+ auto c = static_cast<uint8_t>(s[i]);
+ if (c >= 0x80) {
+ result += '%';
+ char hex[4];
+ auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
+ assert(len == 2);
+ result.append(hex, static_cast<size_t>(len));
+ } else {
+ result += s[i];
+ }
+ break;
+ }
+ }
+
+ return result;
+}
+
+std::string decode_url(const std::string &s,
+ bool convert_plus_to_space) {
+ std::string result;
+
+ for (size_t i = 0; i < s.size(); i++) {
+ if (s[i] == '%' && i + 1 < s.size()) {
+ if (s[i + 1] == 'u') {
+ int val = 0;
+ if (from_hex_to_i(s, i + 2, 4, val)) {
+ // 4 digits Unicode codes
+ char buff[4];
+ size_t len = to_utf8(val, buff);
+ if (len > 0) { result.append(buff, len); }
+ i += 5; // 'u0000'
+ } else {
+ result += s[i];
+ }
+ } else {
+ int val = 0;
+ if (from_hex_to_i(s, i + 1, 2, val)) {
+ // 2 digits hex codes
+ result += static_cast<char>(val);
+ i += 2; // '00'
+ } else {
+ result += s[i];
+ }
+ }
+ } else if (convert_plus_to_space && s[i] == '+') {
+ result += ' ';
+ } else {
+ result += s[i];
+ }
+ }
+
+ return result;
+}
+
+void read_file(const std::string &path, std::string &out) {
+ std::ifstream fs(path, std::ios_base::binary);
+ fs.seekg(0, std::ios_base::end);
+ auto size = fs.tellg();
+ fs.seekg(0);
+ out.resize(static_cast<size_t>(size));
+ fs.read(&out[0], static_cast<std::streamsize>(size));
+}
+
+std::string file_extension(const std::string &path) {
+ std::smatch m;
+ static auto re = std::regex("\\.([a-zA-Z0-9]+)$");
+ if (std::regex_search(path, m, re)) { return m[1].str(); }
+ return std::string();
+}
+
+bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
+
+std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
+ size_t right) {
+ while (b + left < e && is_space_or_tab(b[left])) {
+ left++;
+ }
+ while (right > 0 && is_space_or_tab(b[right - 1])) {
+ right--;
+ }
+ return std::make_pair(left, right);
+}
+
+std::string trim_copy(const std::string &s) {
+ auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
+ return s.substr(r.first, r.second - r.first);
+}
+
+void split(const char *b, const char *e, char d,
+ std::function<void(const char *, const char *)> fn) {
+ size_t i = 0;
+ size_t beg = 0;
+
+ while (e ? (b + i < e) : (b[i] != '\0')) {
+ if (b[i] == d) {
+ auto r = trim(b, e, beg, i);
+ if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
+ beg = i + 1;
+ }
+ i++;
+ }
+
+ if (i) {
+ auto r = trim(b, e, beg, i);
+ if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
+ }
+}
+
+stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer,
+ size_t fixed_buffer_size)
+ : strm_(strm), fixed_buffer_(fixed_buffer),
+ fixed_buffer_size_(fixed_buffer_size) {}
+
+const char *stream_line_reader::ptr() const {
+ if (glowable_buffer_.empty()) {
+ return fixed_buffer_;
+ } else {
+ return glowable_buffer_.data();
+ }
+}
+
+size_t stream_line_reader::size() const {
+ if (glowable_buffer_.empty()) {
+ return fixed_buffer_used_size_;
+ } else {
+ return glowable_buffer_.size();
+ }
+}
+
+bool stream_line_reader::end_with_crlf() const {
+ auto end = ptr() + size();
+ return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
+}
+
+bool stream_line_reader::getline() {
+ fixed_buffer_used_size_ = 0;
+ glowable_buffer_.clear();
+
+ for (size_t i = 0;; i++) {
+ char byte;
+ auto n = strm_.read(&byte, 1);
+
+ if (n < 0) {
+ return false;
+ } else if (n == 0) {
+ if (i == 0) {
+ return false;
+ } else {
+ break;
+ }
+ }
+
+ append(byte);
+
+ if (byte == '\n') { break; }
+ }
+
+ return true;
+}
+
+void stream_line_reader::append(char c) {
+ if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
+ fixed_buffer_[fixed_buffer_used_size_++] = c;
+ fixed_buffer_[fixed_buffer_used_size_] = '\0';
+ } else {
+ if (glowable_buffer_.empty()) {
+ assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
+ glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
+ }
+ glowable_buffer_ += c;
+ }
+}
+
+int close_socket(socket_t sock) {
+#ifdef _WIN32
+ return closesocket(sock);
+#else
+ return close(sock);
+#endif
+}
+
+template <typename T> ssize_t handle_EINTR(T fn) {
+ ssize_t res = false;
+ while (true) {
+ res = fn();
+ if (res < 0 && errno == EINTR) { continue; }
+ break;
+ }
+ return res;
+}
+
+ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
+ return handle_EINTR([&]() {
+ return recv(sock,
+#ifdef _WIN32
+ static_cast<char *>(ptr), static_cast<int>(size),
+#else
+ ptr, size,
+#endif
+ flags);
+ });
+}
+
+ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
+ int flags) {
+ return handle_EINTR([&]() {
+ return send(sock,
+#ifdef _WIN32
+ static_cast<const char *>(ptr), static_cast<int>(size),
+#else
+ ptr, size,
+#endif
+ flags);
+ });
+}
+
+ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
+#ifdef CPPHTTPLIB_USE_POLL
+ struct pollfd pfd_read;
+ pfd_read.fd = sock;
+ pfd_read.events = POLLIN;
+
+ auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
+
+ return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+#else
+#ifndef _WIN32
+ if (sock >= FD_SETSIZE) { return 1; }
+#endif
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(sock, &fds);
+
+ timeval tv;
+ tv.tv_sec = static_cast<long>(sec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+
+ return handle_EINTR([&]() {
+ return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
+ });
+#endif
+}
+
+ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
+#ifdef CPPHTTPLIB_USE_POLL
+ struct pollfd pfd_read;
+ pfd_read.fd = sock;
+ pfd_read.events = POLLOUT;
+
+ auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
+
+ return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+#else
+#ifndef _WIN32
+ if (sock >= FD_SETSIZE) { return 1; }
+#endif
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(sock, &fds);
+
+ timeval tv;
+ tv.tv_sec = static_cast<long>(sec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+
+ return handle_EINTR([&]() {
+ return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
+ });
+#endif
+}
+
+Error wait_until_socket_is_ready(socket_t sock, time_t sec,
+ time_t usec) {
+#ifdef CPPHTTPLIB_USE_POLL
+ struct pollfd pfd_read;
+ pfd_read.fd = sock;
+ pfd_read.events = POLLIN | POLLOUT;
+
+ auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
+
+ auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+
+ if (poll_res == 0) { return Error::ConnectionTimeout; }
+
+ if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
+ int error = 0;
+ socklen_t len = sizeof(error);
+ auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
+ reinterpret_cast<char *>(&error), &len);
+ auto successful = res >= 0 && !error;
+ return successful ? Error::Success : Error::Connection;
+ }
+
+ return Error::Connection;
+#else
+#ifndef _WIN32
+ if (sock >= FD_SETSIZE) { return Error::Connection; }
+#endif
+
+ fd_set fdsr;
+ FD_ZERO(&fdsr);
+ FD_SET(sock, &fdsr);
+
+ auto fdsw = fdsr;
+ auto fdse = fdsr;
+
+ timeval tv;
+ tv.tv_sec = static_cast<long>(sec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+
+ auto ret = handle_EINTR([&]() {
+ return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv);
+ });
+
+ if (ret == 0) { return Error::ConnectionTimeout; }
+
+ if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
+ int error = 0;
+ socklen_t len = sizeof(error);
+ auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
+ reinterpret_cast<char *>(&error), &len);
+ auto successful = res >= 0 && !error;
+ return successful ? Error::Success : Error::Connection;
+ }
+ return Error::Connection;
+#endif
+}
+
+bool is_socket_alive(socket_t sock) {
+ const auto val = detail::select_read(sock, 0, 0);
+ if (val == 0) {
+ return true;
+ } else if (val < 0 && errno == EBADF) {
+ return false;
+ }
+ char buf[1];
+ return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
+}
+
+class SocketStream : public Stream {
+public:
+ SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec);
+ ~SocketStream() override;
+
+ bool is_readable() const override;
+ bool is_writable() const override;
+ ssize_t read(char *ptr, size_t size) override;
+ ssize_t write(const char *ptr, size_t size) override;
+ void get_remote_ip_and_port(std::string &ip, int &port) const override;
+ socket_t socket() const override;
+
+private:
+ socket_t sock_;
+ time_t read_timeout_sec_;
+ time_t read_timeout_usec_;
+ time_t write_timeout_sec_;
+ time_t write_timeout_usec_;
+
+ std::vector<char> read_buff_;
+ size_t read_buff_off_ = 0;
+ size_t read_buff_content_size_ = 0;
+
+ static const size_t read_buff_size_ = 1024 * 4;
+};
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+class SSLSocketStream : public Stream {
+public:
+ SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec);
+ ~SSLSocketStream() override;
+
+ bool is_readable() const override;
+ bool is_writable() const override;
+ ssize_t read(char *ptr, size_t size) override;
+ ssize_t write(const char *ptr, size_t size) override;
+ void get_remote_ip_and_port(std::string &ip, int &port) const override;
+ socket_t socket() const override;
+
+private:
+ socket_t sock_;
+ SSL *ssl_;
+ time_t read_timeout_sec_;
+ time_t read_timeout_usec_;
+ time_t write_timeout_sec_;
+ time_t write_timeout_usec_;
+};
+#endif
+
+bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) {
+ using namespace std::chrono;
+ auto start = steady_clock::now();
+ while (true) {
+ auto val = select_read(sock, 0, 10000);
+ if (val < 0) {
+ return false;
+ } else if (val == 0) {
+ auto current = steady_clock::now();
+ auto duration = duration_cast<milliseconds>(current - start);
+ auto timeout = keep_alive_timeout_sec * 1000;
+ if (duration.count() > timeout) { return false; }
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ } else {
+ return true;
+ }
+ }
+}
+
+template <typename T>
+bool
+process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
+ size_t keep_alive_max_count,
+ time_t keep_alive_timeout_sec, T callback) {
+ assert(keep_alive_max_count > 0);
+ auto ret = false;
+ auto count = keep_alive_max_count;
+ while (svr_sock != INVALID_SOCKET && count > 0 &&
+ keep_alive(sock, keep_alive_timeout_sec)) {
+ auto close_connection = count == 1;
+ auto connection_closed = false;
+ ret = callback(close_connection, connection_closed);
+ if (!ret || connection_closed) { break; }
+ count--;
+ }
+ return ret;
+}
+
+template <typename T>
+bool
+process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
+ size_t keep_alive_max_count,
+ time_t keep_alive_timeout_sec, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, T callback) {
+ return process_server_socket_core(
+ svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
+ [&](bool close_connection, bool &connection_closed) {
+ SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm, close_connection, connection_closed);
+ });
+}
+
+bool process_client_socket(socket_t sock, time_t read_timeout_sec,
+ time_t read_timeout_usec,
+ time_t write_timeout_sec,
+ time_t write_timeout_usec,
+ std::function<bool(Stream &)> callback) {
+ SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm);
+}
+
+int shutdown_socket(socket_t sock) {
+#ifdef _WIN32
+ return shutdown(sock, SD_BOTH);
+#else
+ return shutdown(sock, SHUT_RDWR);
+#endif
+}
+
+template <typename BindOrConnect>
+socket_t create_socket(const std::string &host, const std::string &ip, int port,
+ int address_family, int socket_flags, bool tcp_nodelay,
+ SocketOptions socket_options,
+ BindOrConnect bind_or_connect) {
+ // Get address info
+ const char *node = nullptr;
+ struct addrinfo hints;
+ struct addrinfo *result;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+
+ if (!ip.empty()) {
+ node = ip.c_str();
+ // Ask getaddrinfo to convert IP in c-string to address
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_NUMERICHOST;
+ } else {
+ if (!host.empty()) { node = host.c_str(); }
+ hints.ai_family = address_family;
+ hints.ai_flags = socket_flags;
+ }
+
+#ifndef _WIN32
+ if (hints.ai_family == AF_UNIX) {
+ const auto addrlen = host.length();
+ if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET;
+
+ auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
+ if (sock != INVALID_SOCKET) {
+ sockaddr_un addr;
+ addr.sun_family = AF_UNIX;
+ std::copy(host.begin(), host.end(), addr.sun_path);
+
+ hints.ai_addr = reinterpret_cast<sockaddr*>(&addr);
+ hints.ai_addrlen = static_cast<socklen_t>(
+ sizeof(addr) - sizeof(addr.sun_path) + addrlen);
+
+ if (!bind_or_connect(sock, hints)) {
+ close_socket(sock);
+ sock = INVALID_SOCKET;
+ }
+ }
+ return sock;
+ }
+#endif
+
+ auto service = std::to_string(port);
+
+ if (getaddrinfo(node, service.c_str(), &hints, &result)) {
+#if defined __linux__ && !defined __ANDROID__
+ res_init();
+#endif
+ return INVALID_SOCKET;
+ }
+
+ for (auto rp = result; rp; rp = rp->ai_next) {
+ // Create a socket
+#ifdef _WIN32
+ auto sock =
+ WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
+ WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
+ /**
+ * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
+ * and above the socket creation fails on older Windows Systems.
+ *
+ * Let's try to create a socket the old way in this case.
+ *
+ * Reference:
+ * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
+ *
+ * WSA_FLAG_NO_HANDLE_INHERIT:
+ * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with
+ * SP1, and later
+ *
+ */
+ if (sock == INVALID_SOCKET) {
+ sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ }
+#else
+ auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+#endif
+ if (sock == INVALID_SOCKET) { continue; }
+
+#ifndef _WIN32
+ if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; }
+#endif
+
+ if (tcp_nodelay) {
+ int yes = 1;
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&yes),
+ sizeof(yes));
+ }
+
+ if (socket_options) { socket_options(sock); }
+
+ if (rp->ai_family == AF_INET6) {
+ int no = 0;
+ setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no),
+ sizeof(no));
+ }
+
+ // bind or connect
+ if (bind_or_connect(sock, *rp)) {
+ freeaddrinfo(result);
+ return sock;
+ }
+
+ close_socket(sock);
+ }
+
+ freeaddrinfo(result);
+ return INVALID_SOCKET;
+}
+
+void set_nonblocking(socket_t sock, bool nonblocking) {
+#ifdef _WIN32
+ auto flags = nonblocking ? 1UL : 0UL;
+ ioctlsocket(sock, FIONBIO, &flags);
+#else
+ auto flags = fcntl(sock, F_GETFL, 0);
+ fcntl(sock, F_SETFL,
+ nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
+#endif
+}
+
+bool is_connection_error() {
+#ifdef _WIN32
+ return WSAGetLastError() != WSAEWOULDBLOCK;
+#else
+ return errno != EINPROGRESS;
+#endif
+}
+
+bool bind_ip_address(socket_t sock, const std::string &host) {
+ struct addrinfo hints;
+ struct addrinfo *result;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+
+ if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; }
+
+ auto ret = false;
+ for (auto rp = result; rp; rp = rp->ai_next) {
+ const auto &ai = *rp;
+ if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
+ ret = true;
+ break;
+ }
+ }
+
+ freeaddrinfo(result);
+ return ret;
+}
+
+#if !defined _WIN32 && !defined ANDROID
+#define USE_IF2IP
+#endif
+
+#ifdef USE_IF2IP
+std::string if2ip(int address_family, const std::string &ifn) {
+ struct ifaddrs *ifap;
+ getifaddrs(&ifap);
+ std::string addr_candidate;
+ for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr && ifn == ifa->ifa_name &&
+ (AF_UNSPEC == address_family ||
+ ifa->ifa_addr->sa_family == address_family)) {
+ if (ifa->ifa_addr->sa_family == AF_INET) {
+ auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
+ char buf[INET_ADDRSTRLEN];
+ if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
+ freeifaddrs(ifap);
+ return std::string(buf, INET_ADDRSTRLEN);
+ }
+ } else if (ifa->ifa_addr->sa_family == AF_INET6) {
+ auto sa = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
+ if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
+ char buf[INET6_ADDRSTRLEN] = {};
+ if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) {
+ // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL
+ auto s6_addr_head = sa->sin6_addr.s6_addr[0];
+ if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
+ addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
+ } else {
+ freeifaddrs(ifap);
+ return std::string(buf, INET6_ADDRSTRLEN);
+ }
+ }
+ }
+ }
+ }
+ }
+ freeifaddrs(ifap);
+ return addr_candidate;
+}
+#endif
+
+socket_t create_client_socket(
+ const std::string &host, const std::string &ip, int port,
+ int address_family, bool tcp_nodelay, SocketOptions socket_options,
+ time_t connection_timeout_sec, time_t connection_timeout_usec,
+ time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, const std::string &intf, Error &error) {
+ auto sock = create_socket(
+ host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options),
+ [&](socket_t sock2, struct addrinfo &ai) -> bool {
+ if (!intf.empty()) {
+#ifdef USE_IF2IP
+ auto ip_from_if = if2ip(address_family, intf);
+ if (ip_from_if.empty()) { ip_from_if = intf; }
+ if (!bind_ip_address(sock2, ip_from_if.c_str())) {
+ error = Error::BindIPAddress;
+ return false;
+ }
+#endif
+ }
+
+ set_nonblocking(sock2, true);
+
+ auto ret =
+ ::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
+
+ if (ret < 0) {
+ if (is_connection_error()) {
+ error = Error::Connection;
+ return false;
+ }
+ error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
+ connection_timeout_usec);
+ if (error != Error::Success) { return false; }
+ }
+
+ set_nonblocking(sock2, false);
+
+ {
+#ifdef _WIN32
+ auto timeout = static_cast<uint32_t>(read_timeout_sec * 1000 +
+ read_timeout_usec / 1000);
+ setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
+ sizeof(timeout));
+#else
+ timeval tv;
+ tv.tv_sec = static_cast<long>(read_timeout_sec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec);
+ setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
+#endif
+ }
+ {
+
+#ifdef _WIN32
+ auto timeout = static_cast<uint32_t>(write_timeout_sec * 1000 +
+ write_timeout_usec / 1000);
+ setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
+ sizeof(timeout));
+#else
+ timeval tv;
+ tv.tv_sec = static_cast<long>(write_timeout_sec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec);
+ setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
+#endif
+ }
+
+ error = Error::Success;
+ return true;
+ });
+
+ if (sock != INVALID_SOCKET) {
+ error = Error::Success;
+ } else {
+ if (error == Error::Success) { error = Error::Connection; }
+ }
+
+ return sock;
+}
+
+bool get_remote_ip_and_port(const struct sockaddr_storage &addr,
+ socklen_t addr_len, std::string &ip,
+ int &port) {
+ if (addr.ss_family == AF_INET) {
+ port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
+ } else if (addr.ss_family == AF_INET6) {
+ port =
+ ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
+ } else {
+ return false;
+ }
+
+ std::array<char, NI_MAXHOST> ipstr{};
+ if (getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
+ ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
+ 0, NI_NUMERICHOST)) {
+ return false;
+ }
+
+ ip = ipstr.data();
+ return true;
+}
+
+void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof(addr);
+
+ if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
+ &addr_len)) {
+ get_remote_ip_and_port(addr, addr_len, ip, port);
+ }
+}
+
+constexpr unsigned int str2tag_core(const char *s, size_t l,
+ unsigned int h) {
+ return (l == 0) ? h
+ : str2tag_core(s + 1, l - 1,
+ (h * 33) ^ static_cast<unsigned char>(*s));
+}
+
+unsigned int str2tag(const std::string &s) {
+ return str2tag_core(s.data(), s.size(), 0);
+}
+
+namespace udl {
+
+constexpr unsigned int operator"" _t(const char *s, size_t l) {
+ return str2tag_core(s, l, 0);
+}
+
+} // namespace udl
+
+const char *
+find_content_type(const std::string &path,
+ const std::map<std::string, std::string> &user_data) {
+ auto ext = file_extension(path);
+
+ auto it = user_data.find(ext);
+ if (it != user_data.end()) { return it->second.c_str(); }
+
+ using udl::operator""_t;
+
+ switch (str2tag(ext)) {
+ default: return nullptr;
+ case "css"_t: return "text/css";
+ case "csv"_t: return "text/csv";
+ case "htm"_t:
+ case "html"_t: return "text/html";
+ case "js"_t:
+ case "mjs"_t: return "text/javascript";
+ case "txt"_t: return "text/plain";
+ case "vtt"_t: return "text/vtt";
+
+ case "apng"_t: return "image/apng";
+ case "avif"_t: return "image/avif";
+ case "bmp"_t: return "image/bmp";
+ case "gif"_t: return "image/gif";
+ case "png"_t: return "image/png";
+ case "svg"_t: return "image/svg+xml";
+ case "webp"_t: return "image/webp";
+ case "ico"_t: return "image/x-icon";
+ case "tif"_t: return "image/tiff";
+ case "tiff"_t: return "image/tiff";
+ case "jpg"_t:
+ case "jpeg"_t: return "image/jpeg";
+
+ case "mp4"_t: return "video/mp4";
+ case "mpeg"_t: return "video/mpeg";
+ case "webm"_t: return "video/webm";
+
+ case "mp3"_t: return "audio/mp3";
+ case "mpga"_t: return "audio/mpeg";
+ case "weba"_t: return "audio/webm";
+ case "wav"_t: return "audio/wave";
+
+ case "otf"_t: return "font/otf";
+ case "ttf"_t: return "font/ttf";
+ case "woff"_t: return "font/woff";
+ case "woff2"_t: return "font/woff2";
+
+ case "7z"_t: return "application/x-7z-compressed";
+ case "atom"_t: return "application/atom+xml";
+ case "pdf"_t: return "application/pdf";
+ case "json"_t: return "application/json";
+ case "rss"_t: return "application/rss+xml";
+ case "tar"_t: return "application/x-tar";
+ case "xht"_t:
+ case "xhtml"_t: return "application/xhtml+xml";
+ case "xslt"_t: return "application/xslt+xml";
+ case "xml"_t: return "application/xml";
+ case "gz"_t: return "application/gzip";
+ case "zip"_t: return "application/zip";
+ case "wasm"_t: return "application/wasm";
+ }
+}
+
+const char *status_message(int status) {
+ switch (status) {
+ case 100: return "Continue";
+ case 101: return "Switching Protocol";
+ case 102: return "Processing";
+ case 103: return "Early Hints";
+ case 200: return "OK";
+ case 201: return "Created";
+ case 202: return "Accepted";
+ case 203: return "Non-Authoritative Information";
+ case 204: return "No Content";
+ case 205: return "Reset Content";
+ case 206: return "Partial Content";
+ case 207: return "Multi-Status";
+ case 208: return "Already Reported";
+ case 226: return "IM Used";
+ case 300: return "Multiple Choice";
+ case 301: return "Moved Permanently";
+ case 302: return "Found";
+ case 303: return "See Other";
+ case 304: return "Not Modified";
+ case 305: return "Use Proxy";
+ case 306: return "unused";
+ case 307: return "Temporary Redirect";
+ case 308: return "Permanent Redirect";
+ case 400: return "Bad Request";
+ case 401: return "Unauthorized";
+ case 402: return "Payment Required";
+ case 403: return "Forbidden";
+ case 404: return "Not Found";
+ case 405: return "Method Not Allowed";
+ case 406: return "Not Acceptable";
+ case 407: return "Proxy Authentication Required";
+ case 408: return "Request Timeout";
+ case 409: return "Conflict";
+ case 410: return "Gone";
+ case 411: return "Length Required";
+ case 412: return "Precondition Failed";
+ case 413: return "Payload Too Large";
+ case 414: return "URI Too Long";
+ case 415: return "Unsupported Media Type";
+ case 416: return "Range Not Satisfiable";
+ case 417: return "Expectation Failed";
+ case 418: return "I'm a teapot";
+ case 421: return "Misdirected Request";
+ case 422: return "Unprocessable Entity";
+ case 423: return "Locked";
+ case 424: return "Failed Dependency";
+ case 425: return "Too Early";
+ case 426: return "Upgrade Required";
+ case 428: return "Precondition Required";
+ case 429: return "Too Many Requests";
+ case 431: return "Request Header Fields Too Large";
+ case 451: return "Unavailable For Legal Reasons";
+ case 501: return "Not Implemented";
+ case 502: return "Bad Gateway";
+ case 503: return "Service Unavailable";
+ case 504: return "Gateway Timeout";
+ case 505: return "HTTP Version Not Supported";
+ case 506: return "Variant Also Negotiates";
+ case 507: return "Insufficient Storage";
+ case 508: return "Loop Detected";
+ case 510: return "Not Extended";
+ case 511: return "Network Authentication Required";
+
+ default:
+ case 500: return "Internal Server Error";
+ }
+}
+
+bool can_compress_content_type(const std::string &content_type) {
+ using udl::operator""_t;
+
+ auto tag = str2tag(content_type);
+
+ switch (tag) {
+ case "image/svg+xml"_t:
+ case "application/javascript"_t:
+ case "application/json"_t:
+ case "application/xml"_t:
+ case "application/protobuf"_t:
+ case "application/xhtml+xml"_t: return true;
+
+ default:
+ return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t;
+ }
+}
+
+EncodingType encoding_type(const Request &req, const Response &res) {
+ auto ret =
+ detail::can_compress_content_type(res.get_header_value("Content-Type"));
+ if (!ret) { return EncodingType::None; }
+
+ const auto &s = req.get_header_value("Accept-Encoding");
+ (void)(s);
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ // TODO: 'Accept-Encoding' has br, not br;q=0
+ ret = s.find("br") != std::string::npos;
+ if (ret) { return EncodingType::Brotli; }
+#endif
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
+ ret = s.find("gzip") != std::string::npos;
+ if (ret) { return EncodingType::Gzip; }
+#endif
+
+ return EncodingType::None;
+}
+
+bool nocompressor::compress(const char *data, size_t data_length,
+ bool /*last*/, Callback callback) {
+ if (!data_length) { return true; }
+ return callback(data, data_length);
+}
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+gzip_compressor::gzip_compressor() {
+ std::memset(&strm_, 0, sizeof(strm_));
+ strm_.zalloc = Z_NULL;
+ strm_.zfree = Z_NULL;
+ strm_.opaque = Z_NULL;
+
+ is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
+ Z_DEFAULT_STRATEGY) == Z_OK;
+}
+
+gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); }
+
+bool gzip_compressor::compress(const char *data, size_t data_length,
+ bool last, Callback callback) {
+ assert(is_valid_);
+
+ do {
+ constexpr size_t max_avail_in =
+ (std::numeric_limits<decltype(strm_.avail_in)>::max)();
+
+ strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
+ (std::min)(data_length, max_avail_in));
+ strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
+
+ data_length -= strm_.avail_in;
+ data += strm_.avail_in;
+
+ auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
+ int ret = Z_OK;
+
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+ do {
+ strm_.avail_out = static_cast<uInt>(buff.size());
+ strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
+
+ ret = deflate(&strm_, flush);
+ if (ret == Z_STREAM_ERROR) { return false; }
+
+ if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
+ return false;
+ }
+ } while (strm_.avail_out == 0);
+
+ assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
+ (flush == Z_NO_FLUSH && ret == Z_OK));
+ assert(strm_.avail_in == 0);
+ } while (data_length > 0);
+
+ return true;
+}
+
+gzip_decompressor::gzip_decompressor() {
+ std::memset(&strm_, 0, sizeof(strm_));
+ strm_.zalloc = Z_NULL;
+ strm_.zfree = Z_NULL;
+ strm_.opaque = Z_NULL;
+
+ // 15 is the value of wbits, which should be at the maximum possible value
+ // to ensure that any gzip stream can be decoded. The offset of 32 specifies
+ // that the stream type should be automatically detected either gzip or
+ // deflate.
+ is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
+}
+
+gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); }
+
+bool gzip_decompressor::is_valid() const { return is_valid_; }
+
+bool gzip_decompressor::decompress(const char *data, size_t data_length,
+ Callback callback) {
+ assert(is_valid_);
+
+ int ret = Z_OK;
+
+ do {
+ constexpr size_t max_avail_in =
+ (std::numeric_limits<decltype(strm_.avail_in)>::max)();
+
+ strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
+ (std::min)(data_length, max_avail_in));
+ strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
+
+ data_length -= strm_.avail_in;
+ data += strm_.avail_in;
+
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+ while (strm_.avail_in > 0) {
+ strm_.avail_out = static_cast<uInt>(buff.size());
+ strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
+
+ auto prev_avail_in = strm_.avail_in;
+
+ ret = inflate(&strm_, Z_NO_FLUSH);
+
+ if (prev_avail_in - strm_.avail_in == 0) { return false; }
+
+ assert(ret != Z_STREAM_ERROR);
+ switch (ret) {
+ case Z_NEED_DICT:
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR: inflateEnd(&strm_); return false;
+ }
+
+ if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
+ return false;
+ }
+ }
+
+ if (ret != Z_OK && ret != Z_STREAM_END) return false;
+
+ } while (data_length > 0);
+
+ return true;
+}
+#endif
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+brotli_compressor::brotli_compressor() {
+ state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
+}
+
+brotli_compressor::~brotli_compressor() {
+ BrotliEncoderDestroyInstance(state_);
+}
+
+bool brotli_compressor::compress(const char *data, size_t data_length,
+ bool last, Callback callback) {
+ std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+
+ auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
+ auto available_in = data_length;
+ auto next_in = reinterpret_cast<const uint8_t *>(data);
+
+ for (;;) {
+ if (last) {
+ if (BrotliEncoderIsFinished(state_)) { break; }
+ } else {
+ if (!available_in) { break; }
+ }
+
+ auto available_out = buff.size();
+ auto next_out = buff.data();
+
+ if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in,
+ &available_out, &next_out, nullptr)) {
+ return false;
+ }
+
+ auto output_bytes = buff.size() - available_out;
+ if (output_bytes) {
+ callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
+ }
+ }
+
+ return true;
+}
+
+brotli_decompressor::brotli_decompressor() {
+ decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
+ decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
+ : BROTLI_DECODER_RESULT_ERROR;
+}
+
+brotli_decompressor::~brotli_decompressor() {
+ if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
+}
+
+bool brotli_decompressor::is_valid() const { return decoder_s; }
+
+bool brotli_decompressor::decompress(const char *data,
+ size_t data_length,
+ Callback callback) {
+ if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
+ decoder_r == BROTLI_DECODER_RESULT_ERROR) {
+ return 0;
+ }
+
+ const uint8_t *next_in = (const uint8_t *)data;
+ size_t avail_in = data_length;
+ size_t total_out;
+
+ decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
+
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+ while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
+ char *next_out = buff.data();
+ size_t avail_out = buff.size();
+
+ decoder_r = BrotliDecoderDecompressStream(
+ decoder_s, &avail_in, &next_in, &avail_out,
+ reinterpret_cast<uint8_t **>(&next_out), &total_out);
+
+ if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
+
+ if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
+ }
+
+ return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
+ decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
+}
+#endif
+
+bool has_header(const Headers &headers, const std::string &key) {
+ return headers.find(key) != headers.end();
+}
+
+const char *get_header_value(const Headers &headers,
+ const std::string &key, size_t id,
+ const char *def) {
+ auto rng = headers.equal_range(key);
+ auto it = rng.first;
+ std::advance(it, static_cast<ssize_t>(id));
+ if (it != rng.second) { return it->second.c_str(); }
+ return def;
+}
+
+template <typename T>
+bool parse_header(const char *beg, const char *end, T fn) {
+ // Skip trailing spaces and tabs.
+ while (beg < end && is_space_or_tab(end[-1])) {
+ end--;
+ }
+
+ auto p = beg;
+ while (p < end && *p != ':') {
+ p++;
+ }
+
+ if (p == end) { return false; }
+
+ auto key_end = p;
+
+ if (*p++ != ':') { return false; }
+
+ while (p < end && is_space_or_tab(*p)) {
+ p++;
+ }
+
+ if (p < end) {
+ fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
+ return true;
+ }
+
+ return false;
+}
+
+bool read_headers(Stream &strm, Headers &headers) {
+ const auto bufsiz = 2048;
+ char buf[bufsiz];
+ stream_line_reader line_reader(strm, buf, bufsiz);
+
+ for (;;) {
+ if (!line_reader.getline()) { return false; }
+
+ // Check if the line ends with CRLF.
+ auto line_terminator_len = 2;
+ if (line_reader.end_with_crlf()) {
+ // Blank line indicates end of headers.
+ if (line_reader.size() == 2) { break; }
+#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
+ } else {
+ // Blank line indicates end of headers.
+ if (line_reader.size() == 1) { break; }
+ line_terminator_len = 1;
+ }
+#else
+ } else {
+ continue; // Skip invalid line.
+ }
+#endif
+
+ if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
+
+ // Exclude line terminator
+ auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
+
+ parse_header(line_reader.ptr(), end,
+ [&](std::string &&key, std::string &&val) {
+ headers.emplace(std::move(key), std::move(val));
+ });
+ }
+
+ return true;
+}
+
+bool read_content_with_length(Stream &strm, uint64_t len,
+ Progress progress,
+ ContentReceiverWithProgress out) {
+ char buf[CPPHTTPLIB_RECV_BUFSIZ];
+
+ uint64_t r = 0;
+ while (r < len) {
+ auto read_len = static_cast<size_t>(len - r);
+ auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
+ if (n <= 0) { return false; }
+
+ if (!out(buf, static_cast<size_t>(n), r, len)) { return false; }
+ r += static_cast<uint64_t>(n);
+
+ if (progress) {
+ if (!progress(r, len)) { return false; }
+ }
+ }
+
+ return true;
+}
+
+void skip_content_with_length(Stream &strm, uint64_t len) {
+ char buf[CPPHTTPLIB_RECV_BUFSIZ];
+ uint64_t r = 0;
+ while (r < len) {
+ auto read_len = static_cast<size_t>(len - r);
+ auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
+ if (n <= 0) { return; }
+ r += static_cast<uint64_t>(n);
+ }
+}
+
+bool read_content_without_length(Stream &strm,
+ ContentReceiverWithProgress out) {
+ char buf[CPPHTTPLIB_RECV_BUFSIZ];
+ uint64_t r = 0;
+ for (;;) {
+ auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
+ if (n < 0) {
+ return false;
+ } else if (n == 0) {
+ return true;
+ }
+
+ if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
+ r += static_cast<uint64_t>(n);
+ }
+
+ return true;
+}
+
+bool read_content_chunked(Stream &strm,
+ ContentReceiverWithProgress out) {
+ const auto bufsiz = 16;
+ char buf[bufsiz];
+
+ stream_line_reader line_reader(strm, buf, bufsiz);
+
+ if (!line_reader.getline()) { return false; }
+
+ unsigned long chunk_len;
+ while (true) {
+ char *end_ptr;
+
+ chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16);
+
+ if (end_ptr == line_reader.ptr()) { return false; }
+ if (chunk_len == ULONG_MAX) { return false; }
+
+ if (chunk_len == 0) { break; }
+
+ if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
+ return false;
+ }
+
+ if (!line_reader.getline()) { return false; }
+
+ if (strcmp(line_reader.ptr(), "\r\n")) { break; }
+
+ if (!line_reader.getline()) { return false; }
+ }
+
+ if (chunk_len == 0) {
+ // Reader terminator after chunks
+ if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n"))
+ return false;
+ }
+
+ return true;
+}
+
+bool is_chunked_transfer_encoding(const Headers &headers) {
+ return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""),
+ "chunked");
+}
+
+template <typename T, typename U>
+bool prepare_content_receiver(T &x, int &status,
+ ContentReceiverWithProgress receiver,
+ bool decompress, U callback) {
+ if (decompress) {
+ std::string encoding = x.get_header_value("Content-Encoding");
+ std::unique_ptr<decompressor> decompressor;
+
+ if (encoding == "gzip" || encoding == "deflate") {
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ decompressor = detail::make_unique<gzip_decompressor>();
+#else
+ status = 415;
+ return false;
+#endif
+ } else if (encoding.find("br") != std::string::npos) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ decompressor = detail::make_unique<brotli_decompressor>();
+#else
+ status = 415;
+ return false;
+#endif
+ }
+
+ if (decompressor) {
+ if (decompressor->is_valid()) {
+ ContentReceiverWithProgress out = [&](const char *buf, size_t n,
+ uint64_t off, uint64_t len) {
+ return decompressor->decompress(buf, n,
+ [&](const char *buf2, size_t n2) {
+ return receiver(buf2, n2, off, len);
+ });
+ };
+ return callback(std::move(out));
+ } else {
+ status = 500;
+ return false;
+ }
+ }
+ }
+
+ ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off,
+ uint64_t len) {
+ return receiver(buf, n, off, len);
+ };
+ return callback(std::move(out));
+}
+
+template <typename T>
+bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
+ Progress progress, ContentReceiverWithProgress receiver,
+ bool decompress) {
+ return prepare_content_receiver(
+ x, status, std::move(receiver), decompress,
+ [&](const ContentReceiverWithProgress &out) {
+ auto ret = true;
+ auto exceed_payload_max_length = false;
+
+ if (is_chunked_transfer_encoding(x.headers)) {
+ ret = read_content_chunked(strm, out);
+ } else if (!has_header(x.headers, "Content-Length")) {
+ ret = read_content_without_length(strm, out);
+ } else {
+ auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
+ if (len > payload_max_length) {
+ exceed_payload_max_length = true;
+ skip_content_with_length(strm, len);
+ ret = false;
+ } else if (len > 0) {
+ ret = read_content_with_length(strm, len, std::move(progress), out);
+ }
+ }
+
+ if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
+ return ret;
+ });
+} // namespace detail
+
+ssize_t write_headers(Stream &strm, const Headers &headers) {
+ ssize_t write_len = 0;
+ for (const auto &x : headers) {
+ auto len =
+ strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
+ if (len < 0) { return len; }
+ write_len += len;
+ }
+ auto len = strm.write("\r\n");
+ if (len < 0) { return len; }
+ write_len += len;
+ return write_len;
+}
+
+bool write_data(Stream &strm, const char *d, size_t l) {
+ size_t offset = 0;
+ while (offset < l) {
+ auto length = strm.write(d + offset, l - offset);
+ if (length < 0) { return false; }
+ offset += static_cast<size_t>(length);
+ }
+ return true;
+}
+
+template <typename T>
+bool write_content(Stream &strm, const ContentProvider &content_provider,
+ size_t offset, size_t length, T is_shutting_down,
+ Error &error) {
+ size_t end_offset = offset + length;
+ auto ok = true;
+ DataSink data_sink;
+
+ data_sink.write = [&](const char *d, size_t l) -> bool {
+ if (ok) {
+ if (write_data(strm, d, l)) {
+ offset += l;
+ } else {
+ ok = false;
+ }
+ }
+ return ok;
+ };
+
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+
+ while (offset < end_offset && !is_shutting_down()) {
+ if (!content_provider(offset, end_offset - offset, data_sink)) {
+ error = Error::Canceled;
+ return false;
+ }
+ if (!ok) {
+ error = Error::Write;
+ return false;
+ }
+ }
+
+ error = Error::Success;
+ return true;
+}
+
+template <typename T>
+bool write_content(Stream &strm, const ContentProvider &content_provider,
+ size_t offset, size_t length,
+ const T &is_shutting_down) {
+ auto error = Error::Success;
+ return write_content(strm, content_provider, offset, length, is_shutting_down,
+ error);
+}
+
+template <typename T>
+bool
+write_content_without_length(Stream &strm,
+ const ContentProvider &content_provider,
+ const T &is_shutting_down) {
+ size_t offset = 0;
+ auto data_available = true;
+ auto ok = true;
+ DataSink data_sink;
+
+ data_sink.write = [&](const char *d, size_t l) -> bool {
+ if (ok) {
+ offset += l;
+ if (!write_data(strm, d, l)) { ok = false; }
+ }
+ return ok;
+ };
+
+ data_sink.done = [&](void) { data_available = false; };
+
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+
+ while (data_available && !is_shutting_down()) {
+ if (!content_provider(offset, 0, data_sink)) { return false; }
+ if (!ok) { return false; }
+ }
+ return true;
+}
+
+template <typename T, typename U>
+bool
+write_content_chunked(Stream &strm, const ContentProvider &content_provider,
+ const T &is_shutting_down, U &compressor, Error &error) {
+ size_t offset = 0;
+ auto data_available = true;
+ auto ok = true;
+ DataSink data_sink;
+
+ data_sink.write = [&](const char *d, size_t l) -> bool {
+ if (ok) {
+ data_available = l > 0;
+ offset += l;
+
+ std::string payload;
+ if (compressor.compress(d, l, false,
+ [&](const char *data, size_t data_len) {
+ payload.append(data, data_len);
+ return true;
+ })) {
+ if (!payload.empty()) {
+ // Emit chunked response header and footer for each chunk
+ auto chunk =
+ from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
+ if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
+ }
+ } else {
+ ok = false;
+ }
+ }
+ return ok;
+ };
+
+ data_sink.done = [&](void) {
+ if (!ok) { return; }
+
+ data_available = false;
+
+ std::string payload;
+ if (!compressor.compress(nullptr, 0, true,
+ [&](const char *data, size_t data_len) {
+ payload.append(data, data_len);
+ return true;
+ })) {
+ ok = false;
+ return;
+ }
+
+ if (!payload.empty()) {
+ // Emit chunked response header and footer for each chunk
+ auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
+ if (!write_data(strm, chunk.data(), chunk.size())) {
+ ok = false;
+ return;
+ }
+ }
+
+ static const std::string done_marker("0\r\n\r\n");
+ if (!write_data(strm, done_marker.data(), done_marker.size())) {
+ ok = false;
+ }
+ };
+
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+
+ while (data_available && !is_shutting_down()) {
+ if (!content_provider(offset, 0, data_sink)) {
+ error = Error::Canceled;
+ return false;
+ }
+ if (!ok) {
+ error = Error::Write;
+ return false;
+ }
+ }
+
+ error = Error::Success;
+ return true;
+}
+
+template <typename T, typename U>
+bool write_content_chunked(Stream &strm,
+ const ContentProvider &content_provider,
+ const T &is_shutting_down, U &compressor) {
+ auto error = Error::Success;
+ return write_content_chunked(strm, content_provider, is_shutting_down,
+ compressor, error);
+}
+
+template <typename T>
+bool redirect(T &cli, Request &req, Response &res,
+ const std::string &path, const std::string &location,
+ Error &error) {
+ Request new_req = req;
+ new_req.path = path;
+ new_req.redirect_count_ -= 1;
+
+ if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) {
+ new_req.method = "GET";
+ new_req.body.clear();
+ new_req.headers.clear();
+ }
+
+ Response new_res;
+
+ auto ret = cli.send(new_req, new_res, error);
+ if (ret) {
+ req = new_req;
+ res = new_res;
+ res.location = location;
+ }
+ return ret;
+}
+
+std::string params_to_query_str(const Params ¶ms) {
+ std::string query;
+
+ for (auto it = params.begin(); it != params.end(); ++it) {
+ if (it != params.begin()) { query += "&"; }
+ query += it->first;
+ query += "=";
+ query += encode_query_param(it->second);
+ }
+ return query;
+}
+
+void parse_query_text(const std::string &s, Params ¶ms) {
+ std::set<std::string> cache;
+ split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
+ std::string kv(b, e);
+ if (cache.find(kv) != cache.end()) { return; }
+ cache.insert(kv);
+
+ std::string key;
+ std::string val;
+ split(b, e, '=', [&](const char *b2, const char *e2) {
+ if (key.empty()) {
+ key.assign(b2, e2);
+ } else {
+ val.assign(b2, e2);
+ }
+ });
+
+ if (!key.empty()) {
+ params.emplace(decode_url(key, true), decode_url(val, true));
+ }
+ });
+}
+
+bool parse_multipart_boundary(const std::string &content_type,
+ std::string &boundary) {
+ auto pos = content_type.find("boundary=");
+ if (pos == std::string::npos) { return false; }
+ boundary = content_type.substr(pos + 9);
+ if (boundary.length() >= 2 && boundary.front() == '"' &&
+ boundary.back() == '"') {
+ boundary = boundary.substr(1, boundary.size() - 2);
+ }
+ return !boundary.empty();
+}
+
+#ifdef CPPHTTPLIB_NO_EXCEPTIONS
+bool parse_range_header(const std::string &s, Ranges &ranges) {
+#else
+bool parse_range_header(const std::string &s, Ranges &ranges) try {
+#endif
+ static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
+ std::smatch m;
+ if (std::regex_match(s, m, re_first_range)) {
+ auto pos = static_cast<size_t>(m.position(1));
+ auto len = static_cast<size_t>(m.length(1));
+ bool all_valid_ranges = true;
+ split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
+ if (!all_valid_ranges) return;
+ static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
+ std::cmatch cm;
+ if (std::regex_match(b, e, cm, re_another_range)) {
+ ssize_t first = -1;
+ if (!cm.str(1).empty()) {
+ first = static_cast<ssize_t>(std::stoll(cm.str(1)));
+ }
+
+ ssize_t last = -1;
+ if (!cm.str(2).empty()) {
+ last = static_cast<ssize_t>(std::stoll(cm.str(2)));
+ }
+
+ if (first != -1 && last != -1 && first > last) {
+ all_valid_ranges = false;
+ return;
+ }
+ ranges.emplace_back(std::make_pair(first, last));
+ }
+ });
+ return all_valid_ranges;
+ }
+ return false;
+#ifdef CPPHTTPLIB_NO_EXCEPTIONS
+}
+#else
+} catch (...) { return false; }
+#endif
+
+class MultipartFormDataParser {
+public:
+ MultipartFormDataParser() = default;
+
+ void set_boundary(std::string &&boundary) {
+ boundary_ = boundary;
+ dash_boundary_crlf_ = dash_ + boundary_ + crlf_;
+ crlf_dash_boundary_ = crlf_ + dash_ + boundary_;
+ }
+
+ bool is_valid() const { return is_valid_; }
+
+ bool parse(const char *buf, size_t n, const ContentReceiver &content_callback,
+ const MultipartContentHeader &header_callback) {
+
+ // TODO: support 'filename*'
+ static const std::regex re_content_disposition(
+ R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~",
+ std::regex_constants::icase);
+
+ buf_append(buf, n);
+
+ while (buf_size() > 0) {
+ switch (state_) {
+ case 0: { // Initial boundary
+ buf_erase(buf_find(dash_boundary_crlf_));
+ if (dash_boundary_crlf_.size() > buf_size()) { return true; }
+ if (!buf_start_with(dash_boundary_crlf_)) { return false; }
+ buf_erase(dash_boundary_crlf_.size());
+ state_ = 1;
+ break;
+ }
+ case 1: { // New entry
+ clear_file_info();
+ state_ = 2;
+ break;
+ }
+ case 2: { // Headers
+ auto pos = buf_find(crlf_);
+ if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
+ while (pos < buf_size()) {
+ // Empty line
+ if (pos == 0) {
+ if (!header_callback(file_)) {
+ is_valid_ = false;
+ return false;
+ }
+ buf_erase(crlf_.size());
+ state_ = 3;
+ break;
+ }
+
+ static const std::string header_name = "content-type:";
+ const auto header = buf_head(pos);
+ if (start_with_case_ignore(header, header_name)) {
+ file_.content_type = trim_copy(header.substr(header_name.size()));
+ } else {
+ std::smatch m;
+ if (std::regex_match(header, m, re_content_disposition)) {
+ file_.name = m[1];
+ file_.filename = m[2];
+ }
+ }
+ buf_erase(pos + crlf_.size());
+ pos = buf_find(crlf_);
+ }
+ if (state_ != 3) { return true; }
+ break;
+ }
+ case 3: { // Body
+ if (crlf_dash_boundary_.size() > buf_size()) { return true; }
+ auto pos = buf_find(crlf_dash_boundary_);
+ if (pos < buf_size()) {
+ if (!content_callback(buf_data(), pos)) {
+ is_valid_ = false;
+ return false;
+ }
+ buf_erase(pos + crlf_dash_boundary_.size());
+ state_ = 4;
+ } else {
+ auto len = buf_size() - crlf_dash_boundary_.size();
+ if (len > 0) {
+ if (!content_callback(buf_data(), len)) {
+ is_valid_ = false;
+ return false;
+ }
+ buf_erase(len);
+ }
+ return true;
+ }
+ break;
+ }
+ case 4: { // Boundary
+ if (crlf_.size() > buf_size()) { return true; }
+ if (buf_start_with(crlf_)) {
+ buf_erase(crlf_.size());
+ state_ = 1;
+ } else {
+ if (dash_crlf_.size() > buf_size()) { return true; }
+ if (buf_start_with(dash_crlf_)) {
+ buf_erase(dash_crlf_.size());
+ is_valid_ = true;
+ buf_erase(buf_size()); // Remove epilogue
+ } else {
+ return true;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ return true;
+ }
+
+private:
+ void clear_file_info() {
+ file_.name.clear();
+ file_.filename.clear();
+ file_.content_type.clear();
+ }
+
+ bool start_with_case_ignore(const std::string &a,
+ const std::string &b) const {
+ if (a.size() < b.size()) { return false; }
+ for (size_t i = 0; i < b.size(); i++) {
+ if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
+ }
+ return true;
+ }
+
+ const std::string dash_ = "--";
+ const std::string crlf_ = "\r\n";
+ const std::string dash_crlf_ = "--\r\n";
+ std::string boundary_;
+ std::string dash_boundary_crlf_;
+ std::string crlf_dash_boundary_;
+
+ size_t state_ = 0;
+ bool is_valid_ = false;
+ MultipartFormData file_;
+
+ // Buffer
+ bool start_with(const std::string &a, size_t spos, size_t epos,
+ const std::string &b) const {
+ if (epos - spos < b.size()) { return false; }
+ for (size_t i = 0; i < b.size(); i++) {
+ if (a[i + spos] != b[i]) { return false; }
+ }
+ return true;
+ }
+
+ size_t buf_size() const { return buf_epos_ - buf_spos_; }
+
+ const char *buf_data() const { return &buf_[buf_spos_]; }
+
+ std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); }
+
+ bool buf_start_with(const std::string &s) const {
+ return start_with(buf_, buf_spos_, buf_epos_, s);
+ }
+
+ size_t buf_find(const std::string &s) const {
+ auto c = s.front();
+
+ size_t off = buf_spos_;
+ while (off < buf_epos_) {
+ auto pos = off;
+ while (true) {
+ if (pos == buf_epos_) { return buf_size(); }
+ if (buf_[pos] == c) { break; }
+ pos++;
+ }
+
+ auto remaining_size = buf_epos_ - pos;
+ if (s.size() > remaining_size) { return buf_size(); }
+
+ if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; }
+
+ off = pos + 1;
+ }
+
+ return buf_size();
+ }
+
+ void buf_append(const char *data, size_t n) {
+ auto remaining_size = buf_size();
+ if (remaining_size > 0 && buf_spos_ > 0) {
+ for (size_t i = 0; i < remaining_size; i++) {
+ buf_[i] = buf_[buf_spos_ + i];
+ }
+ }
+ buf_spos_ = 0;
+ buf_epos_ = remaining_size;
+
+ if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); }
+
+ for (size_t i = 0; i < n; i++) {
+ buf_[buf_epos_ + i] = data[i];
+ }
+ buf_epos_ += n;
+ }
+
+ void buf_erase(size_t size) { buf_spos_ += size; }
+
+ std::string buf_;
+ size_t buf_spos_ = 0;
+ size_t buf_epos_ = 0;
+};
+
+std::string to_lower(const char *beg, const char *end) {
+ std::string out;
+ auto it = beg;
+ while (it != end) {
+ out += static_cast<char>(::tolower(*it));
+ it++;
+ }
+ return out;
+}
+
+std::string make_multipart_data_boundary() {
+ static const char data[] =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+ // std::random_device might actually be deterministic on some
+ // platforms, but due to lack of support in the c++ standard library,
+ // doing better requires either some ugly hacks or breaking portability.
+ std::random_device seed_gen;
+
+ // Request 128 bits of entropy for initialization
+ std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
+ std::mt19937 engine(seed_sequence);
+
+ std::string result = "--cpp-httplib-multipart-data-";
+
+ for (auto i = 0; i < 16; i++) {
+ result += data[engine() % (sizeof(data) - 1)];
+ }
+
+ return result;
+}
+
+std::pair<size_t, size_t>
+get_range_offset_and_length(const Request &req, size_t content_length,
+ size_t index) {
+ auto r = req.ranges[index];
+
+ if (r.first == -1 && r.second == -1) {
+ return std::make_pair(0, content_length);
+ }
+
+ auto slen = static_cast<ssize_t>(content_length);
+
+ if (r.first == -1) {
+ r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
+ r.second = slen - 1;
+ }
+
+ if (r.second == -1) { r.second = slen - 1; }
+ return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
+}
+
+std::string make_content_range_header_field(size_t offset, size_t length,
+ size_t content_length) {
+ std::string field = "bytes ";
+ field += std::to_string(offset);
+ field += "-";
+ field += std::to_string(offset + length - 1);
+ field += "/";
+ field += std::to_string(content_length);
+ return field;
+}
+
+template <typename SToken, typename CToken, typename Content>
+bool process_multipart_ranges_data(const Request &req, Response &res,
+ const std::string &boundary,
+ const std::string &content_type,
+ SToken stoken, CToken ctoken,
+ Content content) {
+ for (size_t i = 0; i < req.ranges.size(); i++) {
+ ctoken("--");
+ stoken(boundary);
+ ctoken("\r\n");
+ if (!content_type.empty()) {
+ ctoken("Content-Type: ");
+ stoken(content_type);
+ ctoken("\r\n");
+ }
+
+ auto offsets = get_range_offset_and_length(req, res.body.size(), i);
+ auto offset = offsets.first;
+ auto length = offsets.second;
+
+ ctoken("Content-Range: ");
+ stoken(make_content_range_header_field(offset, length, res.body.size()));
+ ctoken("\r\n");
+ ctoken("\r\n");
+ if (!content(offset, length)) { return false; }
+ ctoken("\r\n");
+ }
+
+ ctoken("--");
+ stoken(boundary);
+ ctoken("--\r\n");
+
+ return true;
+}
+
+bool make_multipart_ranges_data(const Request &req, Response &res,
+ const std::string &boundary,
+ const std::string &content_type,
+ std::string &data) {
+ return process_multipart_ranges_data(
+ req, res, boundary, content_type,
+ [&](const std::string &token) { data += token; },
+ [&](const std::string &token) { data += token; },
+ [&](size_t offset, size_t length) {
+ if (offset < res.body.size()) {
+ data += res.body.substr(offset, length);
+ return true;
+ }
+ return false;
+ });
+}
+
+size_t
+get_multipart_ranges_data_length(const Request &req, Response &res,
+ const std::string &boundary,
+ const std::string &content_type) {
+ size_t data_length = 0;
+
+ process_multipart_ranges_data(
+ req, res, boundary, content_type,
+ [&](const std::string &token) { data_length += token.size(); },
+ [&](const std::string &token) { data_length += token.size(); },
+ [&](size_t /*offset*/, size_t length) {
+ data_length += length;
+ return true;
+ });
+
+ return data_length;
+}
+
+template <typename T>
+bool write_multipart_ranges_data(Stream &strm, const Request &req,
+ Response &res,
+ const std::string &boundary,
+ const std::string &content_type,
+ const T &is_shutting_down) {
+ return process_multipart_ranges_data(
+ req, res, boundary, content_type,
+ [&](const std::string &token) { strm.write(token); },
+ [&](const std::string &token) { strm.write(token); },
+ [&](size_t offset, size_t length) {
+ return write_content(strm, res.content_provider_, offset, length,
+ is_shutting_down);
+ });
+}
+
+std::pair<size_t, size_t>
+get_range_offset_and_length(const Request &req, const Response &res,
+ size_t index) {
+ auto r = req.ranges[index];
+
+ if (r.second == -1) {
+ r.second = static_cast<ssize_t>(res.content_length_) - 1;
+ }
+
+ return std::make_pair(r.first, r.second - r.first + 1);
+}
+
+bool expect_content(const Request &req) {
+ if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
+ req.method == "PRI" || req.method == "DELETE") {
+ return true;
+ }
+ // TODO: check if Content-Length is set
+ return false;
+}
+
+bool has_crlf(const std::string &s) {
+ auto p = s.c_str();
+ while (*p) {
+ if (*p == '\r' || *p == '\n') { return true; }
+ p++;
+ }
+ return false;
+}
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+std::string message_digest(const std::string &s, const EVP_MD *algo) {
+ auto context = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
+ EVP_MD_CTX_new(), EVP_MD_CTX_free);
+
+ unsigned int hash_length = 0;
+ unsigned char hash[EVP_MAX_MD_SIZE];
+
+ EVP_DigestInit_ex(context.get(), algo, nullptr);
+ EVP_DigestUpdate(context.get(), s.c_str(), s.size());
+ EVP_DigestFinal_ex(context.get(), hash, &hash_length);
+
+ std::stringstream ss;
+ for (auto i = 0u; i < hash_length; ++i) {
+ ss << std::hex << std::setw(2) << std::setfill('0')
+ << (unsigned int)hash[i];
+ }
+
+ return ss.str();
+}
+
+std::string MD5(const std::string &s) {
+ return message_digest(s, EVP_md5());
+}
+
+std::string SHA_256(const std::string &s) {
+ return message_digest(s, EVP_sha256());
+}
+
+std::string SHA_512(const std::string &s) {
+ return message_digest(s, EVP_sha512());
+}
+#endif
+
+#ifdef _WIN32
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+// NOTE: This code came up with the following stackoverflow post:
+// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
+bool load_system_certs_on_windows(X509_STORE *store) {
+ auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
+
+ if (!hStore) { return false; }
+
+ PCCERT_CONTEXT pContext = NULL;
+ while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
+ nullptr) {
+ auto encoded_cert =
+ static_cast<const unsigned char *>(pContext->pbCertEncoded);
+
+ auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
+ if (x509) {
+ X509_STORE_add_cert(store, x509);
+ X509_free(x509);
+ }
+ }
+
+ CertFreeCertificateContext(pContext);
+ CertCloseStore(hStore, 0);
+
+ return true;
+}
+#endif
+
+class WSInit {
+public:
+ WSInit() {
+ WSADATA wsaData;
+ if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true;
+ }
+
+ ~WSInit() {
+ if (is_valid_) WSACleanup();
+ }
+
+ bool is_valid_ = false;
+};
+
+static WSInit wsinit_;
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+std::pair<std::string, std::string> make_digest_authentication_header(
+ const Request &req, const std::map<std::string, std::string> &auth,
+ size_t cnonce_count, const std::string &cnonce, const std::string &username,
+ const std::string &password, bool is_proxy = false) {
+ std::string nc;
+ {
+ std::stringstream ss;
+ ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
+ nc = ss.str();
+ }
+
+ std::string qop;
+ if (auth.find("qop") != auth.end()) {
+ qop = auth.at("qop");
+ if (qop.find("auth-int") != std::string::npos) {
+ qop = "auth-int";
+ } else if (qop.find("auth") != std::string::npos) {
+ qop = "auth";
+ } else {
+ qop.clear();
+ }
+ }
+
+ std::string algo = "MD5";
+ if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
+
+ std::string response;
+ {
+ auto H = algo == "SHA-256" ? detail::SHA_256
+ : algo == "SHA-512" ? detail::SHA_512
+ : detail::MD5;
+
+ auto A1 = username + ":" + auth.at("realm") + ":" + password;
+
+ auto A2 = req.method + ":" + req.path;
+ if (qop == "auth-int") { A2 += ":" + H(req.body); }
+
+ if (qop.empty()) {
+ response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
+ } else {
+ response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
+ ":" + qop + ":" + H(A2));
+ }
+ }
+
+ auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
+
+ auto field = "Digest username=\"" + username + "\", realm=\"" +
+ auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
+ "\", uri=\"" + req.path + "\", algorithm=" + algo +
+ (qop.empty() ? ", response=\""
+ : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
+ cnonce + "\", response=\"") +
+ response + "\"" +
+ (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
+
+ auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+ return std::make_pair(key, field);
+}
+#endif
+
+bool parse_www_authenticate(const Response &res,
+ std::map<std::string, std::string> &auth,
+ bool is_proxy) {
+ auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
+ if (res.has_header(auth_key)) {
+ static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
+ auto s = res.get_header_value(auth_key);
+ auto pos = s.find(' ');
+ if (pos != std::string::npos) {
+ auto type = s.substr(0, pos);
+ if (type == "Basic") {
+ return false;
+ } else if (type == "Digest") {
+ s = s.substr(pos + 1);
+ auto beg = std::sregex_iterator(s.begin(), s.end(), re);
+ for (auto i = beg; i != std::sregex_iterator(); ++i) {
+ auto m = *i;
+ auto key = s.substr(static_cast<size_t>(m.position(1)),
+ static_cast<size_t>(m.length(1)));
+ auto val = m.length(2) > 0
+ ? s.substr(static_cast<size_t>(m.position(2)),
+ static_cast<size_t>(m.length(2)))
+ : s.substr(static_cast<size_t>(m.position(3)),
+ static_cast<size_t>(m.length(3)));
+ auth[key] = val;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240
+std::string random_string(size_t length) {
+ auto randchar = []() -> char {
+ const char charset[] = "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz";
+ const size_t max_index = (sizeof(charset) - 1);
+ return charset[static_cast<size_t>(std::rand()) % max_index];
+ };
+ std::string str(length, 0);
+ std::generate_n(str.begin(), length, randchar);
+ return str;
+}
+
+class ContentProviderAdapter {
+public:
+ explicit ContentProviderAdapter(
+ ContentProviderWithoutLength &&content_provider)
+ : content_provider_(content_provider) {}
+
+ bool operator()(size_t offset, size_t, DataSink &sink) {
+ return content_provider_(offset, sink);
+ }
+
+private:
+ ContentProviderWithoutLength content_provider_;
+};
+
+} // namespace detail
+
+std::string hosted_at(const std::string &hostname) {
+ std::vector<std::string> addrs;
+ hosted_at(hostname, addrs);
+ if (addrs.empty()) { return std::string(); }
+ return addrs[0];
+}
+
+void hosted_at(const std::string &hostname,
+ std::vector<std::string> &addrs) {
+ struct addrinfo hints;
+ struct addrinfo *result;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+
+ if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) {
+#if defined __linux__ && !defined __ANDROID__
+ res_init();
+#endif
+ return;
+ }
+
+ for (auto rp = result; rp; rp = rp->ai_next) {
+ const auto &addr =
+ *reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
+ std::string ip;
+ int dummy = -1;
+ if (detail::get_remote_ip_and_port(addr, sizeof(struct sockaddr_storage),
+ ip, dummy)) {
+ addrs.push_back(ip);
+ }
+ }
+
+ freeaddrinfo(result);
+}
+
+std::string append_query_params(const std::string &path,
+ const Params ¶ms) {
+ std::string path_with_query = path;
+ const static std::regex re("[^?]+\\?.*");
+ auto delm = std::regex_match(path, re) ? '&' : '?';
+ path_with_query += delm + detail::params_to_query_str(params);
+ return path_with_query;
+}
+
+// Header utilities
+std::pair<std::string, std::string> make_range_header(Ranges ranges) {
+ std::string field = "bytes=";
+ auto i = 0;
+ for (auto r : ranges) {
+ if (i != 0) { field += ", "; }
+ if (r.first != -1) { field += std::to_string(r.first); }
+ field += '-';
+ if (r.second != -1) { field += std::to_string(r.second); }
+ i++;
+ }
+ return std::make_pair("Range", std::move(field));
+}
+
+std::pair<std::string, std::string>
+make_basic_authentication_header(const std::string &username,
+ const std::string &password, bool is_proxy) {
+ auto field = "Basic " + detail::base64_encode(username + ":" + password);
+ auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+ return std::make_pair(key, std::move(field));
+}
+
+std::pair<std::string, std::string>
+make_bearer_token_authentication_header(const std::string &token,
+ bool is_proxy = false) {
+ auto field = "Bearer " + token;
+ auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+ return std::make_pair(key, std::move(field));
+}
+
+// Request implementation
+bool Request::has_header(const std::string &key) const {
+ return detail::has_header(headers, key);
+}
+
+std::string Request::get_header_value(const std::string &key,
+ size_t id) const {
+ return detail::get_header_value(headers, key, id, "");
+}
+
+size_t Request::get_header_value_count(const std::string &key) const {
+ auto r = headers.equal_range(key);
+ return static_cast<size_t>(std::distance(r.first, r.second));
+}
+
+void Request::set_header(const std::string &key,
+ const std::string &val) {
+ if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
+ headers.emplace(key, val);
+ }
+}
+
+bool Request::has_param(const std::string &key) const {
+ return params.find(key) != params.end();
+}
+
+std::string Request::get_param_value(const std::string &key,
+ size_t id) const {
+ auto rng = params.equal_range(key);
+ auto it = rng.first;
+ std::advance(it, static_cast<ssize_t>(id));
+ if (it != rng.second) { return it->second; }
+ return std::string();
+}
+
+size_t Request::get_param_value_count(const std::string &key) const {
+ auto r = params.equal_range(key);
+ return static_cast<size_t>(std::distance(r.first, r.second));
+}
+
+bool Request::is_multipart_form_data() const {
+ const auto &content_type = get_header_value("Content-Type");
+ return !content_type.rfind("multipart/form-data", 0);
+}
+
+bool Request::has_file(const std::string &key) const {
+ return files.find(key) != files.end();
+}
+
+MultipartFormData Request::get_file_value(const std::string &key) const {
+ auto it = files.find(key);
+ if (it != files.end()) { return it->second; }
+ return MultipartFormData();
+}
+
+// Response implementation
+bool Response::has_header(const std::string &key) const {
+ return headers.find(key) != headers.end();
+}
+
+std::string Response::get_header_value(const std::string &key,
+ size_t id) const {
+ return detail::get_header_value(headers, key, id, "");
+}
+
+size_t Response::get_header_value_count(const std::string &key) const {
+ auto r = headers.equal_range(key);
+ return static_cast<size_t>(std::distance(r.first, r.second));
+}
+
+void Response::set_header(const std::string &key,
+ const std::string &val) {
+ if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
+ headers.emplace(key, val);
+ }
+}
+
+void Response::set_redirect(const std::string &url, int stat) {
+ if (!detail::has_crlf(url)) {
+ set_header("Location", url);
+ if (300 <= stat && stat < 400) {
+ this->status = stat;
+ } else {
+ this->status = 302;
+ }
+ }
+}
+
+void Response::set_content(const char *s, size_t n,
+ const std::string &content_type) {
+ body.assign(s, n);
+
+ auto rng = headers.equal_range("Content-Type");
+ headers.erase(rng.first, rng.second);
+ set_header("Content-Type", content_type);
+}
+
+void Response::set_content(const std::string &s,
+ const std::string &content_type) {
+ set_content(s.data(), s.size(), content_type);
+}
+
+void Response::set_content_provider(
+ size_t in_length, const std::string &content_type, ContentProvider provider,
+ ContentProviderResourceReleaser resource_releaser) {
+ assert(in_length > 0);
+ set_header("Content-Type", content_type);
+ content_length_ = in_length;
+ content_provider_ = std::move(provider);
+ content_provider_resource_releaser_ = resource_releaser;
+ is_chunked_content_provider_ = false;
+}
+
+void Response::set_content_provider(
+ const std::string &content_type, ContentProviderWithoutLength provider,
+ ContentProviderResourceReleaser resource_releaser) {
+ set_header("Content-Type", content_type);
+ content_length_ = 0;
+ content_provider_ = detail::ContentProviderAdapter(std::move(provider));
+ content_provider_resource_releaser_ = resource_releaser;
+ is_chunked_content_provider_ = false;
+}
+
+void Response::set_chunked_content_provider(
+ const std::string &content_type, ContentProviderWithoutLength provider,
+ ContentProviderResourceReleaser resource_releaser) {
+ set_header("Content-Type", content_type);
+ content_length_ = 0;
+ content_provider_ = detail::ContentProviderAdapter(std::move(provider));
+ content_provider_resource_releaser_ = resource_releaser;
+ is_chunked_content_provider_ = true;
+}
+
+// Result implementation
+bool Result::has_request_header(const std::string &key) const {
+ return request_headers_.find(key) != request_headers_.end();
+}
+
+std::string Result::get_request_header_value(const std::string &key,
+ size_t id) const {
+ return detail::get_header_value(request_headers_, key, id, "");
+}
+
+size_t
+Result::get_request_header_value_count(const std::string &key) const {
+ auto r = request_headers_.equal_range(key);
+ return static_cast<size_t>(std::distance(r.first, r.second));
+}
+
+// Stream implementation
+ssize_t Stream::write(const char *ptr) {
+ return write(ptr, strlen(ptr));
+}
+
+ssize_t Stream::write(const std::string &s) {
+ return write(s.data(), s.size());
+}
+
+namespace detail {
+
+// Socket stream implementation
+SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
+ time_t read_timeout_usec,
+ time_t write_timeout_sec,
+ time_t write_timeout_usec)
+ : sock_(sock), read_timeout_sec_(read_timeout_sec),
+ read_timeout_usec_(read_timeout_usec),
+ write_timeout_sec_(write_timeout_sec),
+ write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {}
+
+SocketStream::~SocketStream() {}
+
+bool SocketStream::is_readable() const {
+ return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+}
+
+bool SocketStream::is_writable() const {
+ return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
+}
+
+ssize_t SocketStream::read(char *ptr, size_t size) {
+#ifdef _WIN32
+ size =
+ (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
+#else
+ size = (std::min)(size,
+ static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
+#endif
+
+ if (read_buff_off_ < read_buff_content_size_) {
+ auto remaining_size = read_buff_content_size_ - read_buff_off_;
+ if (size <= remaining_size) {
+ memcpy(ptr, read_buff_.data() + read_buff_off_, size);
+ read_buff_off_ += size;
+ return static_cast<ssize_t>(size);
+ } else {
+ memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size);
+ read_buff_off_ += remaining_size;
+ return static_cast<ssize_t>(remaining_size);
+ }
+ }
+
+ if (!is_readable()) { return -1; }
+
+ read_buff_off_ = 0;
+ read_buff_content_size_ = 0;
+
+ if (size < read_buff_size_) {
+ auto n = read_socket(sock_, read_buff_.data(), read_buff_size_,
+ CPPHTTPLIB_RECV_FLAGS);
+ if (n <= 0) {
+ return n;
+ } else if (n <= static_cast<ssize_t>(size)) {
+ memcpy(ptr, read_buff_.data(), static_cast<size_t>(n));
+ return n;
+ } else {
+ memcpy(ptr, read_buff_.data(), size);
+ read_buff_off_ = size;
+ read_buff_content_size_ = static_cast<size_t>(n);
+ return static_cast<ssize_t>(size);
+ }
+ } else {
+ return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
+ }
+}
+
+ssize_t SocketStream::write(const char *ptr, size_t size) {
+ if (!is_writable()) { return -1; }
+
+#if defined(_WIN32) && !defined(_WIN64)
+ size =
+ (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
+#endif
+
+ return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
+}
+
+void SocketStream::get_remote_ip_and_port(std::string &ip,
+ int &port) const {
+ return detail::get_remote_ip_and_port(sock_, ip, port);
+}
+
+socket_t SocketStream::socket() const { return sock_; }
+
+// Buffer stream implementation
+bool BufferStream::is_readable() const { return true; }
+
+bool BufferStream::is_writable() const { return true; }
+
+ssize_t BufferStream::read(char *ptr, size_t size) {
+#if defined(_MSC_VER) && _MSC_VER < 1910
+ auto len_read = buffer._Copy_s(ptr, size, size, position);
+#else
+ auto len_read = buffer.copy(ptr, size, position);
+#endif
+ position += static_cast<size_t>(len_read);
+ return static_cast<ssize_t>(len_read);
+}
+
+ssize_t BufferStream::write(const char *ptr, size_t size) {
+ buffer.append(ptr, size);
+ return static_cast<ssize_t>(size);
+}
+
+void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
+ int & /*port*/) const {}
+
+socket_t BufferStream::socket() const { return 0; }
+
+const std::string &BufferStream::get_buffer() const { return buffer; }
+
+} // namespace detail
+
+// HTTP server implementation
+Server::Server()
+ : new_task_queue(
+ [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }),
+ svr_sock_(INVALID_SOCKET), is_running_(false) {
+#ifndef _WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+}
+
+Server::~Server() {}
+
+Server &Server::Get(const std::string &pattern, Handler handler) {
+ get_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+Server &Server::Post(const std::string &pattern, Handler handler) {
+ post_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+Server &Server::Post(const std::string &pattern,
+ HandlerWithContentReader handler) {
+ post_handlers_for_content_reader_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+Server &Server::Put(const std::string &pattern, Handler handler) {
+ put_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+Server &Server::Put(const std::string &pattern,
+ HandlerWithContentReader handler) {
+ put_handlers_for_content_reader_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+Server &Server::Patch(const std::string &pattern, Handler handler) {
+ patch_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+Server &Server::Patch(const std::string &pattern,
+ HandlerWithContentReader handler) {
+ patch_handlers_for_content_reader_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+Server &Server::Delete(const std::string &pattern, Handler handler) {
+ delete_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+Server &Server::Delete(const std::string &pattern,
+ HandlerWithContentReader handler) {
+ delete_handlers_for_content_reader_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+Server &Server::Options(const std::string &pattern, Handler handler) {
+ options_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+bool Server::set_base_dir(const std::string &dir,
+ const std::string &mount_point) {
+ return set_mount_point(mount_point, dir);
+}
+
+bool Server::set_mount_point(const std::string &mount_point,
+ const std::string &dir, Headers headers) {
+ if (detail::is_dir(dir)) {
+ std::string mnt = !mount_point.empty() ? mount_point : "/";
+ if (!mnt.empty() && mnt[0] == '/') {
+ base_dirs_.push_back({mnt, dir, std::move(headers)});
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Server::remove_mount_point(const std::string &mount_point) {
+ for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
+ if (it->mount_point == mount_point) {
+ base_dirs_.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+Server &
+Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
+ const std::string &mime) {
+ file_extension_and_mimetype_map_[ext] = mime;
+ return *this;
+}
+
+Server &Server::set_file_request_handler(Handler handler) {
+ file_request_handler_ = std::move(handler);
+ return *this;
+}
+
+Server &Server::set_error_handler(HandlerWithResponse handler) {
+ error_handler_ = std::move(handler);
+ return *this;
+}
+
+Server &Server::set_error_handler(Handler handler) {
+ error_handler_ = [handler](const Request &req, Response &res) {
+ handler(req, res);
+ return HandlerResponse::Handled;
+ };
+ return *this;
+}
+
+Server &Server::set_exception_handler(ExceptionHandler handler) {
+ exception_handler_ = std::move(handler);
+ return *this;
+}
+
+Server &Server::set_pre_routing_handler(HandlerWithResponse handler) {
+ pre_routing_handler_ = std::move(handler);
+ return *this;
+}
+
+Server &Server::set_post_routing_handler(Handler handler) {
+ post_routing_handler_ = std::move(handler);
+ return *this;
+}
+
+Server &Server::set_logger(Logger logger) {
+ logger_ = std::move(logger);
+ return *this;
+}
+
+Server &
+Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
+ expect_100_continue_handler_ = std::move(handler);
+
+ return *this;
+}
+
+Server &Server::set_address_family(int family) {
+ address_family_ = family;
+ return *this;
+}
+
+Server &Server::set_tcp_nodelay(bool on) {
+ tcp_nodelay_ = on;
+ return *this;
+}
+
+Server &Server::set_socket_options(SocketOptions socket_options) {
+ socket_options_ = std::move(socket_options);
+ return *this;
+}
+
+Server &Server::set_default_headers(Headers headers) {
+ default_headers_ = std::move(headers);
+ return *this;
+}
+
+Server &Server::set_keep_alive_max_count(size_t count) {
+ keep_alive_max_count_ = count;
+ return *this;
+}
+
+Server &Server::set_keep_alive_timeout(time_t sec) {
+ keep_alive_timeout_sec_ = sec;
+ return *this;
+}
+
+Server &Server::set_read_timeout(time_t sec, time_t usec) {
+ read_timeout_sec_ = sec;
+ read_timeout_usec_ = usec;
+ return *this;
+}
+
+Server &Server::set_write_timeout(time_t sec, time_t usec) {
+ write_timeout_sec_ = sec;
+ write_timeout_usec_ = usec;
+ return *this;
+}
+
+Server &Server::set_idle_interval(time_t sec, time_t usec) {
+ idle_interval_sec_ = sec;
+ idle_interval_usec_ = usec;
+ return *this;
+}
+
+Server &Server::set_payload_max_length(size_t length) {
+ payload_max_length_ = length;
+ return *this;
+}
+
+bool Server::bind_to_port(const std::string &host, int port,
+ int socket_flags) {
+ if (bind_internal(host, port, socket_flags) < 0) return false;
+ return true;
+}
+int Server::bind_to_any_port(const std::string &host, int socket_flags) {
+ return bind_internal(host, 0, socket_flags);
+}
+
+bool Server::listen_after_bind() { return listen_internal(); }
+
+bool Server::listen(const std::string &host, int port,
+ int socket_flags) {
+ return bind_to_port(host, port, socket_flags) && listen_internal();
+}
+
+bool Server::is_running() const { return is_running_; }
+
+void Server::stop() {
+ if (is_running_) {
+ assert(svr_sock_ != INVALID_SOCKET);
+ std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ }
+}
+
+bool Server::parse_request_line(const char *s, Request &req) {
+ auto len = strlen(s);
+ if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
+ len -= 2;
+
+ {
+ size_t count = 0;
+
+ detail::split(s, s + len, ' ', [&](const char *b, const char *e) {
+ switch (count) {
+ case 0: req.method = std::string(b, e); break;
+ case 1: req.target = std::string(b, e); break;
+ case 2: req.version = std::string(b, e); break;
+ default: break;
+ }
+ count++;
+ });
+
+ if (count != 3) { return false; }
+ }
+
+ static const std::set<std::string> methods{
+ "GET", "HEAD", "POST", "PUT", "DELETE",
+ "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
+
+ if (methods.find(req.method) == methods.end()) { return false; }
+
+ if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; }
+
+ {
+ // Skip URL fragment
+ for (size_t i = 0; i < req.target.size(); i++) {
+ if (req.target[i] == '#') {
+ req.target.erase(i);
+ break;
+ }
+ }
+
+ size_t count = 0;
+
+ detail::split(req.target.data(), req.target.data() + req.target.size(), '?',
+ [&](const char *b, const char *e) {
+ switch (count) {
+ case 0:
+ req.path = detail::decode_url(std::string(b, e), false);
+ break;
+ case 1: {
+ if (e - b > 0) {
+ detail::parse_query_text(std::string(b, e), req.params);
+ }
+ break;
+ }
+ default: break;
+ }
+ count++;
+ });
+
+ if (count > 2) { return false; }
+ }
+
+ return true;
+}
+
+bool Server::write_response(Stream &strm, bool close_connection,
+ const Request &req, Response &res) {
+ return write_response_core(strm, close_connection, req, res, false);
+}
+
+bool Server::write_response_with_content(Stream &strm,
+ bool close_connection,
+ const Request &req,
+ Response &res) {
+ return write_response_core(strm, close_connection, req, res, true);
+}
+
+bool Server::write_response_core(Stream &strm, bool close_connection,
+ const Request &req, Response &res,
+ bool need_apply_ranges) {
+ assert(res.status != -1);
+
+ if (400 <= res.status && error_handler_ &&
+ error_handler_(req, res) == HandlerResponse::Handled) {
+ need_apply_ranges = true;
+ }
+
+ std::string content_type;
+ std::string boundary;
+ if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
+
+ // Prepare additional headers
+ if (close_connection || req.get_header_value("Connection") == "close") {
+ res.set_header("Connection", "close");
+ } else {
+ std::stringstream ss;
+ ss << "timeout=" << keep_alive_timeout_sec_
+ << ", max=" << keep_alive_max_count_;
+ res.set_header("Keep-Alive", ss.str());
+ }
+
+ if (!res.has_header("Content-Type") &&
+ (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) {
+ res.set_header("Content-Type", "text/plain");
+ }
+
+ if (!res.has_header("Content-Length") && res.body.empty() &&
+ !res.content_length_ && !res.content_provider_) {
+ res.set_header("Content-Length", "0");
+ }
+
+ if (!res.has_header("Accept-Ranges") && req.method == "HEAD") {
+ res.set_header("Accept-Ranges", "bytes");
+ }
+
+ if (post_routing_handler_) { post_routing_handler_(req, res); }
+
+ // Response line and headers
+ {
+ detail::BufferStream bstrm;
+
+ if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
+ detail::status_message(res.status))) {
+ return false;
+ }
+
+ if (!detail::write_headers(bstrm, res.headers)) { return false; }
+
+ // Flush buffer
+ auto &data = bstrm.get_buffer();
+ detail::write_data(strm, data.data(), data.size());
+ }
+
+ // Body
+ auto ret = true;
+ if (req.method != "HEAD") {
+ if (!res.body.empty()) {
+ if (!detail::write_data(strm, res.body.data(), res.body.size())) {
+ ret = false;
+ }
+ } else if (res.content_provider_) {
+ if (write_content_with_provider(strm, req, res, boundary, content_type)) {
+ res.content_provider_success_ = true;
+ } else {
+ res.content_provider_success_ = false;
+ ret = false;
+ }
+ }
+ }
+
+ // Log
+ if (logger_) { logger_(req, res); }
+
+ return ret;
+}
+
+bool
+Server::write_content_with_provider(Stream &strm, const Request &req,
+ Response &res, const std::string &boundary,
+ const std::string &content_type) {
+ auto is_shutting_down = [this]() {
+ return this->svr_sock_ == INVALID_SOCKET;
+ };
+
+ if (res.content_length_ > 0) {
+ if (req.ranges.empty()) {
+ return detail::write_content(strm, res.content_provider_, 0,
+ res.content_length_, is_shutting_down);
+ } else if (req.ranges.size() == 1) {
+ auto offsets =
+ detail::get_range_offset_and_length(req, res.content_length_, 0);
+ auto offset = offsets.first;
+ auto length = offsets.second;
+ return detail::write_content(strm, res.content_provider_, offset, length,
+ is_shutting_down);
+ } else {
+ return detail::write_multipart_ranges_data(
+ strm, req, res, boundary, content_type, is_shutting_down);
+ }
+ } else {
+ if (res.is_chunked_content_provider_) {
+ auto type = detail::encoding_type(req, res);
+
+ std::unique_ptr<detail::compressor> compressor;
+ if (type == detail::EncodingType::Gzip) {
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ compressor = detail::make_unique<detail::gzip_compressor>();
+#endif
+ } else if (type == detail::EncodingType::Brotli) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ compressor = detail::make_unique<detail::brotli_compressor>();
+#endif
+ } else {
+ compressor = detail::make_unique<detail::nocompressor>();
+ }
+ assert(compressor != nullptr);
+
+ return detail::write_content_chunked(strm, res.content_provider_,
+ is_shutting_down, *compressor);
+ } else {
+ return detail::write_content_without_length(strm, res.content_provider_,
+ is_shutting_down);
+ }
+ }
+}
+
+bool Server::read_content(Stream &strm, Request &req, Response &res) {
+ MultipartFormDataMap::iterator cur;
+ if (read_content_core(
+ strm, req, res,
+ // Regular
+ [&](const char *buf, size_t n) {
+ if (req.body.size() + n > req.body.max_size()) { return false; }
+ req.body.append(buf, n);
+ return true;
+ },
+ // Multipart
+ [&](const MultipartFormData &file) {
+ cur = req.files.emplace(file.name, file);
+ return true;
+ },
+ [&](const char *buf, size_t n) {
+ auto &content = cur->second.content;
+ if (content.size() + n > content.max_size()) { return false; }
+ content.append(buf, n);
+ return true;
+ })) {
+ const auto &content_type = req.get_header_value("Content-Type");
+ if (!content_type.find("application/x-www-form-urlencoded")) {
+ if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
+ res.status = 413; // NOTE: should be 414?
+ return false;
+ }
+ detail::parse_query_text(req.body, req.params);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool Server::read_content_with_content_receiver(
+ Stream &strm, Request &req, Response &res, ContentReceiver receiver,
+ MultipartContentHeader multipart_header,
+ ContentReceiver multipart_receiver) {
+ return read_content_core(strm, req, res, std::move(receiver),
+ std::move(multipart_header),
+ std::move(multipart_receiver));
+}
+
+bool Server::read_content_core(Stream &strm, Request &req, Response &res,
+ ContentReceiver receiver,
+ MultipartContentHeader mulitpart_header,
+ ContentReceiver multipart_receiver) {
+ detail::MultipartFormDataParser multipart_form_data_parser;
+ ContentReceiverWithProgress out;
+
+ if (req.is_multipart_form_data()) {
+ const auto &content_type = req.get_header_value("Content-Type");
+ std::string boundary;
+ if (!detail::parse_multipart_boundary(content_type, boundary)) {
+ res.status = 400;
+ return false;
+ }
+
+ multipart_form_data_parser.set_boundary(std::move(boundary));
+ out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) {
+ /* For debug
+ size_t pos = 0;
+ while (pos < n) {
+ auto read_size = (std::min)<size_t>(1, n - pos);
+ auto ret = multipart_form_data_parser.parse(
+ buf + pos, read_size, multipart_receiver, mulitpart_header);
+ if (!ret) { return false; }
+ pos += read_size;
+ }
+ return true;
+ */
+ return multipart_form_data_parser.parse(buf, n, multipart_receiver,
+ mulitpart_header);
+ };
+ } else {
+ out = [receiver](const char *buf, size_t n, uint64_t /*off*/,
+ uint64_t /*len*/) { return receiver(buf, n); };
+ }
+
+ if (req.method == "DELETE" && !req.has_header("Content-Length")) {
+ return true;
+ }
+
+ if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
+ out, true)) {
+ return false;
+ }
+
+ if (req.is_multipart_form_data()) {
+ if (!multipart_form_data_parser.is_valid()) {
+ res.status = 400;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Server::handle_file_request(const Request &req, Response &res,
+ bool head) {
+ for (const auto &entry : base_dirs_) {
+ // Prefix match
+ if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
+ std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
+ if (detail::is_valid_path(sub_path)) {
+ auto path = entry.base_dir + sub_path;
+ if (path.back() == '/') { path += "index.html"; }
+
+ if (detail::is_file(path)) {
+ detail::read_file(path, res.body);
+ auto type =
+ detail::find_content_type(path, file_extension_and_mimetype_map_);
+ if (type) { res.set_header("Content-Type", type); }
+ for (const auto &kv : entry.headers) {
+ res.set_header(kv.first.c_str(), kv.second);
+ }
+ res.status = req.has_header("Range") ? 206 : 200;
+ if (!head && file_request_handler_) {
+ file_request_handler_(req, res);
+ }
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+socket_t
+Server::create_server_socket(const std::string &host, int port,
+ int socket_flags,
+ SocketOptions socket_options) const {
+ return detail::create_socket(
+ host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
+ std::move(socket_options),
+ [](socket_t sock, struct addrinfo &ai) -> bool {
+ if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
+ return false;
+ }
+ if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; }
+ return true;
+ });
+}
+
+int Server::bind_internal(const std::string &host, int port,
+ int socket_flags) {
+ if (!is_valid()) { return -1; }
+
+ svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
+ if (svr_sock_ == INVALID_SOCKET) { return -1; }
+
+ if (port == 0) {
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof(addr);
+ if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
+ &addr_len) == -1) {
+ return -1;
+ }
+ if (addr.ss_family == AF_INET) {
+ return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
+ } else if (addr.ss_family == AF_INET6) {
+ return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
+ } else {
+ return -1;
+ }
+ } else {
+ return port;
+ }
+}
+
+bool Server::listen_internal() {
+ auto ret = true;
+ is_running_ = true;
+
+ {
+ std::unique_ptr<TaskQueue> task_queue(new_task_queue());
+
+ while (svr_sock_ != INVALID_SOCKET) {
+#ifndef _WIN32
+ if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
+#endif
+ auto val = detail::select_read(svr_sock_, idle_interval_sec_,
+ idle_interval_usec_);
+ if (val == 0) { // Timeout
+ task_queue->on_idle();
+ continue;
+ }
+#ifndef _WIN32
+ }
+#endif
+ socket_t sock = accept(svr_sock_, nullptr, nullptr);
+
+ if (sock == INVALID_SOCKET) {
+ if (errno == EMFILE) {
+ // The per-process limit of open file descriptors has been reached.
+ // Try to accept new connections after a short sleep.
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ continue;
+ }
+ if (svr_sock_ != INVALID_SOCKET) {
+ detail::close_socket(svr_sock_);
+ ret = false;
+ } else {
+ ; // The server socket was closed by user.
+ }
+ break;
+ }
+
+ {
+#ifdef _WIN32
+ auto timeout = static_cast<uint32_t>(read_timeout_sec_ * 1000 +
+ read_timeout_usec_ / 1000);
+ setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
+ sizeof(timeout));
+#else
+ timeval tv;
+ tv.tv_sec = static_cast<long>(read_timeout_sec_);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec_);
+ setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
+#endif
+ }
+ {
+
+#ifdef _WIN32
+ auto timeout = static_cast<uint32_t>(write_timeout_sec_ * 1000 +
+ write_timeout_usec_ / 1000);
+ setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
+ sizeof(timeout));
+#else
+ timeval tv;
+ tv.tv_sec = static_cast<long>(write_timeout_sec_);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec_);
+ setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
+#endif
+ }
+
+#if __cplusplus > 201703L
+ task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
+#else
+ task_queue->enqueue([=]() { process_and_close_socket(sock); });
+#endif
+ }
+
+ task_queue->shutdown();
+ }
+
+ is_running_ = false;
+ return ret;
+}
+
+bool Server::routing(Request &req, Response &res, Stream &strm) {
+ if (pre_routing_handler_ &&
+ pre_routing_handler_(req, res) == HandlerResponse::Handled) {
+ return true;
+ }
+
+ // File handler
+ bool is_head_request = req.method == "HEAD";
+ if ((req.method == "GET" || is_head_request) &&
+ handle_file_request(req, res, is_head_request)) {
+ return true;
+ }
+
+ if (detail::expect_content(req)) {
+ // Content reader handler
+ {
+ ContentReader reader(
+ [&](ContentReceiver receiver) {
+ return read_content_with_content_receiver(
+ strm, req, res, std::move(receiver), nullptr, nullptr);
+ },
+ [&](MultipartContentHeader header, ContentReceiver receiver) {
+ return read_content_with_content_receiver(strm, req, res, nullptr,
+ std::move(header),
+ std::move(receiver));
+ });
+
+ if (req.method == "POST") {
+ if (dispatch_request_for_content_reader(
+ req, res, std::move(reader),
+ post_handlers_for_content_reader_)) {
+ return true;
+ }
+ } else if (req.method == "PUT") {
+ if (dispatch_request_for_content_reader(
+ req, res, std::move(reader),
+ put_handlers_for_content_reader_)) {
+ return true;
+ }
+ } else if (req.method == "PATCH") {
+ if (dispatch_request_for_content_reader(
+ req, res, std::move(reader),
+ patch_handlers_for_content_reader_)) {
+ return true;
+ }
+ } else if (req.method == "DELETE") {
+ if (dispatch_request_for_content_reader(
+ req, res, std::move(reader),
+ delete_handlers_for_content_reader_)) {
+ return true;
+ }
+ }
+ }
+
+ // Read content into `req.body`
+ if (!read_content(strm, req, res)) { return false; }
+ }
+
+ // Regular handler
+ if (req.method == "GET" || req.method == "HEAD") {
+ return dispatch_request(req, res, get_handlers_);
+ } else if (req.method == "POST") {
+ return dispatch_request(req, res, post_handlers_);
+ } else if (req.method == "PUT") {
+ return dispatch_request(req, res, put_handlers_);
+ } else if (req.method == "DELETE") {
+ return dispatch_request(req, res, delete_handlers_);
+ } else if (req.method == "OPTIONS") {
+ return dispatch_request(req, res, options_handlers_);
+ } else if (req.method == "PATCH") {
+ return dispatch_request(req, res, patch_handlers_);
+ }
+
+ res.status = 400;
+ return false;
+}
+
+bool Server::dispatch_request(Request &req, Response &res,
+ const Handlers &handlers) {
+ for (const auto &x : handlers) {
+ const auto &pattern = x.first;
+ const auto &handler = x.second;
+
+ if (std::regex_match(req.path, req.matches, pattern)) {
+ handler(req, res);
+ return true;
+ }
+ }
+ return false;
+}
+
+void Server::apply_ranges(const Request &req, Response &res,
+ std::string &content_type,
+ std::string &boundary) {
+ if (req.ranges.size() > 1) {
+ boundary = detail::make_multipart_data_boundary();
+
+ auto it = res.headers.find("Content-Type");
+ if (it != res.headers.end()) {
+ content_type = it->second;
+ res.headers.erase(it);
+ }
+
+ res.headers.emplace("Content-Type",
+ "multipart/byteranges; boundary=" + boundary);
+ }
+
+ auto type = detail::encoding_type(req, res);
+
+ if (res.body.empty()) {
+ if (res.content_length_ > 0) {
+ size_t length = 0;
+ if (req.ranges.empty()) {
+ length = res.content_length_;
+ } else if (req.ranges.size() == 1) {
+ auto offsets =
+ detail::get_range_offset_and_length(req, res.content_length_, 0);
+ auto offset = offsets.first;
+ length = offsets.second;
+ auto content_range = detail::make_content_range_header_field(
+ offset, length, res.content_length_);
+ res.set_header("Content-Range", content_range);
+ } else {
+ length = detail::get_multipart_ranges_data_length(req, res, boundary,
+ content_type);
+ }
+ res.set_header("Content-Length", std::to_string(length));
+ } else {
+ if (res.content_provider_) {
+ if (res.is_chunked_content_provider_) {
+ res.set_header("Transfer-Encoding", "chunked");
+ if (type == detail::EncodingType::Gzip) {
+ res.set_header("Content-Encoding", "gzip");
+ } else if (type == detail::EncodingType::Brotli) {
+ res.set_header("Content-Encoding", "br");
+ }
+ }
+ }
+ }
+ } else {
+ if (req.ranges.empty()) {
+ ;
+ } else if (req.ranges.size() == 1) {
+ auto offsets =
+ detail::get_range_offset_and_length(req, res.body.size(), 0);
+ auto offset = offsets.first;
+ auto length = offsets.second;
+ auto content_range = detail::make_content_range_header_field(
+ offset, length, res.body.size());
+ res.set_header("Content-Range", content_range);
+ if (offset < res.body.size()) {
+ res.body = res.body.substr(offset, length);
+ } else {
+ res.body.clear();
+ res.status = 416;
+ }
+ } else {
+ std::string data;
+ if (detail::make_multipart_ranges_data(req, res, boundary, content_type,
+ data)) {
+ res.body.swap(data);
+ } else {
+ res.body.clear();
+ res.status = 416;
+ }
+ }
+
+ if (type != detail::EncodingType::None) {
+ std::unique_ptr<detail::compressor> compressor;
+ std::string content_encoding;
+
+ if (type == detail::EncodingType::Gzip) {
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ compressor = detail::make_unique<detail::gzip_compressor>();
+ content_encoding = "gzip";
+#endif
+ } else if (type == detail::EncodingType::Brotli) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ compressor = detail::make_unique<detail::brotli_compressor>();
+ content_encoding = "br";
+#endif
+ }
+
+ if (compressor) {
+ std::string compressed;
+ if (compressor->compress(res.body.data(), res.body.size(), true,
+ [&](const char *data, size_t data_len) {
+ compressed.append(data, data_len);
+ return true;
+ })) {
+ res.body.swap(compressed);
+ res.set_header("Content-Encoding", content_encoding);
+ }
+ }
+ }
+
+ auto length = std::to_string(res.body.size());
+ res.set_header("Content-Length", length);
+ }
+}
+
+bool Server::dispatch_request_for_content_reader(
+ Request &req, Response &res, ContentReader content_reader,
+ const HandlersForContentReader &handlers) {
+ for (const auto &x : handlers) {
+ const auto &pattern = x.first;
+ const auto &handler = x.second;
+
+ if (std::regex_match(req.path, req.matches, pattern)) {
+ handler(req, res, content_reader);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+Server::process_request(Stream &strm, bool close_connection,
+ bool &connection_closed,
+ const std::function<void(Request &)> &setup_request) {
+ std::array<char, 2048> buf{};
+
+ detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
+
+ // Connection has been closed on client
+ if (!line_reader.getline()) { return false; }
+
+ Request req;
+ Response res;
+
+ res.version = "HTTP/1.1";
+
+ for (const auto &header : default_headers_) {
+ if (res.headers.find(header.first) == res.headers.end()) {
+ res.headers.insert(header);
+ }
+ }
+
+#ifdef _WIN32
+ // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL).
+#else
+#ifndef CPPHTTPLIB_USE_POLL
+ // Socket file descriptor exceeded FD_SETSIZE...
+ if (strm.socket() >= FD_SETSIZE) {
+ Headers dummy;
+ detail::read_headers(strm, dummy);
+ res.status = 500;
+ return write_response(strm, close_connection, req, res);
+ }
+#endif
+#endif
+
+ // Check if the request URI doesn't exceed the limit
+ if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
+ Headers dummy;
+ detail::read_headers(strm, dummy);
+ res.status = 414;
+ return write_response(strm, close_connection, req, res);
+ }
+
+ // Request line and headers
+ if (!parse_request_line(line_reader.ptr(), req) ||
+ !detail::read_headers(strm, req.headers)) {
+ res.status = 400;
+ return write_response(strm, close_connection, req, res);
+ }
+
+ if (req.get_header_value("Connection") == "close") {
+ connection_closed = true;
+ }
+
+ if (req.version == "HTTP/1.0" &&
+ req.get_header_value("Connection") != "Keep-Alive") {
+ connection_closed = true;
+ }
+
+ strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
+ req.set_header("REMOTE_ADDR", req.remote_addr);
+ req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
+
+ if (req.has_header("Range")) {
+ const auto &range_header_value = req.get_header_value("Range");
+ if (!detail::parse_range_header(range_header_value, req.ranges)) {
+ res.status = 416;
+ return write_response(strm, close_connection, req, res);
+ }
+ }
+
+ if (setup_request) { setup_request(req); }
+
+ if (req.get_header_value("Expect") == "100-continue") {
+ auto status = 100;
+ if (expect_100_continue_handler_) {
+ status = expect_100_continue_handler_(req, res);
+ }
+ switch (status) {
+ case 100:
+ case 417:
+ strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
+ detail::status_message(status));
+ break;
+ default: return write_response(strm, close_connection, req, res);
+ }
+ }
+
+ // Rounting
+ bool routed = false;
+#ifdef CPPHTTPLIB_NO_EXCEPTIONS
+ routed = routing(req, res, strm);
+#else
+ try {
+ routed = routing(req, res, strm);
+ } catch (std::exception &e) {
+ if (exception_handler_) {
+ auto ep = std::current_exception();
+ exception_handler_(req, res, ep);
+ routed = true;
+ } else {
+ res.status = 500;
+ res.set_header("EXCEPTION_WHAT", e.what());
+ }
+ } catch (...) {
+ if (exception_handler_) {
+ auto ep = std::current_exception();
+ exception_handler_(req, res, ep);
+ routed = true;
+ } else {
+ res.status = 500;
+ res.set_header("EXCEPTION_WHAT", "UNKNOWN");
+ }
+ }
+#endif
+
+ if (routed) {
+ if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
+ return write_response_with_content(strm, close_connection, req, res);
+ } else {
+ if (res.status == -1) { res.status = 404; }
+ return write_response(strm, close_connection, req, res);
+ }
+}
+
+bool Server::is_valid() const { return true; }
+
+bool Server::process_and_close_socket(socket_t sock) {
+ auto ret = detail::process_server_socket(
+ svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
+ read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+ write_timeout_usec_,
+ [this](Stream &strm, bool close_connection, bool &connection_closed) {
+ return process_request(strm, close_connection, connection_closed,
+ nullptr);
+ });
+
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ return ret;
+}
+
+// HTTP client implementation
+ClientImpl::ClientImpl(const std::string &host)
+ : ClientImpl(host, 80, std::string(), std::string()) {}
+
+ClientImpl::ClientImpl(const std::string &host, int port)
+ : ClientImpl(host, port, std::string(), std::string()) {}
+
+ClientImpl::ClientImpl(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path)
+ : host_(host), port_(port),
+ host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)),
+ client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
+
+ClientImpl::~ClientImpl() {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ shutdown_socket(socket_);
+ close_socket(socket_);
+}
+
+bool ClientImpl::is_valid() const { return true; }
+
+void ClientImpl::copy_settings(const ClientImpl &rhs) {
+ client_cert_path_ = rhs.client_cert_path_;
+ client_key_path_ = rhs.client_key_path_;
+ connection_timeout_sec_ = rhs.connection_timeout_sec_;
+ read_timeout_sec_ = rhs.read_timeout_sec_;
+ read_timeout_usec_ = rhs.read_timeout_usec_;
+ write_timeout_sec_ = rhs.write_timeout_sec_;
+ write_timeout_usec_ = rhs.write_timeout_usec_;
+ basic_auth_username_ = rhs.basic_auth_username_;
+ basic_auth_password_ = rhs.basic_auth_password_;
+ bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ digest_auth_username_ = rhs.digest_auth_username_;
+ digest_auth_password_ = rhs.digest_auth_password_;
+#endif
+ keep_alive_ = rhs.keep_alive_;
+ follow_location_ = rhs.follow_location_;
+ url_encode_ = rhs.url_encode_;
+ address_family_ = rhs.address_family_;
+ tcp_nodelay_ = rhs.tcp_nodelay_;
+ socket_options_ = rhs.socket_options_;
+ compress_ = rhs.compress_;
+ decompress_ = rhs.decompress_;
+ interface_ = rhs.interface_;
+ proxy_host_ = rhs.proxy_host_;
+ proxy_port_ = rhs.proxy_port_;
+ proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
+ proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
+ proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
+ proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
+#endif
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ ca_cert_file_path_ = rhs.ca_cert_file_path_;
+ ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
+ ca_cert_store_ = rhs.ca_cert_store_;
+#endif
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ server_certificate_verification_ = rhs.server_certificate_verification_;
+#endif
+ logger_ = rhs.logger_;
+}
+
+socket_t ClientImpl::create_client_socket(Error &error) const {
+ if (!proxy_host_.empty() && proxy_port_ != -1) {
+ return detail::create_client_socket(
+ proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
+ socket_options_, connection_timeout_sec_, connection_timeout_usec_,
+ read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+ write_timeout_usec_, interface_, error);
+ }
+
+ // Check is custom IP specified for host_
+ std::string ip;
+ auto it = addr_map_.find(host_);
+ if (it != addr_map_.end()) ip = it->second;
+
+ return detail::create_client_socket(
+ host_, ip, port_, address_family_, tcp_nodelay_, socket_options_,
+ connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_,
+ read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_,
+ error);
+}
+
+bool ClientImpl::create_and_connect_socket(Socket &socket,
+ Error &error) {
+ auto sock = create_client_socket(error);
+ if (sock == INVALID_SOCKET) { return false; }
+ socket.sock = sock;
+ return true;
+}
+
+void ClientImpl::shutdown_ssl(Socket & /*socket*/,
+ bool /*shutdown_gracefully*/) {
+ // If there are any requests in flight from threads other than us, then it's
+ // a thread-unsafe race because individual ssl* objects are not thread-safe.
+ assert(socket_requests_in_flight_ == 0 ||
+ socket_requests_are_from_thread_ == std::this_thread::get_id());
+}
+
+void ClientImpl::shutdown_socket(Socket &socket) {
+ if (socket.sock == INVALID_SOCKET) { return; }
+ detail::shutdown_socket(socket.sock);
+}
+
+void ClientImpl::close_socket(Socket &socket) {
+ // If there are requests in flight in another thread, usually closing
+ // the socket will be fine and they will simply receive an error when
+ // using the closed socket, but it is still a bug since rarely the OS
+ // may reassign the socket id to be used for a new socket, and then
+ // suddenly they will be operating on a live socket that is different
+ // than the one they intended!
+ assert(socket_requests_in_flight_ == 0 ||
+ socket_requests_are_from_thread_ == std::this_thread::get_id());
+
+ // It is also a bug if this happens while SSL is still active
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ assert(socket.ssl == nullptr);
+#endif
+ if (socket.sock == INVALID_SOCKET) { return; }
+ detail::close_socket(socket.sock);
+ socket.sock = INVALID_SOCKET;
+}
+
+bool ClientImpl::read_response_line(Stream &strm, const Request &req,
+ Response &res) {
+ std::array<char, 2048> buf{};
+
+ detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
+
+ if (!line_reader.getline()) { return false; }
+
+#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
+ const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
+#else
+ const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
+#endif
+
+ std::cmatch m;
+ if (!std::regex_match(line_reader.ptr(), m, re)) {
+ return req.method == "CONNECT";
+ }
+ res.version = std::string(m[1]);
+ res.status = std::stoi(std::string(m[2]));
+ res.reason = std::string(m[3]);
+
+ // Ignore '100 Continue'
+ while (res.status == 100) {
+ if (!line_reader.getline()) { return false; } // CRLF
+ if (!line_reader.getline()) { return false; } // next response line
+
+ if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
+ res.version = std::string(m[1]);
+ res.status = std::stoi(std::string(m[2]));
+ res.reason = std::string(m[3]);
+ }
+
+ return true;
+}
+
+bool ClientImpl::send(Request &req, Response &res, Error &error) {
+ std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
+
+ {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+
+ // Set this to false immediately - if it ever gets set to true by the end of
+ // the request, we know another thread instructed us to close the socket.
+ socket_should_be_closed_when_request_is_done_ = false;
+
+ auto is_alive = false;
+ if (socket_.is_open()) {
+ is_alive = detail::is_socket_alive(socket_.sock);
+ if (!is_alive) {
+ // Attempt to avoid sigpipe by shutting down nongracefully if it seems
+ // like the other side has already closed the connection Also, there
+ // cannot be any requests in flight from other threads since we locked
+ // request_mutex_, so safe to close everything immediately
+ const bool shutdown_gracefully = false;
+ shutdown_ssl(socket_, shutdown_gracefully);
+ shutdown_socket(socket_);
+ close_socket(socket_);
+ }
+ }
+
+ if (!is_alive) {
+ if (!create_and_connect_socket(socket_, error)) { return false; }
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ // TODO: refactoring
+ if (is_ssl()) {
+ auto &scli = static_cast<SSLClient &>(*this);
+ if (!proxy_host_.empty() && proxy_port_ != -1) {
+ bool success = false;
+ if (!scli.connect_with_proxy(socket_, res, success, error)) {
+ return success;
+ }
+ }
+
+ if (!scli.initialize_ssl(socket_, error)) { return false; }
+ }
+#endif
+ }
+
+ // Mark the current socket as being in use so that it cannot be closed by
+ // anyone else while this request is ongoing, even though we will be
+ // releasing the mutex.
+ if (socket_requests_in_flight_ > 1) {
+ assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
+ }
+ socket_requests_in_flight_ += 1;
+ socket_requests_are_from_thread_ = std::this_thread::get_id();
+ }
+
+ for (const auto &header : default_headers_) {
+ if (req.headers.find(header.first) == req.headers.end()) {
+ req.headers.insert(header);
+ }
+ }
+
+ auto close_connection = !keep_alive_;
+ auto ret = process_socket(socket_, [&](Stream &strm) {
+ return handle_request(strm, req, res, close_connection, error);
+ });
+
+ // Briefly lock mutex in order to mark that a request is no longer ongoing
+ {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ socket_requests_in_flight_ -= 1;
+ if (socket_requests_in_flight_ <= 0) {
+ assert(socket_requests_in_flight_ == 0);
+ socket_requests_are_from_thread_ = std::thread::id();
+ }
+
+ if (socket_should_be_closed_when_request_is_done_ || close_connection ||
+ !ret) {
+ shutdown_ssl(socket_, true);
+ shutdown_socket(socket_);
+ close_socket(socket_);
+ }
+ }
+
+ if (!ret) {
+ if (error == Error::Success) { error = Error::Unknown; }
+ }
+
+ return ret;
+}
+
+Result ClientImpl::send(const Request &req) {
+ auto req2 = req;
+ return send_(std::move(req2));
+}
+
+Result ClientImpl::send_(Request &&req) {
+ auto res = detail::make_unique<Response>();
+ auto error = Error::Success;
+ auto ret = send(req, *res, error);
+ return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
+}
+
+bool ClientImpl::handle_request(Stream &strm, Request &req,
+ Response &res, bool close_connection,
+ Error &error) {
+ if (req.path.empty()) {
+ error = Error::Connection;
+ return false;
+ }
+
+ auto req_save = req;
+
+ bool ret;
+
+ if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
+ auto req2 = req;
+ req2.path = "http://" + host_and_port_ + req.path;
+ ret = process_request(strm, req2, res, close_connection, error);
+ req = req2;
+ req.path = req_save.path;
+ } else {
+ ret = process_request(strm, req, res, close_connection, error);
+ }
+
+ if (!ret) { return false; }
+
+ if (300 < res.status && res.status < 400 && follow_location_) {
+ req = req_save;
+ ret = redirect(req, res, error);
+ }
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ if ((res.status == 401 || res.status == 407) &&
+ req.authorization_count_ < 5) {
+ auto is_proxy = res.status == 407;
+ const auto &username =
+ is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
+ const auto &password =
+ is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
+
+ if (!username.empty() && !password.empty()) {
+ std::map<std::string, std::string> auth;
+ if (detail::parse_www_authenticate(res, auth, is_proxy)) {
+ Request new_req = req;
+ new_req.authorization_count_ += 1;
+ new_req.headers.erase(is_proxy ? "Proxy-Authorization"
+ : "Authorization");
+ new_req.headers.insert(detail::make_digest_authentication_header(
+ req, auth, new_req.authorization_count_, detail::random_string(10),
+ username, password, is_proxy));
+
+ Response new_res;
+
+ ret = send(new_req, new_res, error);
+ if (ret) { res = new_res; }
+ }
+ }
+ }
+#endif
+
+ return ret;
+}
+
+bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
+ if (req.redirect_count_ == 0) {
+ error = Error::ExceedRedirectCount;
+ return false;
+ }
+
+ auto location = detail::decode_url(res.get_header_value("location"), true);
+ if (location.empty()) { return false; }
+
+ const static std::regex re(
+ R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
+
+ std::smatch m;
+ if (!std::regex_match(location, m, re)) { return false; }
+
+ auto scheme = is_ssl() ? "https" : "http";
+
+ auto next_scheme = m[1].str();
+ auto next_host = m[2].str();
+ if (next_host.empty()) { next_host = m[3].str(); }
+ auto port_str = m[4].str();
+ auto next_path = m[5].str();
+
+ auto next_port = port_;
+ if (!port_str.empty()) {
+ next_port = std::stoi(port_str);
+ } else if (!next_scheme.empty()) {
+ next_port = next_scheme == "https" ? 443 : 80;
+ }
+
+ if (next_scheme.empty()) { next_scheme = scheme; }
+ if (next_host.empty()) { next_host = host_; }
+ if (next_path.empty()) { next_path = "/"; }
+
+ if (next_scheme == scheme && next_host == host_ && next_port == port_) {
+ return detail::redirect(*this, req, res, next_path, location, error);
+ } else {
+ if (next_scheme == "https") {
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ SSLClient cli(next_host.c_str(), next_port);
+ cli.copy_settings(*this);
+ if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); }
+ return detail::redirect(cli, req, res, next_path, location, error);
+#else
+ return false;
+#endif
+ } else {
+ ClientImpl cli(next_host.c_str(), next_port);
+ cli.copy_settings(*this);
+ return detail::redirect(cli, req, res, next_path, location, error);
+ }
+ }
+}
+
+bool ClientImpl::write_content_with_provider(Stream &strm,
+ const Request &req,
+ Error &error) {
+ auto is_shutting_down = []() { return false; };
+
+ if (req.is_chunked_content_provider_) {
+ // TODO: Brotli suport
+ std::unique_ptr<detail::compressor> compressor;
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ if (compress_) {
+ compressor = detail::make_unique<detail::gzip_compressor>();
+ } else
+#endif
+ {
+ compressor = detail::make_unique<detail::nocompressor>();
+ }
+
+ return detail::write_content_chunked(strm, req.content_provider_,
+ is_shutting_down, *compressor, error);
+ } else {
+ return detail::write_content(strm, req.content_provider_, 0,
+ req.content_length_, is_shutting_down, error);
+ }
+} // namespace httplib
+
+bool ClientImpl::write_request(Stream &strm, Request &req,
+ bool close_connection, Error &error) {
+ // Prepare additional headers
+ if (close_connection) {
+ if (!req.has_header("Connection")) {
+ req.headers.emplace("Connection", "close");
+ }
+ }
+
+ if (!req.has_header("Host")) {
+ if (is_ssl()) {
+ if (port_ == 443) {
+ req.headers.emplace("Host", host_);
+ } else {
+ req.headers.emplace("Host", host_and_port_);
+ }
+ } else {
+ if (port_ == 80) {
+ req.headers.emplace("Host", host_);
+ } else {
+ req.headers.emplace("Host", host_and_port_);
+ }
+ }
+ }
+
+ if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); }
+
+#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
+ if (!req.has_header("User-Agent")) {
+ auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
+ req.headers.emplace("User-Agent", agent);
+ }
+#endif
+
+ if (req.body.empty()) {
+ if (req.content_provider_) {
+ if (!req.is_chunked_content_provider_) {
+ if (!req.has_header("Content-Length")) {
+ auto length = std::to_string(req.content_length_);
+ req.headers.emplace("Content-Length", length);
+ }
+ }
+ } else {
+ if (req.method == "POST" || req.method == "PUT" ||
+ req.method == "PATCH") {
+ req.headers.emplace("Content-Length", "0");
+ }
+ }
+ } else {
+ if (!req.has_header("Content-Type")) {
+ req.headers.emplace("Content-Type", "text/plain");
+ }
+
+ if (!req.has_header("Content-Length")) {
+ auto length = std::to_string(req.body.size());
+ req.headers.emplace("Content-Length", length);
+ }
+ }
+
+ if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
+ if (!req.has_header("Authorization")) {
+ req.headers.insert(make_basic_authentication_header(
+ basic_auth_username_, basic_auth_password_, false));
+ }
+ }
+
+ if (!proxy_basic_auth_username_.empty() &&
+ !proxy_basic_auth_password_.empty()) {
+ if (!req.has_header("Proxy-Authorization")) {
+ req.headers.insert(make_basic_authentication_header(
+ proxy_basic_auth_username_, proxy_basic_auth_password_, true));
+ }
+ }
+
+ if (!bearer_token_auth_token_.empty()) {
+ if (!req.has_header("Authorization")) {
+ req.headers.insert(make_bearer_token_authentication_header(
+ bearer_token_auth_token_, false));
+ }
+ }
+
+ if (!proxy_bearer_token_auth_token_.empty()) {
+ if (!req.has_header("Proxy-Authorization")) {
+ req.headers.insert(make_bearer_token_authentication_header(
+ proxy_bearer_token_auth_token_, true));
+ }
+ }
+
+ // Request line and headers
+ {
+ detail::BufferStream bstrm;
+
+ const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path;
+ bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
+
+ detail::write_headers(bstrm, req.headers);
+
+ // Flush buffer
+ auto &data = bstrm.get_buffer();
+ if (!detail::write_data(strm, data.data(), data.size())) {
+ error = Error::Write;
+ return false;
+ }
+ }
+
+ // Body
+ if (req.body.empty()) {
+ return write_content_with_provider(strm, req, error);
+ }
+
+ if (!detail::write_data(strm, req.body.data(), req.body.size())) {
+ error = Error::Write;
+ return false;
+ }
+
+ return true;
+}
+
+std::unique_ptr<Response> ClientImpl::send_with_content_provider(
+ Request &req, const char *body, size_t content_length,
+ ContentProvider content_provider,
+ ContentProviderWithoutLength content_provider_without_length,
+ const std::string &content_type, Error &error) {
+ if (!content_type.empty()) {
+ req.headers.emplace("Content-Type", content_type);
+ }
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); }
+#endif
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ if (compress_ && !content_provider_without_length) {
+ // TODO: Brotli support
+ detail::gzip_compressor compressor;
+
+ if (content_provider) {
+ auto ok = true;
+ size_t offset = 0;
+ DataSink data_sink;
+
+ data_sink.write = [&](const char *data, size_t data_len) -> bool {
+ if (ok) {
+ auto last = offset + data_len == content_length;
+
+ auto ret = compressor.compress(
+ data, data_len, last,
+ [&](const char *compressed_data, size_t compressed_data_len) {
+ req.body.append(compressed_data, compressed_data_len);
+ return true;
+ });
+
+ if (ret) {
+ offset += data_len;
+ } else {
+ ok = false;
+ }
+ }
+ return ok;
+ };
+
+ data_sink.is_writable = [&](void) { return ok && true; };
+
+ while (ok && offset < content_length) {
+ if (!content_provider(offset, content_length - offset, data_sink)) {
+ error = Error::Canceled;
+ return nullptr;
+ }
+ }
+ } else {
+ if (!compressor.compress(body, content_length, true,
+ [&](const char *data, size_t data_len) {
+ req.body.append(data, data_len);
+ return true;
+ })) {
+ error = Error::Compression;
+ return nullptr;
+ }
+ }
+ } else
+#endif
+ {
+ if (content_provider) {
+ req.content_length_ = content_length;
+ req.content_provider_ = std::move(content_provider);
+ req.is_chunked_content_provider_ = false;
+ } else if (content_provider_without_length) {
+ req.content_length_ = 0;
+ req.content_provider_ = detail::ContentProviderAdapter(
+ std::move(content_provider_without_length));
+ req.is_chunked_content_provider_ = true;
+ req.headers.emplace("Transfer-Encoding", "chunked");
+ } else {
+ req.body.assign(body, content_length);
+ ;
+ }
+ }
+
+ auto res = detail::make_unique<Response>();
+ return send(req, *res, error) ? std::move(res) : nullptr;
+}
+
+Result ClientImpl::send_with_content_provider(
+ const std::string &method, const std::string &path, const Headers &headers,
+ const char *body, size_t content_length, ContentProvider content_provider,
+ ContentProviderWithoutLength content_provider_without_length,
+ const std::string &content_type) {
+ Request req;
+ req.method = method;
+ req.headers = headers;
+ req.path = path;
+
+ auto error = Error::Success;
+
+ auto res = send_with_content_provider(
+ req, body, content_length, std::move(content_provider),
+ std::move(content_provider_without_length), content_type, error);
+
+ return Result{std::move(res), error, std::move(req.headers)};
+}
+
+std::string
+ClientImpl::adjust_host_string(const std::string &host) const {
+ if (host.find(':') != std::string::npos) { return "[" + host + "]"; }
+ return host;
+}
+
+bool ClientImpl::process_request(Stream &strm, Request &req,
+ Response &res, bool close_connection,
+ Error &error) {
+ // Send request
+ if (!write_request(strm, req, close_connection, error)) { return false; }
+
+ // Receive response and headers
+ if (!read_response_line(strm, req, res) ||
+ !detail::read_headers(strm, res.headers)) {
+ error = Error::Read;
+ return false;
+ }
+
+ // Body
+ if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") {
+ auto redirect = 300 < res.status && res.status < 400 && follow_location_;
+
+ if (req.response_handler && !redirect) {
+ if (!req.response_handler(res)) {
+ error = Error::Canceled;
+ return false;
+ }
+ }
+
+ auto out =
+ req.content_receiver
+ ? static_cast<ContentReceiverWithProgress>(
+ [&](const char *buf, size_t n, uint64_t off, uint64_t len) {
+ if (redirect) { return true; }
+ auto ret = req.content_receiver(buf, n, off, len);
+ if (!ret) { error = Error::Canceled; }
+ return ret;
+ })
+ : static_cast<ContentReceiverWithProgress>(
+ [&](const char *buf, size_t n, uint64_t /*off*/,
+ uint64_t /*len*/) {
+ if (res.body.size() + n > res.body.max_size()) {
+ return false;
+ }
+ res.body.append(buf, n);
+ return true;
+ });
+
+ auto progress = [&](uint64_t current, uint64_t total) {
+ if (!req.progress || redirect) { return true; }
+ auto ret = req.progress(current, total);
+ if (!ret) { error = Error::Canceled; }
+ return ret;
+ };
+
+ int dummy_status;
+ if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
+ dummy_status, std::move(progress), std::move(out),
+ decompress_)) {
+ if (error != Error::Canceled) { error = Error::Read; }
+ return false;
+ }
+ }
+
+ if (res.get_header_value("Connection") == "close" ||
+ (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
+ // TODO this requires a not-entirely-obvious chain of calls to be correct
+ // for this to be safe. Maybe a code refactor (such as moving this out to
+ // the send function and getting rid of the recursiveness of the mutex)
+ // could make this more obvious.
+
+ // This is safe to call because process_request is only called by
+ // handle_request which is only called by send, which locks the request
+ // mutex during the process. It would be a bug to call it from a different
+ // thread since it's a thread-safety issue to do these things to the socket
+ // if another thread is using the socket.
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ shutdown_ssl(socket_, true);
+ shutdown_socket(socket_);
+ close_socket(socket_);
+ }
+
+ // Log
+ if (logger_) { logger_(req, res); }
+
+ return true;
+}
+
+bool
+ClientImpl::process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback) {
+ return detail::process_client_socket(
+ socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+ write_timeout_usec_, std::move(callback));
+}
+
+bool ClientImpl::is_ssl() const { return false; }
+
+Result ClientImpl::Get(const std::string &path) {
+ return Get(path, Headers(), Progress());
+}
+
+Result ClientImpl::Get(const std::string &path, Progress progress) {
+ return Get(path, Headers(), std::move(progress));
+}
+
+Result ClientImpl::Get(const std::string &path, const Headers &headers) {
+ return Get(path, headers, Progress());
+}
+
+Result ClientImpl::Get(const std::string &path, const Headers &headers,
+ Progress progress) {
+ Request req;
+ req.method = "GET";
+ req.path = path;
+ req.headers = headers;
+ req.progress = std::move(progress);
+
+ return send_(std::move(req));
+}
+
+Result ClientImpl::Get(const std::string &path,
+ ContentReceiver content_receiver) {
+ return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr);
+}
+
+Result ClientImpl::Get(const std::string &path,
+ ContentReceiver content_receiver,
+ Progress progress) {
+ return Get(path, Headers(), nullptr, std::move(content_receiver),
+ std::move(progress));
+}
+
+Result ClientImpl::Get(const std::string &path, const Headers &headers,
+ ContentReceiver content_receiver) {
+ return Get(path, headers, nullptr, std::move(content_receiver), nullptr);
+}
+
+Result ClientImpl::Get(const std::string &path, const Headers &headers,
+ ContentReceiver content_receiver,
+ Progress progress) {
+ return Get(path, headers, nullptr, std::move(content_receiver),
+ std::move(progress));
+}
+
+Result ClientImpl::Get(const std::string &path,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return Get(path, Headers(), std::move(response_handler),
+ std::move(content_receiver), nullptr);
+}
+
+Result ClientImpl::Get(const std::string &path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return Get(path, headers, std::move(response_handler),
+ std::move(content_receiver), nullptr);
+}
+
+Result ClientImpl::Get(const std::string &path,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver,
+ Progress progress) {
+ return Get(path, Headers(), std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
+}
+
+Result ClientImpl::Get(const std::string &path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver,
+ Progress progress) {
+ Request req;
+ req.method = "GET";
+ req.path = path;
+ req.headers = headers;
+ req.response_handler = std::move(response_handler);
+ req.content_receiver =
+ [content_receiver](const char *data, size_t data_length,
+ uint64_t /*offset*/, uint64_t /*total_length*/) {
+ return content_receiver(data, data_length);
+ };
+ req.progress = std::move(progress);
+
+ return send_(std::move(req));
+}
+
+Result ClientImpl::Get(const std::string &path, const Params ¶ms,
+ const Headers &headers, Progress progress) {
+ if (params.empty()) { return Get(path, headers); }
+
+ std::string path_with_query = append_query_params(path, params);
+ return Get(path_with_query.c_str(), headers, progress);
+}
+
+Result ClientImpl::Get(const std::string &path, const Params ¶ms,
+ const Headers &headers,
+ ContentReceiver content_receiver,
+ Progress progress) {
+ return Get(path, params, headers, nullptr, content_receiver, progress);
+}
+
+Result ClientImpl::Get(const std::string &path, const Params ¶ms,
+ const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver,
+ Progress progress) {
+ if (params.empty()) {
+ return Get(path, headers, response_handler, content_receiver, progress);
+ }
+
+ std::string path_with_query = append_query_params(path, params);
+ return Get(path_with_query.c_str(), headers, response_handler,
+ content_receiver, progress);
+}
+
+Result ClientImpl::Head(const std::string &path) {
+ return Head(path, Headers());
+}
+
+Result ClientImpl::Head(const std::string &path,
+ const Headers &headers) {
+ Request req;
+ req.method = "HEAD";
+ req.headers = headers;
+ req.path = path;
+
+ return send_(std::move(req));
+}
+
+Result ClientImpl::Post(const std::string &path) {
+ return Post(path, std::string(), std::string());
+}
+
+Result ClientImpl::Post(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type) {
+ return Post(path, Headers(), body, content_length, content_type);
+}
+
+Result ClientImpl::Post(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type) {
+ return send_with_content_provider("POST", path, headers, body, content_length,
+ nullptr, nullptr, content_type);
+}
+
+Result ClientImpl::Post(const std::string &path, const std::string &body,
+ const std::string &content_type) {
+ return Post(path, Headers(), body, content_type);
+}
+
+Result ClientImpl::Post(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type) {
+ return send_with_content_provider("POST", path, headers, body.data(),
+ body.size(), nullptr, nullptr,
+ content_type);
+}
+
+Result ClientImpl::Post(const std::string &path, const Params ¶ms) {
+ return Post(path, Headers(), params);
+}
+
+Result ClientImpl::Post(const std::string &path, size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return Post(path, Headers(), content_length, std::move(content_provider),
+ content_type);
+}
+
+Result ClientImpl::Post(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return Post(path, Headers(), std::move(content_provider), content_type);
+}
+
+Result ClientImpl::Post(const std::string &path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return send_with_content_provider("POST", path, headers, nullptr,
+ content_length, std::move(content_provider),
+ nullptr, content_type);
+}
+
+Result ClientImpl::Post(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr,
+ std::move(content_provider), content_type);
+}
+
+Result ClientImpl::Post(const std::string &path, const Headers &headers,
+ const Params ¶ms) {
+ auto query = detail::params_to_query_str(params);
+ return Post(path, headers, query, "application/x-www-form-urlencoded");
+}
+
+Result ClientImpl::Post(const std::string &path,
+ const MultipartFormDataItems &items) {
+ return Post(path, Headers(), items);
+}
+
+Result ClientImpl::Post(const std::string &path, const Headers &headers,
+ const MultipartFormDataItems &items) {
+ return Post(path, headers, items, detail::make_multipart_data_boundary());
+}
+Result ClientImpl::Post(const std::string &path, const Headers &headers,
+ const MultipartFormDataItems &items,
+ const std::string &boundary) {
+ for (size_t i = 0; i < boundary.size(); i++) {
+ char c = boundary[i];
+ if (!std::isalnum(c) && c != '-' && c != '_') {
+ return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
+ }
+ }
+
+ std::string body;
+
+ for (const auto &item : items) {
+ body += "--" + boundary + "\r\n";
+ body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
+ if (!item.filename.empty()) {
+ body += "; filename=\"" + item.filename + "\"";
+ }
+ body += "\r\n";
+ if (!item.content_type.empty()) {
+ body += "Content-Type: " + item.content_type + "\r\n";
+ }
+ body += "\r\n";
+ body += item.content + "\r\n";
+ }
+
+ body += "--" + boundary + "--\r\n";
+
+ std::string content_type = "multipart/form-data; boundary=" + boundary;
+ return Post(path, headers, body, content_type.c_str());
+}
+
+Result ClientImpl::Put(const std::string &path) {
+ return Put(path, std::string(), std::string());
+}
+
+Result ClientImpl::Put(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type) {
+ return Put(path, Headers(), body, content_length, content_type);
+}
+
+Result ClientImpl::Put(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type) {
+ return send_with_content_provider("PUT", path, headers, body, content_length,
+ nullptr, nullptr, content_type);
+}
+
+Result ClientImpl::Put(const std::string &path, const std::string &body,
+ const std::string &content_type) {
+ return Put(path, Headers(), body, content_type);
+}
+
+Result ClientImpl::Put(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type) {
+ return send_with_content_provider("PUT", path, headers, body.data(),
+ body.size(), nullptr, nullptr,
+ content_type);
+}
+
+Result ClientImpl::Put(const std::string &path, size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return Put(path, Headers(), content_length, std::move(content_provider),
+ content_type);
+}
+
+Result ClientImpl::Put(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return Put(path, Headers(), std::move(content_provider), content_type);
+}
+
+Result ClientImpl::Put(const std::string &path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return send_with_content_provider("PUT", path, headers, nullptr,
+ content_length, std::move(content_provider),
+ nullptr, content_type);
+}
+
+Result ClientImpl::Put(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr,
+ std::move(content_provider), content_type);
+}
+
+Result ClientImpl::Put(const std::string &path, const Params ¶ms) {
+ return Put(path, Headers(), params);
+}
+
+Result ClientImpl::Put(const std::string &path, const Headers &headers,
+ const Params ¶ms) {
+ auto query = detail::params_to_query_str(params);
+ return Put(path, headers, query, "application/x-www-form-urlencoded");
+}
+
+Result ClientImpl::Patch(const std::string &path) {
+ return Patch(path, std::string(), std::string());
+}
+
+Result ClientImpl::Patch(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type) {
+ return Patch(path, Headers(), body, content_length, content_type);
+}
+
+Result ClientImpl::Patch(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type) {
+ return send_with_content_provider("PATCH", path, headers, body,
+ content_length, nullptr, nullptr,
+ content_type);
+}
+
+Result ClientImpl::Patch(const std::string &path,
+ const std::string &body,
+ const std::string &content_type) {
+ return Patch(path, Headers(), body, content_type);
+}
+
+Result ClientImpl::Patch(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type) {
+ return send_with_content_provider("PATCH", path, headers, body.data(),
+ body.size(), nullptr, nullptr,
+ content_type);
+}
+
+Result ClientImpl::Patch(const std::string &path, size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return Patch(path, Headers(), content_length, std::move(content_provider),
+ content_type);
+}
+
+Result ClientImpl::Patch(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return Patch(path, Headers(), std::move(content_provider), content_type);
+}
+
+Result ClientImpl::Patch(const std::string &path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return send_with_content_provider("PATCH", path, headers, nullptr,
+ content_length, std::move(content_provider),
+ nullptr, content_type);
+}
+
+Result ClientImpl::Patch(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr,
+ std::move(content_provider), content_type);
+}
+
+Result ClientImpl::Delete(const std::string &path) {
+ return Delete(path, Headers(), std::string(), std::string());
+}
+
+Result ClientImpl::Delete(const std::string &path,
+ const Headers &headers) {
+ return Delete(path, headers, std::string(), std::string());
+}
+
+Result ClientImpl::Delete(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type) {
+ return Delete(path, Headers(), body, content_length, content_type);
+}
+
+Result ClientImpl::Delete(const std::string &path,
+ const Headers &headers, const char *body,
+ size_t content_length,
+ const std::string &content_type) {
+ Request req;
+ req.method = "DELETE";
+ req.headers = headers;
+ req.path = path;
+
+ if (!content_type.empty()) {
+ req.headers.emplace("Content-Type", content_type);
+ }
+ req.body.assign(body, content_length);
+
+ return send_(std::move(req));
+}
+
+Result ClientImpl::Delete(const std::string &path,
+ const std::string &body,
+ const std::string &content_type) {
+ return Delete(path, Headers(), body.data(), body.size(), content_type);
+}
+
+Result ClientImpl::Delete(const std::string &path,
+ const Headers &headers,
+ const std::string &body,
+ const std::string &content_type) {
+ return Delete(path, headers, body.data(), body.size(), content_type);
+}
+
+Result ClientImpl::Options(const std::string &path) {
+ return Options(path, Headers());
+}
+
+Result ClientImpl::Options(const std::string &path,
+ const Headers &headers) {
+ Request req;
+ req.method = "OPTIONS";
+ req.headers = headers;
+ req.path = path;
+
+ return send_(std::move(req));
+}
+
+size_t ClientImpl::is_socket_open() const {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ return socket_.is_open();
+}
+
+socket_t ClientImpl::socket() const {
+ return socket_.sock;
+}
+
+void ClientImpl::stop() {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+
+ // If there is anything ongoing right now, the ONLY thread-safe thing we can
+ // do is to shutdown_socket, so that threads using this socket suddenly
+ // discover they can't read/write any more and error out. Everything else
+ // (closing the socket, shutting ssl down) is unsafe because these actions are
+ // not thread-safe.
+ if (socket_requests_in_flight_ > 0) {
+ shutdown_socket(socket_);
+
+ // Aside from that, we set a flag for the socket to be closed when we're
+ // done.
+ socket_should_be_closed_when_request_is_done_ = true;
+ return;
+ }
+
+ // Otherwise, sitll holding the mutex, we can shut everything down ourselves
+ shutdown_ssl(socket_, true);
+ shutdown_socket(socket_);
+ close_socket(socket_);
+}
+
+void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
+ connection_timeout_sec_ = sec;
+ connection_timeout_usec_ = usec;
+}
+
+void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
+ read_timeout_sec_ = sec;
+ read_timeout_usec_ = usec;
+}
+
+void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
+ write_timeout_sec_ = sec;
+ write_timeout_usec_ = usec;
+}
+
+void ClientImpl::set_basic_auth(const std::string &username,
+ const std::string &password) {
+ basic_auth_username_ = username;
+ basic_auth_password_ = password;
+}
+
+void ClientImpl::set_bearer_token_auth(const std::string &token) {
+ bearer_token_auth_token_ = token;
+}
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+void ClientImpl::set_digest_auth(const std::string &username,
+ const std::string &password) {
+ digest_auth_username_ = username;
+ digest_auth_password_ = password;
+}
+#endif
+
+void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
+
+void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
+
+void ClientImpl::set_url_encode(bool on) { url_encode_ = on; }
+
+void
+ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
+ addr_map_ = std::move(addr_map);
+}
+
+void ClientImpl::set_default_headers(Headers headers) {
+ default_headers_ = std::move(headers);
+}
+
+void ClientImpl::set_address_family(int family) {
+ address_family_ = family;
+}
+
+void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
+
+void ClientImpl::set_socket_options(SocketOptions socket_options) {
+ socket_options_ = std::move(socket_options);
+}
+
+void ClientImpl::set_compress(bool on) { compress_ = on; }
+
+void ClientImpl::set_decompress(bool on) { decompress_ = on; }
+
+void ClientImpl::set_interface(const std::string &intf) {
+ interface_ = intf;
+}
+
+void ClientImpl::set_proxy(const std::string &host, int port) {
+ proxy_host_ = host;
+ proxy_port_ = port;
+}
+
+void ClientImpl::set_proxy_basic_auth(const std::string &username,
+ const std::string &password) {
+ proxy_basic_auth_username_ = username;
+ proxy_basic_auth_password_ = password;
+}
+
+void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) {
+ proxy_bearer_token_auth_token_ = token;
+}
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+void ClientImpl::set_proxy_digest_auth(const std::string &username,
+ const std::string &password) {
+ proxy_digest_auth_username_ = username;
+ proxy_digest_auth_password_ = password;
+}
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
+ const std::string &ca_cert_dir_path) {
+ ca_cert_file_path_ = ca_cert_file_path;
+ ca_cert_dir_path_ = ca_cert_dir_path;
+}
+
+void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
+ if (ca_cert_store && ca_cert_store != ca_cert_store_) {
+ ca_cert_store_ = ca_cert_store;
+ }
+}
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+void ClientImpl::enable_server_certificate_verification(bool enabled) {
+ server_certificate_verification_ = enabled;
+}
+#endif
+
+void ClientImpl::set_logger(Logger logger) {
+ logger_ = std::move(logger);
+}
+
+/*
+ * SSL Implementation
+ */
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+namespace detail {
+
+template <typename U, typename V>
+SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
+ U SSL_connect_or_accept, V setup) {
+ SSL *ssl = nullptr;
+ {
+ std::lock_guard<std::mutex> guard(ctx_mutex);
+ ssl = SSL_new(ctx);
+ }
+
+ if (ssl) {
+ set_nonblocking(sock, true);
+ auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
+ BIO_set_nbio(bio, 1);
+ SSL_set_bio(ssl, bio, bio);
+
+ if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) {
+ SSL_shutdown(ssl);
+ {
+ std::lock_guard<std::mutex> guard(ctx_mutex);
+ SSL_free(ssl);
+ }
+ set_nonblocking(sock, false);
+ return nullptr;
+ }
+ BIO_set_nbio(bio, 0);
+ set_nonblocking(sock, false);
+ }
+
+ return ssl;
+}
+
+void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
+ bool shutdown_gracefully) {
+ // sometimes we may want to skip this to try to avoid SIGPIPE if we know
+ // the remote has closed the network connection
+ // Note that it is not always possible to avoid SIGPIPE, this is merely a
+ // best-efforts.
+ if (shutdown_gracefully) { SSL_shutdown(ssl); }
+
+ std::lock_guard<std::mutex> guard(ctx_mutex);
+ SSL_free(ssl);
+}
+
+template <typename U>
+bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
+ U ssl_connect_or_accept,
+ time_t timeout_sec,
+ time_t timeout_usec) {
+ int res = 0;
+ while ((res = ssl_connect_or_accept(ssl)) != 1) {
+ auto err = SSL_get_error(ssl, res);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; }
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; }
+ break;
+ default: break;
+ }
+ return false;
+ }
+ return true;
+}
+
+template <typename T>
+bool process_server_socket_ssl(
+ const std::atomic<socket_t> &svr_sock, SSL *ssl, socket_t sock,
+ size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
+ time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, T callback) {
+ return process_server_socket_core(
+ svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
+ [&](bool close_connection, bool &connection_closed) {
+ SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm, close_connection, connection_closed);
+ });
+}
+
+template <typename T>
+bool
+process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, T callback) {
+ SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm);
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static std::shared_ptr<std::vector<std::mutex>> openSSL_locks_;
+
+class SSLThreadLocks {
+public:
+ SSLThreadLocks() {
+ openSSL_locks_ =
+ std::make_shared<std::vector<std::mutex>>(CRYPTO_num_locks());
+ CRYPTO_set_locking_callback(locking_callback);
+ }
+
+ ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); }
+
+private:
+ static void locking_callback(int mode, int type, const char * /*file*/,
+ int /*line*/) {
+ auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
+ if (mode & CRYPTO_LOCK) {
+ lk.lock();
+ } else {
+ lk.unlock();
+ }
+ }
+};
+
+#endif
+
+class SSLInit {
+public:
+ SSLInit() {
+#if OPENSSL_VERSION_NUMBER < 0x1010001fL
+ SSL_load_error_strings();
+ SSL_library_init();
+#else
+ OPENSSL_init_ssl(
+ OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
+#endif
+ }
+
+ ~SSLInit() {
+#if OPENSSL_VERSION_NUMBER < 0x1010001fL
+ ERR_free_strings();
+#endif
+ }
+
+private:
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ SSLThreadLocks thread_init_;
+#endif
+};
+
+// SSL socket stream implementation
+SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
+ time_t read_timeout_sec,
+ time_t read_timeout_usec,
+ time_t write_timeout_sec,
+ time_t write_timeout_usec)
+ : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
+ read_timeout_usec_(read_timeout_usec),
+ write_timeout_sec_(write_timeout_sec),
+ write_timeout_usec_(write_timeout_usec) {
+ SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
+}
+
+SSLSocketStream::~SSLSocketStream() {}
+
+bool SSLSocketStream::is_readable() const {
+ return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+}
+
+bool SSLSocketStream::is_writable() const {
+ return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) >
+ 0;
+}
+
+ssize_t SSLSocketStream::read(char *ptr, size_t size) {
+ if (SSL_pending(ssl_) > 0) {
+ return SSL_read(ssl_, ptr, static_cast<int>(size));
+ } else if (is_readable()) {
+ auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
+ if (ret < 0) {
+ auto err = SSL_get_error(ssl_, ret);
+ int n = 1000;
+#ifdef _WIN32
+ while (--n >= 0 && (err == SSL_ERROR_WANT_READ ||
+ (err == SSL_ERROR_SYSCALL &&
+ WSAGetLastError() == WSAETIMEDOUT))) {
+#else
+ while (--n >= 0 && err == SSL_ERROR_WANT_READ) {
+#endif
+ if (SSL_pending(ssl_) > 0) {
+ return SSL_read(ssl_, ptr, static_cast<int>(size));
+ } else if (is_readable()) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ ret = SSL_read(ssl_, ptr, static_cast<int>(size));
+ if (ret >= 0) { return ret; }
+ err = SSL_get_error(ssl_, ret);
+ } else {
+ return -1;
+ }
+ }
+ }
+ return ret;
+ }
+ return -1;
+}
+
+ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
+ if (is_writable()) {
+ auto handle_size = static_cast<int>(
+ std::min<size_t>(size, (std::numeric_limits<int>::max)()));
+
+ auto ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
+ if (ret < 0) {
+ auto err = SSL_get_error(ssl_, ret);
+ int n = 1000;
+#ifdef _WIN32
+ while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE ||
+ (err == SSL_ERROR_SYSCALL &&
+ WSAGetLastError() == WSAETIMEDOUT))) {
+#else
+ while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
+#endif
+ if (is_writable()) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
+ if (ret >= 0) { return ret; }
+ err = SSL_get_error(ssl_, ret);
+ } else {
+ return -1;
+ }
+ }
+ }
+ return ret;
+ }
+ return -1;
+}
+
+void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
+ int &port) const {
+ detail::get_remote_ip_and_port(sock_, ip, port);
+}
+
+socket_t SSLSocketStream::socket() const { return sock_; }
+
+static SSLInit sslinit_;
+
+} // namespace detail
+
+// SSL HTTP server implementation
+SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
+ const char *client_ca_cert_file_path,
+ const char *client_ca_cert_dir_path,
+ const char *private_key_password) {
+ ctx_ = SSL_CTX_new(TLS_server_method());
+
+ if (ctx_) {
+ SSL_CTX_set_options(ctx_,
+ SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+ SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
+
+ // add default password callback before opening encrypted private key
+ if (private_key_password != nullptr && (private_key_password[0] != '\0')) {
+ SSL_CTX_set_default_passwd_cb_userdata(ctx_,
+ (char *)private_key_password);
+ }
+
+ if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
+ SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
+ 1) {
+ SSL_CTX_free(ctx_);
+ ctx_ = nullptr;
+ } else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
+ SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path,
+ client_ca_cert_dir_path);
+
+ SSL_CTX_set_verify(
+ ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
+ }
+ }
+}
+
+SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
+ X509_STORE *client_ca_cert_store) {
+ ctx_ = SSL_CTX_new(TLS_server_method());
+
+ if (ctx_) {
+ SSL_CTX_set_options(ctx_,
+ SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+ SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
+
+ if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
+ SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
+ SSL_CTX_free(ctx_);
+ ctx_ = nullptr;
+ } else if (client_ca_cert_store) {
+ SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
+
+ SSL_CTX_set_verify(
+ ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
+ }
+ }
+}
+
+SSLServer::SSLServer(
+ const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback) {
+ ctx_ = SSL_CTX_new(TLS_method());
+ if (ctx_) {
+ if (!setup_ssl_ctx_callback(*ctx_)) {
+ SSL_CTX_free(ctx_);
+ ctx_ = nullptr;
+ }
+ }
+}
+
+SSLServer::~SSLServer() {
+ if (ctx_) { SSL_CTX_free(ctx_); }
+}
+
+bool SSLServer::is_valid() const { return ctx_; }
+
+SSL_CTX *SSLServer::ssl_context() const { return ctx_; }
+
+bool SSLServer::process_and_close_socket(socket_t sock) {
+ auto ssl = detail::ssl_new(
+ sock, ctx_, ctx_mutex_,
+ [&](SSL *ssl2) {
+ return detail::ssl_connect_or_accept_nonblocking(
+ sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_);
+ },
+ [](SSL * /*ssl2*/) { return true; });
+
+ bool ret = false;
+ if (ssl) {
+ ret = detail::process_server_socket_ssl(
+ svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
+ read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+ write_timeout_usec_,
+ [this, ssl](Stream &strm, bool close_connection,
+ bool &connection_closed) {
+ return process_request(strm, close_connection, connection_closed,
+ [&](Request &req) { req.ssl = ssl; });
+ });
+
+ // Shutdown gracefully if the result seemed successful, non-gracefully if
+ // the connection appeared to be closed.
+ const bool shutdown_gracefully = ret;
+ detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully);
+ }
+
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ return ret;
+}
+
+// SSL HTTP client implementation
+SSLClient::SSLClient(const std::string &host)
+ : SSLClient(host, 443, std::string(), std::string()) {}
+
+SSLClient::SSLClient(const std::string &host, int port)
+ : SSLClient(host, port, std::string(), std::string()) {}
+
+SSLClient::SSLClient(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path)
+ : ClientImpl(host, port, client_cert_path, client_key_path) {
+ ctx_ = SSL_CTX_new(TLS_client_method());
+
+ detail::split(&host_[0], &host_[host_.size()], '.',
+ [&](const char *b, const char *e) {
+ host_components_.emplace_back(std::string(b, e));
+ });
+
+ if (!client_cert_path.empty() && !client_key_path.empty()) {
+ if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(),
+ SSL_FILETYPE_PEM) != 1 ||
+ SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(),
+ SSL_FILETYPE_PEM) != 1) {
+ SSL_CTX_free(ctx_);
+ ctx_ = nullptr;
+ }
+ }
+}
+
+SSLClient::SSLClient(const std::string &host, int port,
+ X509 *client_cert, EVP_PKEY *client_key)
+ : ClientImpl(host, port) {
+ ctx_ = SSL_CTX_new(TLS_client_method());
+
+ detail::split(&host_[0], &host_[host_.size()], '.',
+ [&](const char *b, const char *e) {
+ host_components_.emplace_back(std::string(b, e));
+ });
+
+ if (client_cert != nullptr && client_key != nullptr) {
+ if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
+ SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
+ SSL_CTX_free(ctx_);
+ ctx_ = nullptr;
+ }
+ }
+}
+
+SSLClient::~SSLClient() {
+ if (ctx_) { SSL_CTX_free(ctx_); }
+ // Make sure to shut down SSL since shutdown_ssl will resolve to the
+ // base function rather than the derived function once we get to the
+ // base class destructor, and won't free the SSL (causing a leak).
+ shutdown_ssl_impl(socket_, true);
+}
+
+bool SSLClient::is_valid() const { return ctx_; }
+
+void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
+ if (ca_cert_store) {
+ if (ctx_) {
+ if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
+ // Free memory allocated for old cert and use new store `ca_cert_store`
+ SSL_CTX_set_cert_store(ctx_, ca_cert_store);
+ }
+ } else {
+ X509_STORE_free(ca_cert_store);
+ }
+ }
+}
+
+long SSLClient::get_openssl_verify_result() const {
+ return verify_result_;
+}
+
+SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
+
+bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
+ return is_valid() && ClientImpl::create_and_connect_socket(socket, error);
+}
+
+// Assumes that socket_mutex_ is locked and that there are no requests in flight
+bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
+ bool &success, Error &error) {
+ success = true;
+ Response res2;
+ if (!detail::process_client_socket(
+ socket.sock, read_timeout_sec_, read_timeout_usec_,
+ write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+ Request req2;
+ req2.method = "CONNECT";
+ req2.path = host_and_port_;
+ return process_request(strm, req2, res2, false, error);
+ })) {
+ // Thread-safe to close everything because we are assuming there are no
+ // requests in flight
+ shutdown_ssl(socket, true);
+ shutdown_socket(socket);
+ close_socket(socket);
+ success = false;
+ return false;
+ }
+
+ if (res2.status == 407) {
+ if (!proxy_digest_auth_username_.empty() &&
+ !proxy_digest_auth_password_.empty()) {
+ std::map<std::string, std::string> auth;
+ if (detail::parse_www_authenticate(res2, auth, true)) {
+ Response res3;
+ if (!detail::process_client_socket(
+ socket.sock, read_timeout_sec_, read_timeout_usec_,
+ write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+ Request req3;
+ req3.method = "CONNECT";
+ req3.path = host_and_port_;
+ req3.headers.insert(detail::make_digest_authentication_header(
+ req3, auth, 1, detail::random_string(10),
+ proxy_digest_auth_username_, proxy_digest_auth_password_,
+ true));
+ return process_request(strm, req3, res3, false, error);
+ })) {
+ // Thread-safe to close everything because we are assuming there are
+ // no requests in flight
+ shutdown_ssl(socket, true);
+ shutdown_socket(socket);
+ close_socket(socket);
+ success = false;
+ return false;
+ }
+ }
+ } else {
+ res = res2;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool SSLClient::load_certs() {
+ bool ret = true;
+
+ std::call_once(initialize_cert_, [&]() {
+ std::lock_guard<std::mutex> guard(ctx_mutex_);
+ if (!ca_cert_file_path_.empty()) {
+ if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(),
+ nullptr)) {
+ ret = false;
+ }
+ } else if (!ca_cert_dir_path_.empty()) {
+ if (!SSL_CTX_load_verify_locations(ctx_, nullptr,
+ ca_cert_dir_path_.c_str())) {
+ ret = false;
+ }
+ } else {
+#ifdef _WIN32
+ detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
+#else
+ SSL_CTX_set_default_verify_paths(ctx_);
+#endif
+ }
+ });
+
+ return ret;
+}
+
+bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
+ auto ssl = detail::ssl_new(
+ socket.sock, ctx_, ctx_mutex_,
+ [&](SSL *ssl2) {
+ if (server_certificate_verification_) {
+ if (!load_certs()) {
+ error = Error::SSLLoadingCerts;
+ return false;
+ }
+ SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr);
+ }
+
+ if (!detail::ssl_connect_or_accept_nonblocking(
+ socket.sock, ssl2, SSL_connect, connection_timeout_sec_,
+ connection_timeout_usec_)) {
+ error = Error::SSLConnection;
+ return false;
+ }
+
+ if (server_certificate_verification_) {
+ verify_result_ = SSL_get_verify_result(ssl2);
+
+ if (verify_result_ != X509_V_OK) {
+ error = Error::SSLServerVerification;
+ return false;
+ }
+
+ auto server_cert = SSL_get_peer_certificate(ssl2);
+
+ if (server_cert == nullptr) {
+ error = Error::SSLServerVerification;
+ return false;
+ }
+
+ if (!verify_host(server_cert)) {
+ X509_free(server_cert);
+ error = Error::SSLServerVerification;
+ return false;
+ }
+ X509_free(server_cert);
+ }
+
+ return true;
+ },
+ [&](SSL *ssl2) {
+ SSL_set_tlsext_host_name(ssl2, host_.c_str());
+ return true;
+ });
+
+ if (ssl) {
+ socket.ssl = ssl;
+ return true;
+ }
+
+ shutdown_socket(socket);
+ close_socket(socket);
+ return false;
+}
+
+void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
+ shutdown_ssl_impl(socket, shutdown_gracefully);
+}
+
+void SSLClient::shutdown_ssl_impl(Socket &socket,
+ bool shutdown_gracefully) {
+ if (socket.sock == INVALID_SOCKET) {
+ assert(socket.ssl == nullptr);
+ return;
+ }
+ if (socket.ssl) {
+ detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully);
+ socket.ssl = nullptr;
+ }
+ assert(socket.ssl == nullptr);
+}
+
+bool
+SSLClient::process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback) {
+ assert(socket.ssl);
+ return detail::process_client_socket_ssl(
+ socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
+ write_timeout_sec_, write_timeout_usec_, std::move(callback));
+}
+
+bool SSLClient::is_ssl() const { return true; }
+
+bool SSLClient::verify_host(X509 *server_cert) const {
+ /* Quote from RFC2818 section 3.1 "Server Identity"
+
+ If a subjectAltName extension of type dNSName is present, that MUST
+ be used as the identity. Otherwise, the (most specific) Common Name
+ field in the Subject field of the certificate MUST be used. Although
+ the use of the Common Name is existing practice, it is deprecated and
+ Certification Authorities are encouraged to use the dNSName instead.
+
+ Matching is performed using the matching rules specified by
+ [RFC2459]. If more than one identity of a given type is present in
+ the certificate (e.g., more than one dNSName name, a match in any one
+ of the set is considered acceptable.) Names may contain the wildcard
+ character * which is considered to match any single domain name
+ component or component fragment. E.g., *.a.com matches foo.a.com but
+ not bar.foo.a.com. f*.com matches foo.com but not bar.com.
+
+ In some cases, the URI is specified as an IP address rather than a
+ hostname. In this case, the iPAddress subjectAltName must be present
+ in the certificate and must exactly match the IP in the URI.
+
+ */
+ return verify_host_with_subject_alt_name(server_cert) ||
+ verify_host_with_common_name(server_cert);
+}
+
+bool
+SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
+ auto ret = false;
+
+ auto type = GEN_DNS;
+
+ struct in6_addr addr6;
+ struct in_addr addr;
+ size_t addr_len = 0;
+
+#ifndef __MINGW32__
+ if (inet_pton(AF_INET6, host_.c_str(), &addr6)) {
+ type = GEN_IPADD;
+ addr_len = sizeof(struct in6_addr);
+ } else if (inet_pton(AF_INET, host_.c_str(), &addr)) {
+ type = GEN_IPADD;
+ addr_len = sizeof(struct in_addr);
+ }
+#endif
+
+ auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>(
+ X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
+
+ if (alt_names) {
+ auto dsn_matched = false;
+ auto ip_mached = false;
+
+ auto count = sk_GENERAL_NAME_num(alt_names);
+
+ for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
+ auto val = sk_GENERAL_NAME_value(alt_names, i);
+ if (val->type == type) {
+ auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
+ auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
+
+ switch (type) {
+ case GEN_DNS: dsn_matched = check_host_name(name, name_len); break;
+
+ case GEN_IPADD:
+ if (!memcmp(&addr6, name, addr_len) ||
+ !memcmp(&addr, name, addr_len)) {
+ ip_mached = true;
+ }
+ break;
+ }
+ }
+ }
+
+ if (dsn_matched || ip_mached) { ret = true; }
+ }
+
+ GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
+ return ret;
+}
+
+bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
+ const auto subject_name = X509_get_subject_name(server_cert);
+
+ if (subject_name != nullptr) {
+ char name[BUFSIZ];
+ auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
+ name, sizeof(name));
+
+ if (name_len != -1) {
+ return check_host_name(name, static_cast<size_t>(name_len));
+ }
+ }
+
+ return false;
+}
+
+bool SSLClient::check_host_name(const char *pattern,
+ size_t pattern_len) const {
+ if (host_.size() == pattern_len && host_ == pattern) { return true; }
+
+ // Wildcard match
+ // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
+ std::vector<std::string> pattern_components;
+ detail::split(&pattern[0], &pattern[pattern_len], '.',
+ [&](const char *b, const char *e) {
+ pattern_components.emplace_back(std::string(b, e));
+ });
+
+ if (host_components_.size() != pattern_components.size()) { return false; }
+
+ auto itr = pattern_components.begin();
+ for (const auto &h : host_components_) {
+ auto &p = *itr;
+ if (p != h && p != "*") {
+ auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' &&
+ !p.compare(0, p.size() - 1, h));
+ if (!partial_match) { return false; }
+ }
+ ++itr;
+ }
+
+ return true;
+}
+#endif
+
+// Universal client implementation
+Client::Client(const std::string &scheme_host_port)
+ : Client(scheme_host_port, std::string(), std::string()) {}
+
+Client::Client(const std::string &scheme_host_port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path) {
+ const static std::regex re(
+ R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)");
+
+ std::smatch m;
+ if (std::regex_match(scheme_host_port, m, re)) {
+ auto scheme = m[1].str();
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
+#else
+ if (!scheme.empty() && scheme != "http") {
+#endif
+#ifndef CPPHTTPLIB_NO_EXCEPTIONS
+ std::string msg = "'" + scheme + "' scheme is not supported.";
+ throw std::invalid_argument(msg);
+#endif
+ return;
+ }
+
+ auto is_ssl = scheme == "https";
+
+ auto host = m[2].str();
+ if (host.empty()) { host = m[3].str(); }
+
+ auto port_str = m[4].str();
+ auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80);
+
+ if (is_ssl) {
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ cli_ = detail::make_unique<SSLClient>(host, port,
+ client_cert_path, client_key_path);
+ is_ssl_ = is_ssl;
+#endif
+ } else {
+ cli_ = detail::make_unique<ClientImpl>(host, port,
+ client_cert_path, client_key_path);
+ }
+ } else {
+ cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
+ client_cert_path, client_key_path);
+ }
+}
+
+Client::Client(const std::string &host, int port)
+ : cli_(detail::make_unique<ClientImpl>(host, port)) {}
+
+Client::Client(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path)
+ : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
+ client_key_path)) {}
+
+Client::~Client() {}
+
+bool Client::is_valid() const {
+ return cli_ != nullptr && cli_->is_valid();
+}
+
+Result Client::Get(const std::string &path) { return cli_->Get(path); }
+Result Client::Get(const std::string &path, const Headers &headers) {
+ return cli_->Get(path, headers);
+}
+Result Client::Get(const std::string &path, Progress progress) {
+ return cli_->Get(path, std::move(progress));
+}
+Result Client::Get(const std::string &path, const Headers &headers,
+ Progress progress) {
+ return cli_->Get(path, headers, std::move(progress));
+}
+Result Client::Get(const std::string &path,
+ ContentReceiver content_receiver) {
+ return cli_->Get(path, std::move(content_receiver));
+}
+Result Client::Get(const std::string &path, const Headers &headers,
+ ContentReceiver content_receiver) {
+ return cli_->Get(path, headers, std::move(content_receiver));
+}
+Result Client::Get(const std::string &path,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, std::move(content_receiver), std::move(progress));
+}
+Result Client::Get(const std::string &path, const Headers &headers,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, headers, std::move(content_receiver),
+ std::move(progress));
+}
+Result Client::Get(const std::string &path,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return cli_->Get(path, std::move(response_handler),
+ std::move(content_receiver));
+}
+Result Client::Get(const std::string &path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return cli_->Get(path, headers, std::move(response_handler),
+ std::move(content_receiver));
+}
+Result Client::Get(const std::string &path,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
+}
+Result Client::Get(const std::string &path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, headers, std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
+}
+Result Client::Get(const std::string &path, const Params ¶ms,
+ const Headers &headers, Progress progress) {
+ return cli_->Get(path, params, headers, progress);
+}
+Result Client::Get(const std::string &path, const Params ¶ms,
+ const Headers &headers,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, params, headers, content_receiver, progress);
+}
+Result Client::Get(const std::string &path, const Params ¶ms,
+ const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, params, headers, response_handler, content_receiver,
+ progress);
+}
+
+Result Client::Head(const std::string &path) { return cli_->Head(path); }
+Result Client::Head(const std::string &path, const Headers &headers) {
+ return cli_->Head(path, headers);
+}
+
+Result Client::Post(const std::string &path) { return cli_->Post(path); }
+Result Client::Post(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type) {
+ return cli_->Post(path, body, content_length, content_type);
+}
+Result Client::Post(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type) {
+ return cli_->Post(path, headers, body, content_length, content_type);
+}
+Result Client::Post(const std::string &path, const std::string &body,
+ const std::string &content_type) {
+ return cli_->Post(path, body, content_type);
+}
+Result Client::Post(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type) {
+ return cli_->Post(path, headers, body, content_type);
+}
+Result Client::Post(const std::string &path, size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return cli_->Post(path, content_length, std::move(content_provider),
+ content_type);
+}
+Result Client::Post(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return cli_->Post(path, std::move(content_provider), content_type);
+}
+Result Client::Post(const std::string &path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return cli_->Post(path, headers, content_length, std::move(content_provider),
+ content_type);
+}
+Result Client::Post(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return cli_->Post(path, headers, std::move(content_provider), content_type);
+}
+Result Client::Post(const std::string &path, const Params ¶ms) {
+ return cli_->Post(path, params);
+}
+Result Client::Post(const std::string &path, const Headers &headers,
+ const Params ¶ms) {
+ return cli_->Post(path, headers, params);
+}
+Result Client::Post(const std::string &path,
+ const MultipartFormDataItems &items) {
+ return cli_->Post(path, items);
+}
+Result Client::Post(const std::string &path, const Headers &headers,
+ const MultipartFormDataItems &items) {
+ return cli_->Post(path, headers, items);
+}
+Result Client::Post(const std::string &path, const Headers &headers,
+ const MultipartFormDataItems &items,
+ const std::string &boundary) {
+ return cli_->Post(path, headers, items, boundary);
+}
+Result Client::Put(const std::string &path) { return cli_->Put(path); }
+Result Client::Put(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type) {
+ return cli_->Put(path, body, content_length, content_type);
+}
+Result Client::Put(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type) {
+ return cli_->Put(path, headers, body, content_length, content_type);
+}
+Result Client::Put(const std::string &path, const std::string &body,
+ const std::string &content_type) {
+ return cli_->Put(path, body, content_type);
+}
+Result Client::Put(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type) {
+ return cli_->Put(path, headers, body, content_type);
+}
+Result Client::Put(const std::string &path, size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return cli_->Put(path, content_length, std::move(content_provider),
+ content_type);
+}
+Result Client::Put(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return cli_->Put(path, std::move(content_provider), content_type);
+}
+Result Client::Put(const std::string &path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return cli_->Put(path, headers, content_length, std::move(content_provider),
+ content_type);
+}
+Result Client::Put(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return cli_->Put(path, headers, std::move(content_provider), content_type);
+}
+Result Client::Put(const std::string &path, const Params ¶ms) {
+ return cli_->Put(path, params);
+}
+Result Client::Put(const std::string &path, const Headers &headers,
+ const Params ¶ms) {
+ return cli_->Put(path, headers, params);
+}
+Result Client::Patch(const std::string &path) {
+ return cli_->Patch(path);
+}
+Result Client::Patch(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type) {
+ return cli_->Patch(path, body, content_length, content_type);
+}
+Result Client::Patch(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type) {
+ return cli_->Patch(path, headers, body, content_length, content_type);
+}
+Result Client::Patch(const std::string &path, const std::string &body,
+ const std::string &content_type) {
+ return cli_->Patch(path, body, content_type);
+}
+Result Client::Patch(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type) {
+ return cli_->Patch(path, headers, body, content_type);
+}
+Result Client::Patch(const std::string &path, size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return cli_->Patch(path, content_length, std::move(content_provider),
+ content_type);
+}
+Result Client::Patch(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return cli_->Patch(path, std::move(content_provider), content_type);
+}
+Result Client::Patch(const std::string &path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type) {
+ return cli_->Patch(path, headers, content_length, std::move(content_provider),
+ content_type);
+}
+Result Client::Patch(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type) {
+ return cli_->Patch(path, headers, std::move(content_provider), content_type);
+}
+Result Client::Delete(const std::string &path) {
+ return cli_->Delete(path);
+}
+Result Client::Delete(const std::string &path, const Headers &headers) {
+ return cli_->Delete(path, headers);
+}
+Result Client::Delete(const std::string &path, const char *body,
+ size_t content_length,
+ const std::string &content_type) {
+ return cli_->Delete(path, body, content_length, content_type);
+}
+Result Client::Delete(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type) {
+ return cli_->Delete(path, headers, body, content_length, content_type);
+}
+Result Client::Delete(const std::string &path, const std::string &body,
+ const std::string &content_type) {
+ return cli_->Delete(path, body, content_type);
+}
+Result Client::Delete(const std::string &path, const Headers &headers,
+ const std::string &body,
+ const std::string &content_type) {
+ return cli_->Delete(path, headers, body, content_type);
+}
+Result Client::Options(const std::string &path) {
+ return cli_->Options(path);
+}
+Result Client::Options(const std::string &path, const Headers &headers) {
+ return cli_->Options(path, headers);
+}
+
+bool Client::send(Request &req, Response &res, Error &error) {
+ return cli_->send(req, res, error);
+}
+
+Result Client::send(const Request &req) { return cli_->send(req); }
+
+size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
+
+socket_t Client::socket() const { return cli_->socket(); }
+
+void Client::stop() { cli_->stop(); }
+
+void
+Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
+ cli_->set_hostname_addr_map(std::move(addr_map));
+}
+
+void Client::set_default_headers(Headers headers) {
+ cli_->set_default_headers(std::move(headers));
+}
+
+void Client::set_address_family(int family) {
+ cli_->set_address_family(family);
+}
+
+void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
+
+void Client::set_socket_options(SocketOptions socket_options) {
+ cli_->set_socket_options(std::move(socket_options));
+}
+
+void Client::set_connection_timeout(time_t sec, time_t usec) {
+ cli_->set_connection_timeout(sec, usec);
+}
+
+void Client::set_read_timeout(time_t sec, time_t usec) {
+ cli_->set_read_timeout(sec, usec);
+}
+
+void Client::set_write_timeout(time_t sec, time_t usec) {
+ cli_->set_write_timeout(sec, usec);
+}
+
+void Client::set_basic_auth(const std::string &username,
+ const std::string &password) {
+ cli_->set_basic_auth(username, password);
+}
+void Client::set_bearer_token_auth(const std::string &token) {
+ cli_->set_bearer_token_auth(token);
+}
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+void Client::set_digest_auth(const std::string &username,
+ const std::string &password) {
+ cli_->set_digest_auth(username, password);
+}
+#endif
+
+void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
+void Client::set_follow_location(bool on) {
+ cli_->set_follow_location(on);
+}
+
+void Client::set_url_encode(bool on) { cli_->set_url_encode(on); }
+
+void Client::set_compress(bool on) { cli_->set_compress(on); }
+
+void Client::set_decompress(bool on) { cli_->set_decompress(on); }
+
+void Client::set_interface(const std::string &intf) {
+ cli_->set_interface(intf);
+}
+
+void Client::set_proxy(const std::string &host, int port) {
+ cli_->set_proxy(host, port);
+}
+void Client::set_proxy_basic_auth(const std::string &username,
+ const std::string &password) {
+ cli_->set_proxy_basic_auth(username, password);
+}
+void Client::set_proxy_bearer_token_auth(const std::string &token) {
+ cli_->set_proxy_bearer_token_auth(token);
+}
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+void Client::set_proxy_digest_auth(const std::string &username,
+ const std::string &password) {
+ cli_->set_proxy_digest_auth(username, password);
+}
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+void Client::enable_server_certificate_verification(bool enabled) {
+ cli_->enable_server_certificate_verification(enabled);
+}
+#endif
+
+void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
+ const std::string &ca_cert_dir_path) {
+ cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
+}
+
+void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
+ if (is_ssl_) {
+ static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
+ } else {
+ cli_->set_ca_cert_store(ca_cert_store);
+ }
+}
+
+long Client::get_openssl_verify_result() const {
+ if (is_ssl_) {
+ return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();
+ }
+ return -1; // NOTE: -1 doesn't match any of X509_V_ERR_???
+}
+
+SSL_CTX *Client::ssl_context() const {
+ if (is_ssl_) { return static_cast<SSLClient &>(*cli_).ssl_context(); }
+ return nullptr;
+}
+#endif
+
+} // namespace httplib
--- /dev/null
+//
+// httplib.h
+//
+// Copyright (c) 2022 Yuji Hirose. All rights reserved.
+// MIT License
+//
+
+#ifndef CPPHTTPLIB_HTTPLIB_H
+#define CPPHTTPLIB_HTTPLIB_H
+
+#define CPPHTTPLIB_VERSION "0.11.1"
+
+/*
+ * Configuration
+ */
+
+#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND
+#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
+#endif
+
+#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
+#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
+#endif
+
+#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
+#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
+#endif
+
+#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
+#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
+#endif
+
+#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND
+#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
+#endif
+
+#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND
+#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
+#endif
+
+#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND
+#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5
+#endif
+
+#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND
+#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0
+#endif
+
+#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
+#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
+#endif
+
+#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
+#ifdef _WIN32
+#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000
+#else
+#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
+#endif
+#endif
+
+#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
+#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
+#endif
+
+#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH
+#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192
+#endif
+
+#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
+#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
+#endif
+
+#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
+#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)())
+#endif
+
+#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
+#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
+#endif
+
+#ifndef CPPHTTPLIB_TCP_NODELAY
+#define CPPHTTPLIB_TCP_NODELAY false
+#endif
+
+#ifndef CPPHTTPLIB_RECV_BUFSIZ
+#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
+#endif
+
+#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
+#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
+#endif
+
+#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
+#define CPPHTTPLIB_THREAD_POOL_COUNT \
+ ((std::max)(8u, std::thread::hardware_concurrency() > 0 \
+ ? std::thread::hardware_concurrency() - 1 \
+ : 0))
+#endif
+
+#ifndef CPPHTTPLIB_RECV_FLAGS
+#define CPPHTTPLIB_RECV_FLAGS 0
+#endif
+
+#ifndef CPPHTTPLIB_SEND_FLAGS
+#define CPPHTTPLIB_SEND_FLAGS 0
+#endif
+
+#ifndef CPPHTTPLIB_LISTEN_BACKLOG
+#define CPPHTTPLIB_LISTEN_BACKLOG 5
+#endif
+
+/*
+ * Headers
+ */
+
+#ifdef _WIN32
+#ifndef _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS
+#endif //_CRT_SECURE_NO_WARNINGS
+
+#ifndef _CRT_NONSTDC_NO_DEPRECATE
+#define _CRT_NONSTDC_NO_DEPRECATE
+#endif //_CRT_NONSTDC_NO_DEPRECATE
+
+#if defined(_MSC_VER)
+#if _MSC_VER < 1900
+#error Sorry, Visual Studio versions prior to 2015 are not supported
+#endif
+
+#pragma comment(lib, "ws2_32.lib")
+
+#ifdef _WIN64
+using ssize_t = __int64;
+#else
+using ssize_t = long;
+#endif
+#endif // _MSC_VER
+
+#ifndef S_ISREG
+#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG)
+#endif // S_ISREG
+
+#ifndef S_ISDIR
+#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR)
+#endif // S_ISDIR
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
+
+#include <io.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+#ifndef WSA_FLAG_NO_HANDLE_INHERIT
+#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
+#endif
+
+#ifndef strcasecmp
+#define strcasecmp _stricmp
+#endif // strcasecmp
+
+using socket_t = SOCKET;
+#ifdef CPPHTTPLIB_USE_POLL
+#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
+#endif
+
+#else // not _WIN32
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef __linux__
+#include <resolv.h>
+#endif
+#include <netinet/tcp.h>
+#ifdef CPPHTTPLIB_USE_POLL
+#include <poll.h>
+#endif
+#include <csignal>
+#include <pthread.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+using socket_t = int;
+#ifndef INVALID_SOCKET
+#define INVALID_SOCKET (-1)
+#endif
+#endif //_WIN32
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <cassert>
+#include <cctype>
+#include <climits>
+#include <condition_variable>
+#include <cstring>
+#include <errno.h>
+#include <fcntl.h>
+#include <fstream>
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <random>
+#include <regex>
+#include <set>
+#include <sstream>
+#include <string>
+#include <sys/stat.h>
+#include <thread>
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+#ifdef _WIN32
+#include <wincrypt.h>
+
+// these are defined in wincrypt.h and it breaks compilation if BoringSSL is
+// used
+#undef X509_NAME
+#undef X509_CERT_PAIR
+#undef X509_EXTENSIONS
+#undef PKCS7_SIGNER_INFO
+
+#ifdef _MSC_VER
+#pragma comment(lib, "crypt32.lib")
+#pragma comment(lib, "cryptui.lib")
+#endif
+#endif //_WIN32
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
+#include <openssl/applink.c>
+#endif
+
+#include <iostream>
+#include <sstream>
+
+#if OPENSSL_VERSION_NUMBER < 0x1010100fL
+#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#include <openssl/crypto.h>
+inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
+ return M_ASN1_STRING_data(asn1);
+}
+#endif
+#endif
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+#include <zlib.h>
+#endif
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+#include <brotli/decode.h>
+#include <brotli/encode.h>
+#endif
+
+/*
+ * Declaration
+ */
+namespace httplib {
+
+namespace detail {
+
+/*
+ * Backport std::make_unique from C++14.
+ *
+ * NOTE: This code came up with the following stackoverflow post:
+ * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique
+ *
+ */
+
+template <class T, class... Args>
+typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(Args &&...args) {
+ return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+template <class T>
+typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(std::size_t n) {
+ typedef typename std::remove_extent<T>::type RT;
+ return std::unique_ptr<T>(new RT[n]);
+}
+
+struct ci {
+ bool operator()(const std::string &s1, const std::string &s2) const {
+ return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(),
+ s2.end(),
+ [](unsigned char c1, unsigned char c2) {
+ return ::tolower(c1) < ::tolower(c2);
+ });
+ }
+};
+
+} // namespace detail
+
+using Headers = std::multimap<std::string, std::string, detail::ci>;
+
+using Params = std::multimap<std::string, std::string>;
+using Match = std::smatch;
+
+using Progress = std::function<bool(uint64_t current, uint64_t total)>;
+
+struct Response;
+using ResponseHandler = std::function<bool(const Response &response)>;
+
+struct MultipartFormData {
+ std::string name;
+ std::string content;
+ std::string filename;
+ std::string content_type;
+};
+using MultipartFormDataItems = std::vector<MultipartFormData>;
+using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
+
+class DataSink {
+public:
+ DataSink() : os(&sb_), sb_(*this) {}
+
+ DataSink(const DataSink &) = delete;
+ DataSink &operator=(const DataSink &) = delete;
+ DataSink(DataSink &&) = delete;
+ DataSink &operator=(DataSink &&) = delete;
+
+ std::function<bool(const char *data, size_t data_len)> write;
+ std::function<void()> done;
+ std::function<bool()> is_writable;
+ std::ostream os;
+
+private:
+ class data_sink_streambuf : public std::streambuf {
+ public:
+ explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
+
+ protected:
+ std::streamsize xsputn(const char *s, std::streamsize n) {
+ sink_.write(s, static_cast<size_t>(n));
+ return n;
+ }
+
+ private:
+ DataSink &sink_;
+ };
+
+ data_sink_streambuf sb_;
+};
+
+using ContentProvider =
+ std::function<bool(size_t offset, size_t length, DataSink &sink)>;
+
+using ContentProviderWithoutLength =
+ std::function<bool(size_t offset, DataSink &sink)>;
+
+using ContentProviderResourceReleaser = std::function<void(bool success)>;
+
+using ContentReceiverWithProgress =
+ std::function<bool(const char *data, size_t data_length, uint64_t offset,
+ uint64_t total_length)>;
+
+using ContentReceiver =
+ std::function<bool(const char *data, size_t data_length)>;
+
+using MultipartContentHeader =
+ std::function<bool(const MultipartFormData &file)>;
+
+class ContentReader {
+public:
+ using Reader = std::function<bool(ContentReceiver receiver)>;
+ using MultipartReader = std::function<bool(MultipartContentHeader header,
+ ContentReceiver receiver)>;
+
+ ContentReader(Reader reader, MultipartReader multipart_reader)
+ : reader_(std::move(reader)),
+ multipart_reader_(std::move(multipart_reader)) {}
+
+ bool operator()(MultipartContentHeader header,
+ ContentReceiver receiver) const {
+ return multipart_reader_(std::move(header), std::move(receiver));
+ }
+
+ bool operator()(ContentReceiver receiver) const {
+ return reader_(std::move(receiver));
+ }
+
+ Reader reader_;
+ MultipartReader multipart_reader_;
+};
+
+using Range = std::pair<ssize_t, ssize_t>;
+using Ranges = std::vector<Range>;
+
+struct Request {
+ std::string method;
+ std::string path;
+ Headers headers;
+ std::string body;
+
+ std::string remote_addr;
+ int remote_port = -1;
+
+ // for server
+ std::string version;
+ std::string target;
+ Params params;
+ MultipartFormDataMap files;
+ Ranges ranges;
+ Match matches;
+
+ // for client
+ ResponseHandler response_handler;
+ ContentReceiverWithProgress content_receiver;
+ Progress progress;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ const SSL *ssl = nullptr;
+#endif
+
+ bool has_header(const std::string &key) const;
+ std::string get_header_value(const std::string &key, size_t id = 0) const;
+ template <typename T>
+ T get_header_value(const std::string &key, size_t id = 0) const;
+ size_t get_header_value_count(const std::string &key) const;
+ void set_header(const std::string &key, const std::string &val);
+
+ bool has_param(const std::string &key) const;
+ std::string get_param_value(const std::string &key, size_t id = 0) const;
+ size_t get_param_value_count(const std::string &key) const;
+
+ bool is_multipart_form_data() const;
+
+ bool has_file(const std::string &key) const;
+ MultipartFormData get_file_value(const std::string &key) const;
+
+ // private members...
+ size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
+ size_t content_length_ = 0;
+ ContentProvider content_provider_;
+ bool is_chunked_content_provider_ = false;
+ size_t authorization_count_ = 0;
+};
+
+struct Response {
+ std::string version;
+ int status = -1;
+ std::string reason;
+ Headers headers;
+ std::string body;
+ std::string location; // Redirect location
+
+ bool has_header(const std::string &key) const;
+ std::string get_header_value(const std::string &key, size_t id = 0) const;
+ template <typename T>
+ T get_header_value(const std::string &key, size_t id = 0) const;
+ size_t get_header_value_count(const std::string &key) const;
+ void set_header(const std::string &key, const std::string &val);
+
+ void set_redirect(const std::string &url, int status = 302);
+ void set_content(const char *s, size_t n, const std::string &content_type);
+ void set_content(const std::string &s, const std::string &content_type);
+
+ void set_content_provider(
+ size_t length, const std::string &content_type, ContentProvider provider,
+ ContentProviderResourceReleaser resource_releaser = nullptr);
+
+ void set_content_provider(
+ const std::string &content_type, ContentProviderWithoutLength provider,
+ ContentProviderResourceReleaser resource_releaser = nullptr);
+
+ void set_chunked_content_provider(
+ const std::string &content_type, ContentProviderWithoutLength provider,
+ ContentProviderResourceReleaser resource_releaser = nullptr);
+
+ Response() = default;
+ Response(const Response &) = default;
+ Response &operator=(const Response &) = default;
+ Response(Response &&) = default;
+ Response &operator=(Response &&) = default;
+ ~Response() {
+ if (content_provider_resource_releaser_) {
+ content_provider_resource_releaser_(content_provider_success_);
+ }
+ }
+
+ // private members...
+ size_t content_length_ = 0;
+ ContentProvider content_provider_;
+ ContentProviderResourceReleaser content_provider_resource_releaser_;
+ bool is_chunked_content_provider_ = false;
+ bool content_provider_success_ = false;
+};
+
+class Stream {
+public:
+ virtual ~Stream() = default;
+
+ virtual bool is_readable() const = 0;
+ virtual bool is_writable() const = 0;
+
+ virtual ssize_t read(char *ptr, size_t size) = 0;
+ virtual ssize_t write(const char *ptr, size_t size) = 0;
+ virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
+ virtual socket_t socket() const = 0;
+
+ template <typename... Args>
+ ssize_t write_format(const char *fmt, const Args &...args);
+ ssize_t write(const char *ptr);
+ ssize_t write(const std::string &s);
+};
+
+class TaskQueue {
+public:
+ TaskQueue() = default;
+ virtual ~TaskQueue() = default;
+
+ virtual void enqueue(std::function<void()> fn) = 0;
+ virtual void shutdown() = 0;
+
+ virtual void on_idle() {}
+};
+
+class ThreadPool : public TaskQueue {
+public:
+ explicit ThreadPool(size_t n) : shutdown_(false) {
+ while (n) {
+ threads_.emplace_back(worker(*this));
+ n--;
+ }
+ }
+
+ ThreadPool(const ThreadPool &) = delete;
+ ~ThreadPool() override = default;
+
+ void enqueue(std::function<void()> fn) override {
+ std::unique_lock<std::mutex> lock(mutex_);
+ jobs_.push_back(std::move(fn));
+ cond_.notify_one();
+ }
+
+ void shutdown() override {
+ // Stop all worker threads...
+ {
+ std::unique_lock<std::mutex> lock(mutex_);
+ shutdown_ = true;
+ }
+
+ cond_.notify_all();
+
+ // Join...
+ for (auto &t : threads_) {
+ t.join();
+ }
+ }
+
+private:
+ struct worker {
+ explicit worker(ThreadPool &pool) : pool_(pool) {}
+
+ void operator()() {
+ for (;;) {
+ std::function<void()> fn;
+ {
+ std::unique_lock<std::mutex> lock(pool_.mutex_);
+
+ pool_.cond_.wait(
+ lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; });
+
+ if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }
+
+ fn = pool_.jobs_.front();
+ pool_.jobs_.pop_front();
+ }
+
+ assert(true == static_cast<bool>(fn));
+ fn();
+ }
+ }
+
+ ThreadPool &pool_;
+ };
+ friend struct worker;
+
+ std::vector<std::thread> threads_;
+ std::list<std::function<void()>> jobs_;
+
+ bool shutdown_;
+
+ std::condition_variable cond_;
+ std::mutex mutex_;
+};
+
+using Logger = std::function<void(const Request &, const Response &)>;
+
+using SocketOptions = std::function<void(socket_t sock)>;
+
+void default_socket_options(socket_t sock);
+
+class Server {
+public:
+ using Handler = std::function<void(const Request &, Response &)>;
+
+ using ExceptionHandler =
+ std::function<void(const Request &, Response &, std::exception_ptr ep)>;
+
+ enum class HandlerResponse {
+ Handled,
+ Unhandled,
+ };
+ using HandlerWithResponse =
+ std::function<HandlerResponse(const Request &, Response &)>;
+
+ using HandlerWithContentReader = std::function<void(
+ const Request &, Response &, const ContentReader &content_reader)>;
+
+ using Expect100ContinueHandler =
+ std::function<int(const Request &, Response &)>;
+
+ Server();
+
+ virtual ~Server();
+
+ virtual bool is_valid() const;
+
+ Server &Get(const std::string &pattern, Handler handler);
+ Server &Post(const std::string &pattern, Handler handler);
+ Server &Post(const std::string &pattern, HandlerWithContentReader handler);
+ Server &Put(const std::string &pattern, Handler handler);
+ Server &Put(const std::string &pattern, HandlerWithContentReader handler);
+ Server &Patch(const std::string &pattern, Handler handler);
+ Server &Patch(const std::string &pattern, HandlerWithContentReader handler);
+ Server &Delete(const std::string &pattern, Handler handler);
+ Server &Delete(const std::string &pattern, HandlerWithContentReader handler);
+ Server &Options(const std::string &pattern, Handler handler);
+
+ bool set_base_dir(const std::string &dir,
+ const std::string &mount_point = std::string());
+ bool set_mount_point(const std::string &mount_point, const std::string &dir,
+ Headers headers = Headers());
+ bool remove_mount_point(const std::string &mount_point);
+ Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
+ const std::string &mime);
+ Server &set_file_request_handler(Handler handler);
+
+ Server &set_error_handler(HandlerWithResponse handler);
+ Server &set_error_handler(Handler handler);
+ Server &set_exception_handler(ExceptionHandler handler);
+ Server &set_pre_routing_handler(HandlerWithResponse handler);
+ Server &set_post_routing_handler(Handler handler);
+
+ Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
+ Server &set_logger(Logger logger);
+
+ Server &set_address_family(int family);
+ Server &set_tcp_nodelay(bool on);
+ Server &set_socket_options(SocketOptions socket_options);
+
+ Server &set_default_headers(Headers headers);
+
+ Server &set_keep_alive_max_count(size_t count);
+ Server &set_keep_alive_timeout(time_t sec);
+
+ Server &set_read_timeout(time_t sec, time_t usec = 0);
+ template <class Rep, class Period>
+ Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+ Server &set_write_timeout(time_t sec, time_t usec = 0);
+ template <class Rep, class Period>
+ Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+ Server &set_idle_interval(time_t sec, time_t usec = 0);
+ template <class Rep, class Period>
+ Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration);
+
+ Server &set_payload_max_length(size_t length);
+
+ bool bind_to_port(const std::string &host, int port, int socket_flags = 0);
+ int bind_to_any_port(const std::string &host, int socket_flags = 0);
+ bool listen_after_bind();
+
+ bool listen(const std::string &host, int port, int socket_flags = 0);
+
+ bool is_running() const;
+ void stop();
+
+ std::function<TaskQueue *(void)> new_task_queue;
+
+protected:
+ bool process_request(Stream &strm, bool close_connection,
+ bool &connection_closed,
+ const std::function<void(Request &)> &setup_request);
+
+ std::atomic<socket_t> svr_sock_;
+ size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
+ time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
+ time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
+ time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
+ time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
+ time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
+ time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
+ time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
+ size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
+
+private:
+ using Handlers = std::vector<std::pair<std::regex, Handler>>;
+ using HandlersForContentReader =
+ std::vector<std::pair<std::regex, HandlerWithContentReader>>;
+
+ socket_t create_server_socket(const std::string &host, int port,
+ int socket_flags,
+ SocketOptions socket_options) const;
+ int bind_internal(const std::string &host, int port, int socket_flags);
+ bool listen_internal();
+
+ bool routing(Request &req, Response &res, Stream &strm);
+ bool handle_file_request(const Request &req, Response &res,
+ bool head = false);
+ bool dispatch_request(Request &req, Response &res, const Handlers &handlers);
+ bool
+ dispatch_request_for_content_reader(Request &req, Response &res,
+ ContentReader content_reader,
+ const HandlersForContentReader &handlers);
+
+ bool parse_request_line(const char *s, Request &req);
+ void apply_ranges(const Request &req, Response &res,
+ std::string &content_type, std::string &boundary);
+ bool write_response(Stream &strm, bool close_connection, const Request &req,
+ Response &res);
+ bool write_response_with_content(Stream &strm, bool close_connection,
+ const Request &req, Response &res);
+ bool write_response_core(Stream &strm, bool close_connection,
+ const Request &req, Response &res,
+ bool need_apply_ranges);
+ bool write_content_with_provider(Stream &strm, const Request &req,
+ Response &res, const std::string &boundary,
+ const std::string &content_type);
+ bool read_content(Stream &strm, Request &req, Response &res);
+ bool
+ read_content_with_content_receiver(Stream &strm, Request &req, Response &res,
+ ContentReceiver receiver,
+ MultipartContentHeader multipart_header,
+ ContentReceiver multipart_receiver);
+ bool read_content_core(Stream &strm, Request &req, Response &res,
+ ContentReceiver receiver,
+ MultipartContentHeader mulitpart_header,
+ ContentReceiver multipart_receiver);
+
+ virtual bool process_and_close_socket(socket_t sock);
+
+ struct MountPointEntry {
+ std::string mount_point;
+ std::string base_dir;
+ Headers headers;
+ };
+ std::vector<MountPointEntry> base_dirs_;
+
+ std::atomic<bool> is_running_;
+ std::map<std::string, std::string> file_extension_and_mimetype_map_;
+ Handler file_request_handler_;
+ Handlers get_handlers_;
+ Handlers post_handlers_;
+ HandlersForContentReader post_handlers_for_content_reader_;
+ Handlers put_handlers_;
+ HandlersForContentReader put_handlers_for_content_reader_;
+ Handlers patch_handlers_;
+ HandlersForContentReader patch_handlers_for_content_reader_;
+ Handlers delete_handlers_;
+ HandlersForContentReader delete_handlers_for_content_reader_;
+ Handlers options_handlers_;
+ HandlerWithResponse error_handler_;
+ ExceptionHandler exception_handler_;
+ HandlerWithResponse pre_routing_handler_;
+ Handler post_routing_handler_;
+ Logger logger_;
+ Expect100ContinueHandler expect_100_continue_handler_;
+
+ int address_family_ = AF_UNSPEC;
+ bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
+ SocketOptions socket_options_ = default_socket_options;
+
+ Headers default_headers_;
+};
+
+enum class Error {
+ Success = 0,
+ Unknown,
+ Connection,
+ BindIPAddress,
+ Read,
+ Write,
+ ExceedRedirectCount,
+ Canceled,
+ SSLConnection,
+ SSLLoadingCerts,
+ SSLServerVerification,
+ UnsupportedMultipartBoundaryChars,
+ Compression,
+ ConnectionTimeout,
+};
+
+std::string to_string(const Error error);
+
+std::ostream &operator<<(std::ostream &os, const Error &obj);
+
+class Result {
+public:
+ Result(std::unique_ptr<Response> &&res, Error err,
+ Headers &&request_headers = Headers{})
+ : res_(std::move(res)), err_(err),
+ request_headers_(std::move(request_headers)) {}
+ // Response
+ operator bool() const { return res_ != nullptr; }
+ bool operator==(std::nullptr_t) const { return res_ == nullptr; }
+ bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
+ const Response &value() const { return *res_; }
+ Response &value() { return *res_; }
+ const Response &operator*() const { return *res_; }
+ Response &operator*() { return *res_; }
+ const Response *operator->() const { return res_.get(); }
+ Response *operator->() { return res_.get(); }
+
+ // Error
+ Error error() const { return err_; }
+
+ // Request Headers
+ bool has_request_header(const std::string &key) const;
+ std::string get_request_header_value(const std::string &key,
+ size_t id = 0) const;
+ template <typename T>
+ T get_request_header_value(const std::string &key, size_t id = 0) const;
+ size_t get_request_header_value_count(const std::string &key) const;
+
+private:
+ std::unique_ptr<Response> res_;
+ Error err_;
+ Headers request_headers_;
+};
+
+class ClientImpl {
+public:
+ explicit ClientImpl(const std::string &host);
+
+ explicit ClientImpl(const std::string &host, int port);
+
+ explicit ClientImpl(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
+
+ virtual ~ClientImpl();
+
+ virtual bool is_valid() const;
+
+ Result Get(const std::string &path);
+ Result Get(const std::string &path, const Headers &headers);
+ Result Get(const std::string &path, Progress progress);
+ Result Get(const std::string &path, const Headers &headers,
+ Progress progress);
+ Result Get(const std::string &path, ContentReceiver content_receiver);
+ Result Get(const std::string &path, const Headers &headers,
+ ContentReceiver content_receiver);
+ Result Get(const std::string &path, ContentReceiver content_receiver,
+ Progress progress);
+ Result Get(const std::string &path, const Headers &headers,
+ ContentReceiver content_receiver, Progress progress);
+ Result Get(const std::string &path, ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const std::string &path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const std::string &path, ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress);
+ Result Get(const std::string &path, const Headers &headers,
+ ResponseHandler response_handler, ContentReceiver content_receiver,
+ Progress progress);
+
+ Result Get(const std::string &path, const Params ¶ms,
+ const Headers &headers, Progress progress = nullptr);
+ Result Get(const std::string &path, const Params ¶ms,
+ const Headers &headers, ContentReceiver content_receiver,
+ Progress progress = nullptr);
+ Result Get(const std::string &path, const Params ¶ms,
+ const Headers &headers, ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress = nullptr);
+
+ Result Head(const std::string &path);
+ Result Head(const std::string &path, const Headers &headers);
+
+ Result Post(const std::string &path);
+ Result Post(const std::string &path, const char *body, size_t content_length,
+ const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers, const char *body,
+ size_t content_length, const std::string &content_type);
+ Result Post(const std::string &path, const std::string &body,
+ const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type);
+ Result Post(const std::string &path, size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type);
+ Result Post(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers,
+ size_t content_length, ContentProvider content_provider,
+ const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+ Result Post(const std::string &path, const Params ¶ms);
+ Result Post(const std::string &path, const Headers &headers,
+ const Params ¶ms);
+ Result Post(const std::string &path, const MultipartFormDataItems &items);
+ Result Post(const std::string &path, const Headers &headers,
+ const MultipartFormDataItems &items);
+ Result Post(const std::string &path, const Headers &headers,
+ const MultipartFormDataItems &items, const std::string &boundary);
+
+ Result Put(const std::string &path);
+ Result Put(const std::string &path, const char *body, size_t content_length,
+ const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers, const char *body,
+ size_t content_length, const std::string &content_type);
+ Result Put(const std::string &path, const std::string &body,
+ const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type);
+ Result Put(const std::string &path, size_t content_length,
+ ContentProvider content_provider, const std::string &content_type);
+ Result Put(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers,
+ size_t content_length, ContentProvider content_provider,
+ const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+ Result Put(const std::string &path, const Params ¶ms);
+ Result Put(const std::string &path, const Headers &headers,
+ const Params ¶ms);
+
+ Result Patch(const std::string &path);
+ Result Patch(const std::string &path, const char *body, size_t content_length,
+ const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type);
+ Result Patch(const std::string &path, const std::string &body,
+ const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type);
+ Result Patch(const std::string &path, size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type);
+ Result Patch(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ size_t content_length, ContentProvider content_provider,
+ const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+
+ Result Delete(const std::string &path);
+ Result Delete(const std::string &path, const Headers &headers);
+ Result Delete(const std::string &path, const char *body,
+ size_t content_length, const std::string &content_type);
+ Result Delete(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type);
+ Result Delete(const std::string &path, const std::string &body,
+ const std::string &content_type);
+ Result Delete(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type);
+
+ Result Options(const std::string &path);
+ Result Options(const std::string &path, const Headers &headers);
+
+ bool send(Request &req, Response &res, Error &error);
+ Result send(const Request &req);
+
+ size_t is_socket_open() const;
+
+ socket_t socket() const;
+
+ void stop();
+
+ void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
+
+ void set_default_headers(Headers headers);
+
+ void set_address_family(int family);
+ void set_tcp_nodelay(bool on);
+ void set_socket_options(SocketOptions socket_options);
+
+ void set_connection_timeout(time_t sec, time_t usec = 0);
+ template <class Rep, class Period>
+ void
+ set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+ void set_read_timeout(time_t sec, time_t usec = 0);
+ template <class Rep, class Period>
+ void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+ void set_write_timeout(time_t sec, time_t usec = 0);
+ template <class Rep, class Period>
+ void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+ void set_basic_auth(const std::string &username, const std::string &password);
+ void set_bearer_token_auth(const std::string &token);
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_digest_auth(const std::string &username,
+ const std::string &password);
+#endif
+
+ void set_keep_alive(bool on);
+ void set_follow_location(bool on);
+
+ void set_url_encode(bool on);
+
+ void set_compress(bool on);
+
+ void set_decompress(bool on);
+
+ void set_interface(const std::string &intf);
+
+ void set_proxy(const std::string &host, int port);
+ void set_proxy_basic_auth(const std::string &username,
+ const std::string &password);
+ void set_proxy_bearer_token_auth(const std::string &token);
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_proxy_digest_auth(const std::string &username,
+ const std::string &password);
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_ca_cert_path(const std::string &ca_cert_file_path,
+ const std::string &ca_cert_dir_path = std::string());
+ void set_ca_cert_store(X509_STORE *ca_cert_store);
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void enable_server_certificate_verification(bool enabled);
+#endif
+
+ void set_logger(Logger logger);
+
+protected:
+ struct Socket {
+ socket_t sock = INVALID_SOCKET;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ SSL *ssl = nullptr;
+#endif
+
+ bool is_open() const { return sock != INVALID_SOCKET; }
+ };
+
+ Result send_(Request &&req);
+
+ virtual bool create_and_connect_socket(Socket &socket, Error &error);
+
+ // All of:
+ // shutdown_ssl
+ // shutdown_socket
+ // close_socket
+ // should ONLY be called when socket_mutex_ is locked.
+ // Also, shutdown_ssl and close_socket should also NOT be called concurrently
+ // with a DIFFERENT thread sending requests using that socket.
+ virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
+ void shutdown_socket(Socket &socket);
+ void close_socket(Socket &socket);
+
+ bool process_request(Stream &strm, Request &req, Response &res,
+ bool close_connection, Error &error);
+
+ bool write_content_with_provider(Stream &strm, const Request &req,
+ Error &error);
+
+ void copy_settings(const ClientImpl &rhs);
+
+ // Socket endoint information
+ const std::string host_;
+ const int port_;
+ const std::string host_and_port_;
+
+ // Current open socket
+ Socket socket_;
+ mutable std::mutex socket_mutex_;
+ std::recursive_mutex request_mutex_;
+
+ // These are all protected under socket_mutex
+ size_t socket_requests_in_flight_ = 0;
+ std::thread::id socket_requests_are_from_thread_ = std::thread::id();
+ bool socket_should_be_closed_when_request_is_done_ = false;
+
+ // Hostname-IP map
+ std::map<std::string, std::string> addr_map_;
+
+ // Default headers
+ Headers default_headers_;
+
+ // Settings
+ std::string client_cert_path_;
+ std::string client_key_path_;
+
+ time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
+ time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
+ time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
+ time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
+ time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
+ time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
+
+ std::string basic_auth_username_;
+ std::string basic_auth_password_;
+ std::string bearer_token_auth_token_;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ std::string digest_auth_username_;
+ std::string digest_auth_password_;
+#endif
+
+ bool keep_alive_ = false;
+ bool follow_location_ = false;
+
+ bool url_encode_ = true;
+
+ int address_family_ = AF_UNSPEC;
+ bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
+ SocketOptions socket_options_ = nullptr;
+
+ bool compress_ = false;
+ bool decompress_ = true;
+
+ std::string interface_;
+
+ std::string proxy_host_;
+ int proxy_port_ = -1;
+
+ std::string proxy_basic_auth_username_;
+ std::string proxy_basic_auth_password_;
+ std::string proxy_bearer_token_auth_token_;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ std::string proxy_digest_auth_username_;
+ std::string proxy_digest_auth_password_;
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ std::string ca_cert_file_path_;
+ std::string ca_cert_dir_path_;
+
+ X509_STORE *ca_cert_store_ = nullptr;
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ bool server_certificate_verification_ = true;
+#endif
+
+ Logger logger_;
+
+private:
+ socket_t create_client_socket(Error &error) const;
+ bool read_response_line(Stream &strm, const Request &req, Response &res);
+ bool write_request(Stream &strm, Request &req, bool close_connection,
+ Error &error);
+ bool redirect(Request &req, Response &res, Error &error);
+ bool handle_request(Stream &strm, Request &req, Response &res,
+ bool close_connection, Error &error);
+ std::unique_ptr<Response> send_with_content_provider(
+ Request &req, const char *body, size_t content_length,
+ ContentProvider content_provider,
+ ContentProviderWithoutLength content_provider_without_length,
+ const std::string &content_type, Error &error);
+ Result send_with_content_provider(
+ const std::string &method, const std::string &path,
+ const Headers &headers, const char *body, size_t content_length,
+ ContentProvider content_provider,
+ ContentProviderWithoutLength content_provider_without_length,
+ const std::string &content_type);
+
+ std::string adjust_host_string(const std::string &host) const;
+
+ virtual bool process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback);
+ virtual bool is_ssl() const;
+};
+
+class Client {
+public:
+ // Universal interface
+ explicit Client(const std::string &scheme_host_port);
+
+ explicit Client(const std::string &scheme_host_port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
+
+ // HTTP only interface
+ explicit Client(const std::string &host, int port);
+
+ explicit Client(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
+
+ Client(Client &&) = default;
+
+ ~Client();
+
+ bool is_valid() const;
+
+ Result Get(const std::string &path);
+ Result Get(const std::string &path, const Headers &headers);
+ Result Get(const std::string &path, Progress progress);
+ Result Get(const std::string &path, const Headers &headers,
+ Progress progress);
+ Result Get(const std::string &path, ContentReceiver content_receiver);
+ Result Get(const std::string &path, const Headers &headers,
+ ContentReceiver content_receiver);
+ Result Get(const std::string &path, ContentReceiver content_receiver,
+ Progress progress);
+ Result Get(const std::string &path, const Headers &headers,
+ ContentReceiver content_receiver, Progress progress);
+ Result Get(const std::string &path, ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const std::string &path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const std::string &path, const Headers &headers,
+ ResponseHandler response_handler, ContentReceiver content_receiver,
+ Progress progress);
+ Result Get(const std::string &path, ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress);
+
+ Result Get(const std::string &path, const Params ¶ms,
+ const Headers &headers, Progress progress = nullptr);
+ Result Get(const std::string &path, const Params ¶ms,
+ const Headers &headers, ContentReceiver content_receiver,
+ Progress progress = nullptr);
+ Result Get(const std::string &path, const Params ¶ms,
+ const Headers &headers, ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress = nullptr);
+
+ Result Head(const std::string &path);
+ Result Head(const std::string &path, const Headers &headers);
+
+ Result Post(const std::string &path);
+ Result Post(const std::string &path, const char *body, size_t content_length,
+ const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers, const char *body,
+ size_t content_length, const std::string &content_type);
+ Result Post(const std::string &path, const std::string &body,
+ const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type);
+ Result Post(const std::string &path, size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type);
+ Result Post(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers,
+ size_t content_length, ContentProvider content_provider,
+ const std::string &content_type);
+ Result Post(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+ Result Post(const std::string &path, const Params ¶ms);
+ Result Post(const std::string &path, const Headers &headers,
+ const Params ¶ms);
+ Result Post(const std::string &path, const MultipartFormDataItems &items);
+ Result Post(const std::string &path, const Headers &headers,
+ const MultipartFormDataItems &items);
+ Result Post(const std::string &path, const Headers &headers,
+ const MultipartFormDataItems &items, const std::string &boundary);
+ Result Put(const std::string &path);
+ Result Put(const std::string &path, const char *body, size_t content_length,
+ const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers, const char *body,
+ size_t content_length, const std::string &content_type);
+ Result Put(const std::string &path, const std::string &body,
+ const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type);
+ Result Put(const std::string &path, size_t content_length,
+ ContentProvider content_provider, const std::string &content_type);
+ Result Put(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers,
+ size_t content_length, ContentProvider content_provider,
+ const std::string &content_type);
+ Result Put(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+ Result Put(const std::string &path, const Params ¶ms);
+ Result Put(const std::string &path, const Headers &headers,
+ const Params ¶ms);
+ Result Patch(const std::string &path);
+ Result Patch(const std::string &path, const char *body, size_t content_length,
+ const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type);
+ Result Patch(const std::string &path, const std::string &body,
+ const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type);
+ Result Patch(const std::string &path, size_t content_length,
+ ContentProvider content_provider,
+ const std::string &content_type);
+ Result Patch(const std::string &path,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ size_t content_length, ContentProvider content_provider,
+ const std::string &content_type);
+ Result Patch(const std::string &path, const Headers &headers,
+ ContentProviderWithoutLength content_provider,
+ const std::string &content_type);
+
+ Result Delete(const std::string &path);
+ Result Delete(const std::string &path, const Headers &headers);
+ Result Delete(const std::string &path, const char *body,
+ size_t content_length, const std::string &content_type);
+ Result Delete(const std::string &path, const Headers &headers,
+ const char *body, size_t content_length,
+ const std::string &content_type);
+ Result Delete(const std::string &path, const std::string &body,
+ const std::string &content_type);
+ Result Delete(const std::string &path, const Headers &headers,
+ const std::string &body, const std::string &content_type);
+
+ Result Options(const std::string &path);
+ Result Options(const std::string &path, const Headers &headers);
+
+ bool send(Request &req, Response &res, Error &error);
+ Result send(const Request &req);
+
+ size_t is_socket_open() const;
+
+ socket_t socket() const;
+
+ void stop();
+
+ void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
+
+ void set_default_headers(Headers headers);
+
+ void set_address_family(int family);
+ void set_tcp_nodelay(bool on);
+ void set_socket_options(SocketOptions socket_options);
+
+ void set_connection_timeout(time_t sec, time_t usec = 0);
+ template <class Rep, class Period>
+ void
+ set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+ void set_read_timeout(time_t sec, time_t usec = 0);
+ template <class Rep, class Period>
+ void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+ void set_write_timeout(time_t sec, time_t usec = 0);
+ template <class Rep, class Period>
+ void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+ void set_basic_auth(const std::string &username, const std::string &password);
+ void set_bearer_token_auth(const std::string &token);
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_digest_auth(const std::string &username,
+ const std::string &password);
+#endif
+
+ void set_keep_alive(bool on);
+ void set_follow_location(bool on);
+
+ void set_url_encode(bool on);
+
+ void set_compress(bool on);
+
+ void set_decompress(bool on);
+
+ void set_interface(const std::string &intf);
+
+ void set_proxy(const std::string &host, int port);
+ void set_proxy_basic_auth(const std::string &username,
+ const std::string &password);
+ void set_proxy_bearer_token_auth(const std::string &token);
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_proxy_digest_auth(const std::string &username,
+ const std::string &password);
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void enable_server_certificate_verification(bool enabled);
+#endif
+
+ void set_logger(Logger logger);
+
+ // SSL
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_ca_cert_path(const std::string &ca_cert_file_path,
+ const std::string &ca_cert_dir_path = std::string());
+
+ void set_ca_cert_store(X509_STORE *ca_cert_store);
+
+ long get_openssl_verify_result() const;
+
+ SSL_CTX *ssl_context() const;
+#endif
+
+private:
+ std::unique_ptr<ClientImpl> cli_;
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ bool is_ssl_ = false;
+#endif
+};
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+class SSLServer : public Server {
+public:
+ SSLServer(const char *cert_path, const char *private_key_path,
+ const char *client_ca_cert_file_path = nullptr,
+ const char *client_ca_cert_dir_path = nullptr,
+ const char *private_key_password = nullptr);
+
+ SSLServer(X509 *cert, EVP_PKEY *private_key,
+ X509_STORE *client_ca_cert_store = nullptr);
+
+ SSLServer(
+ const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback);
+
+ ~SSLServer() override;
+
+ bool is_valid() const override;
+
+ SSL_CTX *ssl_context() const;
+
+private:
+ bool process_and_close_socket(socket_t sock) override;
+
+ SSL_CTX *ctx_;
+ std::mutex ctx_mutex_;
+};
+
+class SSLClient : public ClientImpl {
+public:
+ explicit SSLClient(const std::string &host);
+
+ explicit SSLClient(const std::string &host, int port);
+
+ explicit SSLClient(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
+
+ explicit SSLClient(const std::string &host, int port, X509 *client_cert,
+ EVP_PKEY *client_key);
+
+ ~SSLClient() override;
+
+ bool is_valid() const override;
+
+ void set_ca_cert_store(X509_STORE *ca_cert_store);
+
+ long get_openssl_verify_result() const;
+
+ SSL_CTX *ssl_context() const;
+
+private:
+ bool create_and_connect_socket(Socket &socket, Error &error) override;
+ void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
+ void shutdown_ssl_impl(Socket &socket, bool shutdown_socket);
+
+ bool process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback) override;
+ bool is_ssl() const override;
+
+ bool connect_with_proxy(Socket &sock, Response &res, bool &success,
+ Error &error);
+ bool initialize_ssl(Socket &socket, Error &error);
+
+ bool load_certs();
+
+ bool verify_host(X509 *server_cert) const;
+ bool verify_host_with_subject_alt_name(X509 *server_cert) const;
+ bool verify_host_with_common_name(X509 *server_cert) const;
+ bool check_host_name(const char *pattern, size_t pattern_len) const;
+
+ SSL_CTX *ctx_;
+ std::mutex ctx_mutex_;
+ std::once_flag initialize_cert_;
+
+ std::vector<std::string> host_components_;
+
+ long verify_result_ = 0;
+
+ friend class ClientImpl;
+};
+#endif
+
+/*
+ * Implementation of template methods.
+ */
+
+namespace detail {
+
+template <typename T, typename U>
+inline void duration_to_sec_and_usec(const T &duration, U callback) {
+ auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count();
+ auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
+ duration - std::chrono::seconds(sec))
+ .count();
+ callback(sec, usec);
+}
+
+template <typename T>
+inline T get_header_value(const Headers & /*headers*/,
+ const std::string & /*key*/, size_t /*id*/ = 0,
+ uint64_t /*def*/ = 0) {}
+
+template <>
+inline uint64_t get_header_value<uint64_t>(const Headers &headers,
+ const std::string &key, size_t id,
+ uint64_t def) {
+ auto rng = headers.equal_range(key);
+ auto it = rng.first;
+ std::advance(it, static_cast<ssize_t>(id));
+ if (it != rng.second) {
+ return std::strtoull(it->second.data(), nullptr, 10);
+ }
+ return def;
+}
+
+} // namespace detail
+
+template <typename T>
+inline T Request::get_header_value(const std::string &key, size_t id) const {
+ return detail::get_header_value<T>(headers, key, id, 0);
+}
+
+template <typename T>
+inline T Response::get_header_value(const std::string &key, size_t id) const {
+ return detail::get_header_value<T>(headers, key, id, 0);
+}
+
+template <typename... Args>
+inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
+ const auto bufsiz = 2048;
+ std::array<char, bufsiz> buf{};
+
+ auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...);
+ if (sn <= 0) { return sn; }
+
+ auto n = static_cast<size_t>(sn);
+
+ if (n >= buf.size() - 1) {
+ std::vector<char> glowable_buf(buf.size());
+
+ while (n >= glowable_buf.size() - 1) {
+ glowable_buf.resize(glowable_buf.size() * 2);
+ n = static_cast<size_t>(
+ snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...));
+ }
+ return write(&glowable_buf[0], n);
+ } else {
+ return write(buf.data(), n);
+ }
+}
+
+inline void default_socket_options(socket_t sock) {
+ int yes = 1;
+#ifdef _WIN32
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
+ sizeof(yes));
+ setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+ reinterpret_cast<char *>(&yes), sizeof(yes));
+#else
+#ifdef SO_REUSEPORT
+ setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<void *>(&yes),
+ sizeof(yes));
+#else
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void *>(&yes),
+ sizeof(yes));
+#endif
+#endif
+}
+
+template <class Rep, class Period>
+inline Server &
+Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
+ detail::duration_to_sec_and_usec(
+ duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
+ return *this;
+}
+
+template <class Rep, class Period>
+inline Server &
+Server::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
+ detail::duration_to_sec_and_usec(
+ duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
+ return *this;
+}
+
+template <class Rep, class Period>
+inline Server &
+Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
+ detail::duration_to_sec_and_usec(
+ duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); });
+ return *this;
+}
+
+inline std::string to_string(const Error error) {
+ switch (error) {
+ case Error::Success: return "Success";
+ case Error::Connection: return "Connection";
+ case Error::BindIPAddress: return "BindIPAddress";
+ case Error::Read: return "Read";
+ case Error::Write: return "Write";
+ case Error::ExceedRedirectCount: return "ExceedRedirectCount";
+ case Error::Canceled: return "Canceled";
+ case Error::SSLConnection: return "SSLConnection";
+ case Error::SSLLoadingCerts: return "SSLLoadingCerts";
+ case Error::SSLServerVerification: return "SSLServerVerification";
+ case Error::UnsupportedMultipartBoundaryChars:
+ return "UnsupportedMultipartBoundaryChars";
+ case Error::Compression: return "Compression";
+ case Error::ConnectionTimeout: return "ConnectionTimeout";
+ case Error::Unknown: return "Unknown";
+ default: break;
+ }
+
+ return "Invalid";
+}
+
+inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
+ os << to_string(obj);
+ os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')';
+ return os;
+}
+
+template <typename T>
+inline T Result::get_request_header_value(const std::string &key,
+ size_t id) const {
+ return detail::get_header_value<T>(request_headers_, key, id, 0);
+}
+
+template <class Rep, class Period>
+inline void ClientImpl::set_connection_timeout(
+ const std::chrono::duration<Rep, Period> &duration) {
+ detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
+ set_connection_timeout(sec, usec);
+ });
+}
+
+template <class Rep, class Period>
+inline void ClientImpl::set_read_timeout(
+ const std::chrono::duration<Rep, Period> &duration) {
+ detail::duration_to_sec_and_usec(
+ duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
+}
+
+template <class Rep, class Period>
+inline void ClientImpl::set_write_timeout(
+ const std::chrono::duration<Rep, Period> &duration) {
+ detail::duration_to_sec_and_usec(
+ duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
+}
+
+template <class Rep, class Period>
+inline void Client::set_connection_timeout(
+ const std::chrono::duration<Rep, Period> &duration) {
+ cli_->set_connection_timeout(duration);
+}
+
+template <class Rep, class Period>
+inline void
+Client::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
+ cli_->set_read_timeout(duration);
+}
+
+template <class Rep, class Period>
+inline void
+Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
+ cli_->set_write_timeout(duration);
+}
+
+/*
+ * Forward declarations and types that will be part of the .h file if split into
+ * .h + .cc.
+ */
+
+std::string hosted_at(const std::string &hostname);
+
+void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
+
+std::string append_query_params(const std::string &path, const Params ¶ms);
+
+std::pair<std::string, std::string> make_range_header(Ranges ranges);
+
+std::pair<std::string, std::string>
+make_basic_authentication_header(const std::string &username,
+ const std::string &password,
+ bool is_proxy = false);
+
+namespace detail {
+
+std::string encode_query_param(const std::string &value);
+
+std::string decode_url(const std::string &s, bool convert_plus_to_space);
+
+void read_file(const std::string &path, std::string &out);
+
+std::string trim_copy(const std::string &s);
+
+void split(const char *b, const char *e, char d,
+ std::function<void(const char *, const char *)> fn);
+
+bool process_client_socket(socket_t sock, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec,
+ std::function<bool(Stream &)> callback);
+
+socket_t create_client_socket(
+ const std::string &host, const std::string &ip, int port,
+ int address_family, bool tcp_nodelay, SocketOptions socket_options,
+ time_t connection_timeout_sec, time_t connection_timeout_usec,
+ time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, const std::string &intf, Error &error);
+
+const char *get_header_value(const Headers &headers, const std::string &key,
+ size_t id = 0, const char *def = nullptr);
+
+std::string params_to_query_str(const Params ¶ms);
+
+void parse_query_text(const std::string &s, Params ¶ms);
+
+bool parse_range_header(const std::string &s, Ranges &ranges);
+
+int close_socket(socket_t sock);
+
+ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
+
+ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
+
+enum class EncodingType { None = 0, Gzip, Brotli };
+
+EncodingType encoding_type(const Request &req, const Response &res);
+
+class BufferStream : public Stream {
+public:
+ BufferStream() = default;
+ ~BufferStream() override = default;
+
+ bool is_readable() const override;
+ bool is_writable() const override;
+ ssize_t read(char *ptr, size_t size) override;
+ ssize_t write(const char *ptr, size_t size) override;
+ void get_remote_ip_and_port(std::string &ip, int &port) const override;
+ socket_t socket() const override;
+
+ const std::string &get_buffer() const;
+
+private:
+ std::string buffer;
+ size_t position = 0;
+};
+
+class compressor {
+public:
+ virtual ~compressor() = default;
+
+ typedef std::function<bool(const char *data, size_t data_len)> Callback;
+ virtual bool compress(const char *data, size_t data_length, bool last,
+ Callback callback) = 0;
+};
+
+class decompressor {
+public:
+ virtual ~decompressor() = default;
+
+ virtual bool is_valid() const = 0;
+
+ typedef std::function<bool(const char *data, size_t data_len)> Callback;
+ virtual bool decompress(const char *data, size_t data_length,
+ Callback callback) = 0;
+};
+
+class nocompressor : public compressor {
+public:
+ virtual ~nocompressor() = default;
+
+ bool compress(const char *data, size_t data_length, bool /*last*/,
+ Callback callback) override;
+};
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+class gzip_compressor : public compressor {
+public:
+ gzip_compressor();
+ ~gzip_compressor();
+
+ bool compress(const char *data, size_t data_length, bool last,
+ Callback callback) override;
+
+private:
+ bool is_valid_ = false;
+ z_stream strm_;
+};
+
+class gzip_decompressor : public decompressor {
+public:
+ gzip_decompressor();
+ ~gzip_decompressor();
+
+ bool is_valid() const override;
+
+ bool decompress(const char *data, size_t data_length,
+ Callback callback) override;
+
+private:
+ bool is_valid_ = false;
+ z_stream strm_;
+};
+#endif
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+class brotli_compressor : public compressor {
+public:
+ brotli_compressor();
+ ~brotli_compressor();
+
+ bool compress(const char *data, size_t data_length, bool last,
+ Callback callback) override;
+
+private:
+ BrotliEncoderState *state_ = nullptr;
+};
+
+class brotli_decompressor : public decompressor {
+public:
+ brotli_decompressor();
+ ~brotli_decompressor();
+
+ bool is_valid() const override;
+
+ bool decompress(const char *data, size_t data_length,
+ Callback callback) override;
+
+private:
+ BrotliDecoderResult decoder_r;
+ BrotliDecoderState *decoder_s = nullptr;
+};
+#endif
+
+// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
+// to store data. The call can set memory on stack for performance.
+class stream_line_reader {
+public:
+ stream_line_reader(Stream &strm, char *fixed_buffer,
+ size_t fixed_buffer_size);
+ const char *ptr() const;
+ size_t size() const;
+ bool end_with_crlf() const;
+ bool getline();
+
+private:
+ void append(char c);
+
+ Stream &strm_;
+ char *fixed_buffer_;
+ const size_t fixed_buffer_size_;
+ size_t fixed_buffer_used_size_ = 0;
+ std::string glowable_buffer_;
+};
+
+} // namespace detail
+
+
+} // namespace httplib
+
+#endif // CPPHTTPLIB_HTTPLIB_H
#endif
#define ARRAY_SIZE(x) sizeof(x)/sizeof(x[0])
+#define TRUE 1
+#define FALSE 0
// Ugh, this struct is already pretty heavy.
// Will probably need to move arguments to a second buffer to support more than one.
};
} raw_event_t;
-static raw_event_t *buffer;
-static volatile int count;
-static int is_tracing = 0;
+static raw_event_t *event_buffer;
+static raw_event_t *flush_buffer;
+static volatile int event_count;
+static int is_tracing = FALSE;
+static int is_flushing = FALSE;
+static int events_in_progress = 0;
static int64_t time_offset;
static int first_line = 1;
static FILE *f;
static __thread int cur_thread_id; // Thread local storage
static int cur_process_id;
static pthread_mutex_t mutex;
+static pthread_mutex_t event_mutex;
#define STRING_POOL_SIZE 100
static char *str_pool[100];
+// forward declaration
+void mtr_flush_with_state(int);
+
// Tiny portability layer.
// Exposes:
// get_cur_thread_id()
#ifndef MTR_ENABLED
return;
#endif
- buffer = (raw_event_t *)malloc(INTERNAL_MINITRACE_BUFFER_SIZE * sizeof(raw_event_t));
+ event_buffer = (raw_event_t *)malloc(INTERNAL_MINITRACE_BUFFER_SIZE * sizeof(raw_event_t));
+ flush_buffer = (raw_event_t *)malloc(INTERNAL_MINITRACE_BUFFER_SIZE * sizeof(raw_event_t));
is_tracing = 1;
- count = 0;
+ event_count = 0;
f = (FILE *)stream;
const char *header = "{\"traceEvents\":[\n";
fwrite(header, 1, strlen(header), f);
time_offset = (uint64_t)(mtr_time_s() * 1000000);
first_line = 1;
pthread_mutex_init(&mutex, 0);
+ pthread_mutex_init(&event_mutex, 0);
}
void mtr_init(const char *json_file) {
#ifndef MTR_ENABLED
return;
#endif
- is_tracing = 0;
- mtr_flush();
+ pthread_mutex_lock(&mutex);
+ is_tracing = FALSE;
+ pthread_mutex_unlock(&mutex);
+ mtr_flush_with_state(TRUE);
+
fwrite("\n]}\n", 1, 4, f);
fclose(f);
pthread_mutex_destroy(&mutex);
+ pthread_mutex_destroy(&event_mutex);
f = 0;
- free(buffer);
- buffer = 0;
+ free(event_buffer);
+ event_buffer = 0;
for (i = 0; i < STRING_POOL_SIZE; i++) {
if (str_pool[i]) {
free(str_pool[i]);
#ifndef MTR_ENABLED
return;
#endif
- is_tracing = 1;
+ pthread_mutex_lock(&mutex);
+ is_tracing = TRUE;
+ pthread_mutex_unlock(&mutex);
}
void mtr_stop() {
#ifndef MTR_ENABLED
return;
#endif
- is_tracing = 0;
+ pthread_mutex_lock(&mutex);
+ is_tracing = FALSE;
+ pthread_mutex_unlock(&mutex);
}
// TODO: fwrite more than one line at a time.
-void mtr_flush() {
+// Flushing is thread safe and process async
+// using double-buffering mechanism.
+// Aware: only one flushing process may be
+// running at any point of time
+void mtr_flush_with_state(int is_last) {
#ifndef MTR_ENABLED
return;
#endif
char linebuf[1024];
char arg_buf[1024];
char id_buf[256];
- // We have to lock while flushing. So we really should avoid flushing as much as possible.
-
-
+ int event_count_copy = 0;
+ int events_in_progress_copy = 1;
+ raw_event_t *event_buffer_tmp = NULL;
+
+ // small critical section to swap buffers
+ // - no any new events can be spawn while
+ // swapping since they tied to the same mutex
+ // - checks for any flushing in process
pthread_mutex_lock(&mutex);
- int old_tracing = is_tracing;
- is_tracing = 0; // Stop logging even if using interlocked increments instead of the mutex. Can cause data loss.
+ // if not flushing already
+ if (is_flushing) {
+ pthread_mutex_unlock(&mutex);
+ return;
+ }
+ is_flushing = TRUE;
+ event_count_copy = event_count;
+ event_buffer_tmp = flush_buffer;
+ flush_buffer = event_buffer;
+ event_buffer = event_buffer_tmp;
+ event_count = 0;
+ // waiting for any unfinished events before swap
+ while (events_in_progress_copy != 0) {
+ pthread_mutex_lock(&event_mutex);
+ events_in_progress_copy = events_in_progress;
+ pthread_mutex_unlock(&event_mutex);
+ }
+ pthread_mutex_unlock(&mutex);
- for (i = 0; i < count; i++) {
- raw_event_t *raw = &buffer[i];
+ for (i = 0; i < event_count_copy; i++) {
+ raw_event_t *raw = &flush_buffer[i];
int len;
switch (raw->arg_type) {
case MTR_ARG_TYPE_INT:
cat, raw->pid, raw->tid, raw->ts - time_offset, raw->ph, raw->name, arg_buf, id_buf);
fwrite(linebuf, 1, len, f);
first_line = 0;
+
+ if (raw->arg_type == MTR_ARG_TYPE_STRING_COPY) {
+ free((void*)raw->a_str);
+ }
+ #ifdef MTR_COPY_EVENT_CATEGORY_AND_NAME
+ free(raw->name);
+ free(raw->cat);
+ #endif
}
- count = 0;
- is_tracing = old_tracing;
+
+ pthread_mutex_lock(&mutex);
+ is_flushing = is_last;
pthread_mutex_unlock(&mutex);
}
+void mtr_flush() {
+ mtr_flush_with_state(FALSE);
+}
+
void internal_mtr_raw_event(const char *category, const char *name, char ph, void *id) {
#ifndef MTR_ENABLED
return;
#endif
- if (!is_tracing || count >= INTERNAL_MINITRACE_BUFFER_SIZE)
+ pthread_mutex_lock(&mutex);
+ if (!is_tracing || event_count >= INTERNAL_MINITRACE_BUFFER_SIZE) {
+ pthread_mutex_unlock(&mutex);
return;
+ }
+ raw_event_t *ev = &event_buffer[event_count];
+ ++event_count;
+ pthread_mutex_lock(&event_mutex);
+ ++events_in_progress;
+ pthread_mutex_unlock(&event_mutex);
+ pthread_mutex_unlock(&mutex);
+
double ts = mtr_time_s();
if (!cur_thread_id) {
cur_thread_id = get_cur_thread_id();
cur_process_id = get_cur_process_id();
}
-#if 0 && _WIN32 // This should work, feel free to enable if you're adventurous and need performance.
- int bufPos = InterlockedExchangeAdd((LONG volatile *)&count, 1);
- raw_event_t *ev = &buffer[bufPos];
-#else
- pthread_mutex_lock(&mutex);
- raw_event_t *ev = &buffer[count];
- count++;
- pthread_mutex_unlock(&mutex);
-#endif
+#ifdef MTR_COPY_EVENT_CATEGORY_AND_NAME
+ const size_t category_len = strlen(category);
+ ev->cat = malloc(category_len + 1);
+ strcpy(ev->cat, category);
+ const size_t name_len = strlen(name);
+ ev->name = malloc(name_len + 1);
+ strcpy(ev->name, name);
+
+#else
ev->cat = category;
ev->name = name;
+#endif
+
ev->id = id;
ev->ph = ph;
if (ev->ph == 'X') {
ev->tid = cur_thread_id;
ev->pid = cur_process_id;
ev->arg_type = MTR_ARG_TYPE_NONE;
+
+ pthread_mutex_lock(&event_mutex);
+ --events_in_progress;
+ pthread_mutex_unlock(&event_mutex);
}
void internal_mtr_raw_event_arg(const char *category, const char *name, char ph, void *id, mtr_arg_type arg_type, const char *arg_name, void *arg_value) {
#ifndef MTR_ENABLED
return;
#endif
- if (!is_tracing || count >= INTERNAL_MINITRACE_BUFFER_SIZE)
+ pthread_mutex_lock(&mutex);
+ if (!is_tracing || event_count >= INTERNAL_MINITRACE_BUFFER_SIZE) {
+ pthread_mutex_unlock(&mutex);
return;
+ }
+ raw_event_t *ev = &event_buffer[event_count];
+ ++event_count;
+ pthread_mutex_lock(&event_mutex);
+ ++events_in_progress;
+ pthread_mutex_unlock(&event_mutex);
+ pthread_mutex_unlock(&mutex);
+
if (!cur_thread_id) {
cur_thread_id = get_cur_thread_id();
}
}
double ts = mtr_time_s();
-#if 0 && _WIN32 // This should work, feel free to enable if you're adventurous and need performance.
- int bufPos = InterlockedExchangeAdd((LONG volatile *)&count, 1);
- raw_event_t *ev = &buffer[bufPos];
-#else
- pthread_mutex_lock(&mutex);
- raw_event_t *ev = &buffer[count];
- count++;
- pthread_mutex_unlock(&mutex);
-#endif
+#ifdef MTR_COPY_EVENT_CATEGORY_AND_NAME
+ const size_t category_len = strlen(category);
+ ev->cat = malloc(category_len + 1);
+ strcpy(ev->cat, category);
+
+ const size_t name_len = strlen(name);
+ ev->name = malloc(name_len + 1);
+ strcpy(ev->name, name);
+#else
ev->cat = category;
ev->name = name;
+#endif
+
ev->id = id;
ev->ts = (int64_t)(ts * 1000000);
ev->ph = ph;
case MTR_ARG_TYPE_STRING_COPY: ev->a_str = strdup((const char*)arg_value); break;
case MTR_ARG_TYPE_NONE: break;
}
+
+ pthread_mutex_lock(&event_mutex);
+ --events_in_progress;
+ pthread_mutex_unlock(&event_mutex);
}
--- /dev/null
+// This version targets C++11 and later.
+//
+// Copyright (C) 2016-2020 Martin Moene.
+//
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// expected lite is based on:
+// A proposal to add a utility class to represent expected monad
+// by Vicente J. Botet Escriba and Pierre Talbot. http:://wg21.link/p0323
+
+#ifndef NONSTD_EXPECTED_LITE_HPP
+#define NONSTD_EXPECTED_LITE_HPP
+
+#define expected_lite_MAJOR 0
+#define expected_lite_MINOR 6
+#define expected_lite_PATCH 2
+
+#define expected_lite_VERSION expected_STRINGIFY(expected_lite_MAJOR) "." expected_STRINGIFY(expected_lite_MINOR) "." expected_STRINGIFY(expected_lite_PATCH)
+
+#define expected_STRINGIFY( x ) expected_STRINGIFY_( x )
+#define expected_STRINGIFY_( x ) #x
+
+// expected-lite configuration:
+
+#define nsel_EXPECTED_DEFAULT 0
+#define nsel_EXPECTED_NONSTD 1
+#define nsel_EXPECTED_STD 2
+
+// tweak header support:
+
+#ifdef __has_include
+# if __has_include(<nonstd/expected.tweak.hpp>)
+# include <nonstd/expected.tweak.hpp>
+# endif
+#define expected_HAVE_TWEAK_HEADER 1
+#else
+#define expected_HAVE_TWEAK_HEADER 0
+//# pragma message("expected.hpp: Note: Tweak header not supported.")
+#endif
+
+// expected selection and configuration:
+
+#if !defined( nsel_CONFIG_SELECT_EXPECTED )
+# define nsel_CONFIG_SELECT_EXPECTED ( nsel_HAVE_STD_EXPECTED ? nsel_EXPECTED_STD : nsel_EXPECTED_NONSTD )
+#endif
+
+// Proposal revisions:
+//
+// DXXXXR0: --
+// N4015 : -2 (2014-05-26)
+// N4109 : -1 (2014-06-29)
+// P0323R0: 0 (2016-05-28)
+// P0323R1: 1 (2016-10-12)
+// -------:
+// P0323R2: 2 (2017-06-15)
+// P0323R3: 3 (2017-10-15)
+// P0323R4: 4 (2017-11-26)
+// P0323R5: 5 (2018-02-08)
+// P0323R6: 6 (2018-04-02)
+// P0323R7: 7 (2018-06-22) *
+//
+// expected-lite uses 2 and higher
+
+#ifndef nsel_P0323R
+# define nsel_P0323R 7
+#endif
+
+// Control presence of C++ exception handling (try and auto discover):
+
+#ifndef nsel_CONFIG_NO_EXCEPTIONS
+# if defined(_MSC_VER)
+# include <cstddef> // for _HAS_EXCEPTIONS
+# endif
+# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS)
+# define nsel_CONFIG_NO_EXCEPTIONS 0
+# else
+# define nsel_CONFIG_NO_EXCEPTIONS 1
+# endif
+#endif
+
+// at default use SEH with MSVC for no C++ exceptions
+
+#ifndef nsel_CONFIG_NO_EXCEPTIONS_SEH
+# define nsel_CONFIG_NO_EXCEPTIONS_SEH ( nsel_CONFIG_NO_EXCEPTIONS && _MSC_VER )
+#endif
+
+// C++ language version detection (C++23 is speculative):
+// Note: VC14.0/1900 (VS2015) lacks too much from C++14.
+
+#ifndef nsel_CPLUSPLUS
+# if defined(_MSVC_LANG ) && !defined(__clang__)
+# define nsel_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG )
+# else
+# define nsel_CPLUSPLUS __cplusplus
+# endif
+#endif
+
+#define nsel_CPP98_OR_GREATER ( nsel_CPLUSPLUS >= 199711L )
+#define nsel_CPP11_OR_GREATER ( nsel_CPLUSPLUS >= 201103L )
+#define nsel_CPP14_OR_GREATER ( nsel_CPLUSPLUS >= 201402L )
+#define nsel_CPP17_OR_GREATER ( nsel_CPLUSPLUS >= 201703L )
+#define nsel_CPP20_OR_GREATER ( nsel_CPLUSPLUS >= 202002L )
+#define nsel_CPP23_OR_GREATER ( nsel_CPLUSPLUS >= 202300L )
+
+// Use C++23 std::expected if available and requested:
+
+#if nsel_CPP23_OR_GREATER && defined(__has_include )
+# if __has_include( <expected> )
+# define nsel_HAVE_STD_EXPECTED 1
+# else
+# define nsel_HAVE_STD_EXPECTED 0
+# endif
+#else
+# define nsel_HAVE_STD_EXPECTED 0
+#endif
+
+#define nsel_USES_STD_EXPECTED ( (nsel_CONFIG_SELECT_EXPECTED == nsel_EXPECTED_STD) || ((nsel_CONFIG_SELECT_EXPECTED == nsel_EXPECTED_DEFAULT) && nsel_HAVE_STD_EXPECTED) )
+
+//
+// in_place: code duplicated in any-lite, expected-lite, expected-lite, value-ptr-lite, variant-lite:
+//
+
+#ifndef nonstd_lite_HAVE_IN_PLACE_TYPES
+#define nonstd_lite_HAVE_IN_PLACE_TYPES 1
+
+// C++17 std::in_place in <utility>:
+
+#if nsel_CPP17_OR_GREATER
+
+#include <utility>
+
+namespace nonstd {
+
+using std::in_place;
+using std::in_place_type;
+using std::in_place_index;
+using std::in_place_t;
+using std::in_place_type_t;
+using std::in_place_index_t;
+
+#define nonstd_lite_in_place_t( T) std::in_place_t
+#define nonstd_lite_in_place_type_t( T) std::in_place_type_t<T>
+#define nonstd_lite_in_place_index_t(K) std::in_place_index_t<K>
+
+#define nonstd_lite_in_place( T) std::in_place_t{}
+#define nonstd_lite_in_place_type( T) std::in_place_type_t<T>{}
+#define nonstd_lite_in_place_index(K) std::in_place_index_t<K>{}
+
+} // namespace nonstd
+
+#else // nsel_CPP17_OR_GREATER
+
+#include <cstddef>
+
+namespace nonstd {
+namespace detail {
+
+template< class T >
+struct in_place_type_tag {};
+
+template< std::size_t K >
+struct in_place_index_tag {};
+
+} // namespace detail
+
+struct in_place_t {};
+
+template< class T >
+inline in_place_t in_place( detail::in_place_type_tag<T> = detail::in_place_type_tag<T>() )
+{
+ return in_place_t();
+}
+
+template< std::size_t K >
+inline in_place_t in_place( detail::in_place_index_tag<K> = detail::in_place_index_tag<K>() )
+{
+ return in_place_t();
+}
+
+template< class T >
+inline in_place_t in_place_type( detail::in_place_type_tag<T> = detail::in_place_type_tag<T>() )
+{
+ return in_place_t();
+}
+
+template< std::size_t K >
+inline in_place_t in_place_index( detail::in_place_index_tag<K> = detail::in_place_index_tag<K>() )
+{
+ return in_place_t();
+}
+
+// mimic templated typedef:
+
+#define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag<T> )
+#define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag<T> )
+#define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag<K> )
+
+#define nonstd_lite_in_place( T) nonstd::in_place_type<T>
+#define nonstd_lite_in_place_type( T) nonstd::in_place_type<T>
+#define nonstd_lite_in_place_index(K) nonstd::in_place_index<K>
+
+} // namespace nonstd
+
+#endif // nsel_CPP17_OR_GREATER
+#endif // nonstd_lite_HAVE_IN_PLACE_TYPES
+
+//
+// Using std::expected:
+//
+
+#if nsel_USES_STD_EXPECTED
+
+#include <expected>
+
+namespace nonstd {
+
+ using std::expected;
+// ...
+}
+
+#else // nsel_USES_STD_EXPECTED
+
+#include <cassert>
+#include <exception>
+#include <functional>
+#include <initializer_list>
+#include <memory>
+#include <new>
+#include <system_error>
+#include <type_traits>
+#include <utility>
+
+// additional includes:
+
+#if nsel_CONFIG_NO_EXCEPTIONS
+# if nsel_CONFIG_NO_EXCEPTIONS_SEH
+# include <windows.h> // for ExceptionCodes
+# else
+// already included: <cassert>
+# endif
+#else
+# include <stdexcept>
+#endif
+
+// C++ feature usage:
+
+#if nsel_CPP11_OR_GREATER
+# define nsel_constexpr constexpr
+#else
+# define nsel_constexpr /*constexpr*/
+#endif
+
+#if nsel_CPP14_OR_GREATER
+# define nsel_constexpr14 constexpr
+#else
+# define nsel_constexpr14 /*constexpr*/
+#endif
+
+#if nsel_CPP17_OR_GREATER
+# define nsel_inline17 inline
+#else
+# define nsel_inline17 /*inline*/
+#endif
+
+// Compiler versions:
+//
+// MSVC++ 6.0 _MSC_VER == 1200 nsel_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0)
+// MSVC++ 7.0 _MSC_VER == 1300 nsel_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002)
+// MSVC++ 7.1 _MSC_VER == 1310 nsel_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003)
+// MSVC++ 8.0 _MSC_VER == 1400 nsel_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005)
+// MSVC++ 9.0 _MSC_VER == 1500 nsel_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008)
+// MSVC++ 10.0 _MSC_VER == 1600 nsel_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010)
+// MSVC++ 11.0 _MSC_VER == 1700 nsel_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012)
+// MSVC++ 12.0 _MSC_VER == 1800 nsel_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013)
+// MSVC++ 14.0 _MSC_VER == 1900 nsel_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015)
+// MSVC++ 14.1 _MSC_VER >= 1910 nsel_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017)
+// MSVC++ 14.2 _MSC_VER >= 1920 nsel_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019)
+
+#if defined(_MSC_VER) && !defined(__clang__)
+# define nsel_COMPILER_MSVC_VER (_MSC_VER )
+# define nsel_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900)) )
+#else
+# define nsel_COMPILER_MSVC_VER 0
+# define nsel_COMPILER_MSVC_VERSION 0
+#endif
+
+#define nsel_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) )
+
+#if defined(__clang__)
+# define nsel_COMPILER_CLANG_VERSION nsel_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
+#else
+# define nsel_COMPILER_CLANG_VERSION 0
+#endif
+
+#if defined(__GNUC__) && !defined(__clang__)
+# define nsel_COMPILER_GNUC_VERSION nsel_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
+#else
+# define nsel_COMPILER_GNUC_VERSION 0
+#endif
+
+// half-open range [lo..hi):
+//#define nsel_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) )
+
+// Method enabling
+
+#define nsel_REQUIRES_0(...) \
+ template< bool B = (__VA_ARGS__), typename std::enable_if<B, int>::type = 0 >
+
+#define nsel_REQUIRES_T(...) \
+ , typename std::enable_if< (__VA_ARGS__), int >::type = 0
+
+#define nsel_REQUIRES_R(R, ...) \
+ typename std::enable_if< (__VA_ARGS__), R>::type
+
+#define nsel_REQUIRES_A(...) \
+ , typename std::enable_if< (__VA_ARGS__), void*>::type = nullptr
+
+// Presence of language and library features:
+
+#ifdef _HAS_CPP0X
+# define nsel_HAS_CPP0X _HAS_CPP0X
+#else
+# define nsel_HAS_CPP0X 0
+#endif
+
+//#define nsel_CPP11_140 (nsel_CPP11_OR_GREATER || nsel_COMPILER_MSVC_VER >= 1900)
+
+// Clang, GNUC, MSVC warning suppression macros:
+
+#ifdef __clang__
+# pragma clang diagnostic push
+#elif defined __GNUC__
+# pragma GCC diagnostic push
+#endif // __clang__
+
+#if nsel_COMPILER_MSVC_VERSION >= 140
+# pragma warning( push )
+# define nsel_DISABLE_MSVC_WARNINGS(codes) __pragma( warning(disable: codes) )
+#else
+# define nsel_DISABLE_MSVC_WARNINGS(codes)
+#endif
+
+#ifdef __clang__
+# define nsel_RESTORE_WARNINGS() _Pragma("clang diagnostic pop")
+#elif defined __GNUC__
+# define nsel_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop")
+#elif nsel_COMPILER_MSVC_VERSION >= 140
+# define nsel_RESTORE_WARNINGS() __pragma( warning( pop ) )
+#else
+# define nsel_RESTORE_WARNINGS()
+#endif
+
+// Suppress the following MSVC (GSL) warnings:
+// - C26409: Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11)
+
+nsel_DISABLE_MSVC_WARNINGS( 26409 )
+
+//
+// expected:
+//
+
+namespace nonstd { namespace expected_lite {
+
+// type traits C++17:
+
+namespace std17 {
+
+#if nsel_CPP17_OR_GREATER
+
+using std::conjunction;
+using std::is_swappable;
+using std::is_nothrow_swappable;
+
+#else // nsel_CPP17_OR_GREATER
+
+namespace detail {
+
+using std::swap;
+
+struct is_swappable
+{
+ template< typename T, typename = decltype( swap( std::declval<T&>(), std::declval<T&>() ) ) >
+ static std::true_type test( int /* unused */);
+
+ template< typename >
+ static std::false_type test(...);
+};
+
+struct is_nothrow_swappable
+{
+ // wrap noexcept(expr) in separate function as work-around for VC140 (VS2015):
+
+ template< typename T >
+ static constexpr bool satisfies()
+ {
+ return noexcept( swap( std::declval<T&>(), std::declval<T&>() ) );
+ }
+
+ template< typename T >
+ static auto test( int ) -> std::integral_constant<bool, satisfies<T>()>{}
+
+ template< typename >
+ static auto test(...) -> std::false_type;
+};
+} // namespace detail
+
+// is [nothow] swappable:
+
+template< typename T >
+struct is_swappable : decltype( detail::is_swappable::test<T>(0) ){};
+
+template< typename T >
+struct is_nothrow_swappable : decltype( detail::is_nothrow_swappable::test<T>(0) ){};
+
+// conjunction:
+
+template< typename... > struct conjunction : std::true_type{};
+template< typename B1 > struct conjunction<B1> : B1{};
+
+template< typename B1, typename... Bn >
+struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type{};
+
+#endif // nsel_CPP17_OR_GREATER
+
+} // namespace std17
+
+// type traits C++20:
+
+namespace std20 {
+
+#if defined(__cpp_lib_remove_cvref)
+
+using std::remove_cvref;
+
+#else
+
+template< typename T >
+struct remove_cvref
+{
+ typedef typename std::remove_cv< typename std::remove_reference<T>::type >::type type;
+};
+
+#endif
+
+} // namespace std20
+
+// forward declaration:
+
+template< typename T, typename E >
+class expected;
+
+namespace detail {
+
+/// discriminated union to hold value or 'error'.
+
+template< typename T, typename E >
+class storage_t_impl
+{
+ template< typename, typename > friend class nonstd::expected_lite::expected;
+
+public:
+ using value_type = T;
+ using error_type = E;
+
+ // no-op construction
+ storage_t_impl() {}
+ ~storage_t_impl() {}
+
+ explicit storage_t_impl( bool has_value )
+ : m_has_value( has_value )
+ {}
+
+ void construct_value( value_type const & e )
+ {
+ new( &m_value ) value_type( e );
+ }
+
+ void construct_value( value_type && e )
+ {
+ new( &m_value ) value_type( std::move( e ) );
+ }
+
+ template< class... Args >
+ void emplace_value( Args&&... args )
+ {
+ new( &m_value ) value_type( std::forward<Args>(args)...);
+ }
+
+ template< class U, class... Args >
+ void emplace_value( std::initializer_list<U> il, Args&&... args )
+ {
+ new( &m_value ) value_type( il, std::forward<Args>(args)... );
+ }
+
+ void destruct_value()
+ {
+ m_value.~value_type();
+ }
+
+ void construct_error( error_type const & e )
+ {
+ new( &m_error ) error_type( e );
+ }
+
+ void construct_error( error_type && e )
+ {
+ new( &m_error ) error_type( std::move( e ) );
+ }
+
+ template< class... Args >
+ void emplace_error( Args&&... args )
+ {
+ new( &m_error ) error_type( std::forward<Args>(args)...);
+ }
+
+ template< class U, class... Args >
+ void emplace_error( std::initializer_list<U> il, Args&&... args )
+ {
+ new( &m_error ) error_type( il, std::forward<Args>(args)... );
+ }
+
+ void destruct_error()
+ {
+ m_error.~error_type();
+ }
+
+ constexpr value_type const & value() const &
+ {
+ return m_value;
+ }
+
+ value_type & value() &
+ {
+ return m_value;
+ }
+
+ constexpr value_type const && value() const &&
+ {
+ return std::move( m_value );
+ }
+
+ nsel_constexpr14 value_type && value() &&
+ {
+ return std::move( m_value );
+ }
+
+ value_type const * value_ptr() const
+ {
+ return &m_value;
+ }
+
+ value_type * value_ptr()
+ {
+ return &m_value;
+ }
+
+ error_type const & error() const &
+ {
+ return m_error;
+ }
+
+ error_type & error() &
+ {
+ return m_error;
+ }
+
+ constexpr error_type const && error() const &&
+ {
+ return std::move( m_error );
+ }
+
+ nsel_constexpr14 error_type && error() &&
+ {
+ return std::move( m_error );
+ }
+
+ bool has_value() const
+ {
+ return m_has_value;
+ }
+
+ void set_has_value( bool v )
+ {
+ m_has_value = v;
+ }
+
+private:
+ union
+ {
+ value_type m_value;
+ error_type m_error;
+ };
+
+ bool m_has_value = false;
+};
+
+/// discriminated union to hold only 'error'.
+
+template< typename E >
+struct storage_t_impl<void, E>
+{
+ template< typename, typename > friend class nonstd::expected_lite::expected;
+
+public:
+ using value_type = void;
+ using error_type = E;
+
+ // no-op construction
+ storage_t_impl() {}
+ ~storage_t_impl() {}
+
+ explicit storage_t_impl( bool has_value )
+ : m_has_value( has_value )
+ {}
+
+ void construct_error( error_type const & e )
+ {
+ new( &m_error ) error_type( e );
+ }
+
+ void construct_error( error_type && e )
+ {
+ new( &m_error ) error_type( std::move( e ) );
+ }
+
+ template< class... Args >
+ void emplace_error( Args&&... args )
+ {
+ new( &m_error ) error_type( std::forward<Args>(args)...);
+ }
+
+ template< class U, class... Args >
+ void emplace_error( std::initializer_list<U> il, Args&&... args )
+ {
+ new( &m_error ) error_type( il, std::forward<Args>(args)... );
+ }
+
+ void destruct_error()
+ {
+ m_error.~error_type();
+ }
+
+ error_type const & error() const &
+ {
+ return m_error;
+ }
+
+ error_type & error() &
+ {
+ return m_error;
+ }
+
+ constexpr error_type const && error() const &&
+ {
+ return std::move( m_error );
+ }
+
+ nsel_constexpr14 error_type && error() &&
+ {
+ return std::move( m_error );
+ }
+
+ bool has_value() const
+ {
+ return m_has_value;
+ }
+
+ void set_has_value( bool v )
+ {
+ m_has_value = v;
+ }
+
+private:
+ union
+ {
+ char m_dummy;
+ error_type m_error;
+ };
+
+ bool m_has_value = false;
+};
+
+template< typename T, typename E, bool isConstructable, bool isMoveable >
+class storage_t
+{
+public:
+ storage_t() = default;
+ ~storage_t() = default;
+
+ explicit storage_t( bool has_value )
+ : storage_t_impl<T, E>( has_value )
+ {}
+
+ storage_t( storage_t const & other ) = delete;
+ storage_t( storage_t && other ) = delete;
+};
+
+template< typename T, typename E >
+class storage_t<T, E, true, true> : public storage_t_impl<T, E>
+{
+public:
+ storage_t() = default;
+ ~storage_t() = default;
+
+ explicit storage_t( bool has_value )
+ : storage_t_impl<T, E>( has_value )
+ {}
+
+ storage_t( storage_t const & other )
+ : storage_t_impl<T, E>( other.has_value() )
+ {
+ if ( this->has_value() ) this->construct_value( other.value() );
+ else this->construct_error( other.error() );
+ }
+
+ storage_t(storage_t && other )
+ : storage_t_impl<T, E>( other.has_value() )
+ {
+ if ( this->has_value() ) this->construct_value( std::move( other.value() ) );
+ else this->construct_error( std::move( other.error() ) );
+ }
+};
+
+template< typename E >
+class storage_t<void, E, true, true> : public storage_t_impl<void, E>
+{
+public:
+ storage_t() = default;
+ ~storage_t() = default;
+
+ explicit storage_t( bool has_value )
+ : storage_t_impl<void, E>( has_value )
+ {}
+
+ storage_t( storage_t const & other )
+ : storage_t_impl<void, E>( other.has_value() )
+ {
+ if ( this->has_value() ) ;
+ else this->construct_error( other.error() );
+ }
+
+ storage_t(storage_t && other )
+ : storage_t_impl<void, E>( other.has_value() )
+ {
+ if ( this->has_value() ) ;
+ else this->construct_error( std::move( other.error() ) );
+ }
+};
+
+template< typename T, typename E >
+class storage_t<T, E, true, false> : public storage_t_impl<T, E>
+{
+public:
+ storage_t() = default;
+ ~storage_t() = default;
+
+ explicit storage_t( bool has_value )
+ : storage_t_impl<T, E>( has_value )
+ {}
+
+ storage_t( storage_t const & other )
+ : storage_t_impl<T, E>(other.has_value())
+ {
+ if ( this->has_value() ) this->construct_value( other.value() );
+ else this->construct_error( other.error() );
+ }
+
+ storage_t( storage_t && other ) = delete;
+};
+
+template< typename E >
+class storage_t<void, E, true, false> : public storage_t_impl<void, E>
+{
+public:
+ storage_t() = default;
+ ~storage_t() = default;
+
+ explicit storage_t( bool has_value )
+ : storage_t_impl<void, E>( has_value )
+ {}
+
+ storage_t( storage_t const & other )
+ : storage_t_impl<void, E>(other.has_value())
+ {
+ if ( this->has_value() ) ;
+ else this->construct_error( other.error() );
+ }
+
+ storage_t( storage_t && other ) = delete;
+};
+
+template< typename T, typename E >
+class storage_t<T, E, false, true> : public storage_t_impl<T, E>
+{
+public:
+ storage_t() = default;
+ ~storage_t() = default;
+
+ explicit storage_t( bool has_value )
+ : storage_t_impl<T, E>( has_value )
+ {}
+
+ storage_t( storage_t const & other ) = delete;
+
+ storage_t( storage_t && other )
+ : storage_t_impl<T, E>( other.has_value() )
+ {
+ if ( this->has_value() ) this->construct_value( std::move( other.value() ) );
+ else this->construct_error( std::move( other.error() ) );
+ }
+};
+
+template< typename E >
+class storage_t<void, E, false, true> : public storage_t_impl<void, E>
+{
+public:
+ storage_t() = default;
+ ~storage_t() = default;
+
+ explicit storage_t( bool has_value )
+ : storage_t_impl<void, E>( has_value )
+ {}
+
+ storage_t( storage_t const & other ) = delete;
+
+ storage_t( storage_t && other )
+ : storage_t_impl<void, E>( other.has_value() )
+ {
+ if ( this->has_value() ) ;
+ else this->construct_error( std::move( other.error() ) );
+ }
+};
+
+} // namespace detail
+
+/// x.x.5 Unexpected object type; unexpected_type; C++17 and later can also use aliased type unexpected.
+
+#if nsel_P0323R <= 2
+template< typename E = std::exception_ptr >
+class unexpected_type
+#else
+template< typename E >
+class unexpected_type
+#endif // nsel_P0323R
+{
+public:
+ using error_type = E;
+
+ // x.x.5.2.1 Constructors
+
+// unexpected_type() = delete;
+
+ constexpr unexpected_type( unexpected_type const & ) = default;
+ constexpr unexpected_type( unexpected_type && ) = default;
+
+ template< typename... Args
+ nsel_REQUIRES_T(
+ std::is_constructible<E, Args&&...>::value
+ )
+ >
+ constexpr explicit unexpected_type( nonstd_lite_in_place_t(E), Args &&... args )
+ : m_error( std::forward<Args>( args )...)
+ {}
+
+ template< typename U, typename... Args
+ nsel_REQUIRES_T(
+ std::is_constructible<E, std::initializer_list<U>, Args&&...>::value
+ )
+ >
+ constexpr explicit unexpected_type( nonstd_lite_in_place_t(E), std::initializer_list<U> il, Args &&... args )
+ : m_error( il, std::forward<Args>( args )...)
+ {}
+
+ template< typename E2
+ nsel_REQUIRES_T(
+ std::is_constructible<E,E2>::value
+ && !std::is_same< typename std20::remove_cvref<E2>::type, nonstd_lite_in_place_t(E2) >::value
+ && !std::is_same< typename std20::remove_cvref<E2>::type, unexpected_type >::value
+ )
+ >
+ constexpr explicit unexpected_type( E2 && error )
+ : m_error( std::forward<E2>( error ) )
+ {}
+
+ template< typename E2
+ nsel_REQUIRES_T(
+ std::is_constructible< E, E2>::value
+ && !std::is_constructible<E, unexpected_type<E2> & >::value
+ && !std::is_constructible<E, unexpected_type<E2> >::value
+ && !std::is_constructible<E, unexpected_type<E2> const & >::value
+ && !std::is_constructible<E, unexpected_type<E2> const >::value
+ && !std::is_convertible< unexpected_type<E2> &, E>::value
+ && !std::is_convertible< unexpected_type<E2> , E>::value
+ && !std::is_convertible< unexpected_type<E2> const &, E>::value
+ && !std::is_convertible< unexpected_type<E2> const , E>::value
+ && !std::is_convertible< E2 const &, E>::value /*=> explicit */
+ )
+ >
+ constexpr explicit unexpected_type( unexpected_type<E2> const & error )
+ : m_error( E{ error.value() } )
+ {}
+
+ template< typename E2
+ nsel_REQUIRES_T(
+ std::is_constructible< E, E2>::value
+ && !std::is_constructible<E, unexpected_type<E2> & >::value
+ && !std::is_constructible<E, unexpected_type<E2> >::value
+ && !std::is_constructible<E, unexpected_type<E2> const & >::value
+ && !std::is_constructible<E, unexpected_type<E2> const >::value
+ && !std::is_convertible< unexpected_type<E2> &, E>::value
+ && !std::is_convertible< unexpected_type<E2> , E>::value
+ && !std::is_convertible< unexpected_type<E2> const &, E>::value
+ && !std::is_convertible< unexpected_type<E2> const , E>::value
+ && std::is_convertible< E2 const &, E>::value /*=> explicit */
+ )
+ >
+ constexpr /*non-explicit*/ unexpected_type( unexpected_type<E2> const & error )
+ : m_error( error.value() )
+ {}
+
+ template< typename E2
+ nsel_REQUIRES_T(
+ std::is_constructible< E, E2>::value
+ && !std::is_constructible<E, unexpected_type<E2> & >::value
+ && !std::is_constructible<E, unexpected_type<E2> >::value
+ && !std::is_constructible<E, unexpected_type<E2> const & >::value
+ && !std::is_constructible<E, unexpected_type<E2> const >::value
+ && !std::is_convertible< unexpected_type<E2> &, E>::value
+ && !std::is_convertible< unexpected_type<E2> , E>::value
+ && !std::is_convertible< unexpected_type<E2> const &, E>::value
+ && !std::is_convertible< unexpected_type<E2> const , E>::value
+ && !std::is_convertible< E2 const &, E>::value /*=> explicit */
+ )
+ >
+ constexpr explicit unexpected_type( unexpected_type<E2> && error )
+ : m_error( E{ std::move( error.value() ) } )
+ {}
+
+ template< typename E2
+ nsel_REQUIRES_T(
+ std::is_constructible< E, E2>::value
+ && !std::is_constructible<E, unexpected_type<E2> & >::value
+ && !std::is_constructible<E, unexpected_type<E2> >::value
+ && !std::is_constructible<E, unexpected_type<E2> const & >::value
+ && !std::is_constructible<E, unexpected_type<E2> const >::value
+ && !std::is_convertible< unexpected_type<E2> &, E>::value
+ && !std::is_convertible< unexpected_type<E2> , E>::value
+ && !std::is_convertible< unexpected_type<E2> const &, E>::value
+ && !std::is_convertible< unexpected_type<E2> const , E>::value
+ && std::is_convertible< E2 const &, E>::value /*=> non-explicit */
+ )
+ >
+ constexpr /*non-explicit*/ unexpected_type( unexpected_type<E2> && error )
+ : m_error( std::move( error.value() ) )
+ {}
+
+ // x.x.5.2.2 Assignment
+
+ nsel_constexpr14 unexpected_type& operator=( unexpected_type const & ) = default;
+ nsel_constexpr14 unexpected_type& operator=( unexpected_type && ) = default;
+
+ template< typename E2 = E >
+ nsel_constexpr14 unexpected_type & operator=( unexpected_type<E2> const & other )
+ {
+ unexpected_type{ other.value() }.swap( *this );
+ return *this;
+ }
+
+ template< typename E2 = E >
+ nsel_constexpr14 unexpected_type & operator=( unexpected_type<E2> && other )
+ {
+ unexpected_type{ std::move( other.value() ) }.swap( *this );
+ return *this;
+ }
+
+ // x.x.5.2.3 Observers
+
+ nsel_constexpr14 E & value() & noexcept
+ {
+ return m_error;
+ }
+
+ constexpr E const & value() const & noexcept
+ {
+ return m_error;
+ }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+ nsel_constexpr14 E && value() && noexcept
+ {
+ return std::move( m_error );
+ }
+
+ constexpr E const && value() const && noexcept
+ {
+ return std::move( m_error );
+ }
+
+#endif
+
+ // x.x.5.2.4 Swap
+
+ nsel_REQUIRES_R( void,
+ std17::is_swappable<E>::value
+ )
+ swap( unexpected_type & other ) noexcept (
+ std17::is_nothrow_swappable<E>::value
+ )
+ {
+ using std::swap;
+ swap( m_error, other.m_error );
+ }
+
+ // TODO: ??? unexpected_type: in-class friend operator==, !=
+
+private:
+ error_type m_error;
+};
+
+#if nsel_CPP17_OR_GREATER
+
+/// template deduction guide:
+
+template< typename E >
+unexpected_type( E ) -> unexpected_type< E >;
+
+#endif
+
+/// class unexpected_type, std::exception_ptr specialization (P0323R2)
+
+#if !nsel_CONFIG_NO_EXCEPTIONS
+#if nsel_P0323R <= 2
+
+// TODO: Should expected be specialized for particular E types such as exception_ptr and how?
+// See p0323r7 2.1. Ergonomics, http://wg21.link/p0323
+template<>
+class unexpected_type< std::exception_ptr >
+{
+public:
+ using error_type = std::exception_ptr;
+
+ unexpected_type() = delete;
+
+ ~unexpected_type(){}
+
+ explicit unexpected_type( std::exception_ptr const & error )
+ : m_error( error )
+ {}
+
+ explicit unexpected_type(std::exception_ptr && error )
+ : m_error( std::move( error ) )
+ {}
+
+ template< typename E >
+ explicit unexpected_type( E error )
+ : m_error( std::make_exception_ptr( error ) )
+ {}
+
+ std::exception_ptr const & value() const
+ {
+ return m_error;
+ }
+
+ std::exception_ptr & value()
+ {
+ return m_error;
+ }
+
+private:
+ std::exception_ptr m_error;
+};
+
+#endif // nsel_P0323R
+#endif // !nsel_CONFIG_NO_EXCEPTIONS
+
+/// x.x.4, Unexpected equality operators
+
+template< typename E1, typename E2 >
+constexpr bool operator==( unexpected_type<E1> const & x, unexpected_type<E2> const & y )
+{
+ return x.value() == y.value();
+}
+
+template< typename E1, typename E2 >
+constexpr bool operator!=( unexpected_type<E1> const & x, unexpected_type<E2> const & y )
+{
+ return ! ( x == y );
+}
+
+#if nsel_P0323R <= 2
+
+template< typename E >
+constexpr bool operator<( unexpected_type<E> const & x, unexpected_type<E> const & y )
+{
+ return x.value() < y.value();
+}
+
+template< typename E >
+constexpr bool operator>( unexpected_type<E> const & x, unexpected_type<E> const & y )
+{
+ return ( y < x );
+}
+
+template< typename E >
+constexpr bool operator<=( unexpected_type<E> const & x, unexpected_type<E> const & y )
+{
+ return ! ( y < x );
+}
+
+template< typename E >
+constexpr bool operator>=( unexpected_type<E> const & x, unexpected_type<E> const & y )
+{
+ return ! ( x < y );
+}
+
+#endif // nsel_P0323R
+
+/// x.x.5 Specialized algorithms
+
+template< typename E
+ nsel_REQUIRES_T(
+ std17::is_swappable<E>::value
+ )
+>
+void swap( unexpected_type<E> & x, unexpected_type<E> & y) noexcept ( noexcept ( x.swap(y) ) )
+{
+ x.swap( y );
+}
+
+#if nsel_P0323R <= 2
+
+// unexpected: relational operators for std::exception_ptr:
+
+inline constexpr bool operator<( unexpected_type<std::exception_ptr> const & /*x*/, unexpected_type<std::exception_ptr> const & /*y*/ )
+{
+ return false;
+}
+
+inline constexpr bool operator>( unexpected_type<std::exception_ptr> const & /*x*/, unexpected_type<std::exception_ptr> const & /*y*/ )
+{
+ return false;
+}
+
+inline constexpr bool operator<=( unexpected_type<std::exception_ptr> const & x, unexpected_type<std::exception_ptr> const & y )
+{
+ return ( x == y );
+}
+
+inline constexpr bool operator>=( unexpected_type<std::exception_ptr> const & x, unexpected_type<std::exception_ptr> const & y )
+{
+ return ( x == y );
+}
+
+#endif // nsel_P0323R
+
+// unexpected: traits
+
+#if nsel_P0323R <= 3
+
+template< typename E>
+struct is_unexpected : std::false_type {};
+
+template< typename E>
+struct is_unexpected< unexpected_type<E> > : std::true_type {};
+
+#endif // nsel_P0323R
+
+// unexpected: factory
+
+// keep make_unexpected() removed in p0323r2 for pre-C++17:
+
+template< typename E>
+nsel_constexpr14 auto
+make_unexpected( E && value ) -> unexpected_type< typename std::decay<E>::type >
+{
+ return unexpected_type< typename std::decay<E>::type >( std::forward<E>(value) );
+}
+
+#if nsel_P0323R <= 3
+
+/*nsel_constexpr14*/ auto inline
+make_unexpected_from_current_exception() -> unexpected_type< std::exception_ptr >
+{
+ return unexpected_type< std::exception_ptr >( std::current_exception() );
+}
+
+#endif // nsel_P0323R
+
+/// x.x.6, x.x.7 expected access error
+
+template< typename E >
+class bad_expected_access;
+
+/// x.x.7 bad_expected_access<void>: expected access error
+
+template <>
+class bad_expected_access< void > : public std::exception
+{
+public:
+ explicit bad_expected_access()
+ : std::exception()
+ {}
+};
+
+/// x.x.6 bad_expected_access: expected access error
+
+#if !nsel_CONFIG_NO_EXCEPTIONS
+
+template< typename E >
+class bad_expected_access : public bad_expected_access< void >
+{
+public:
+ using error_type = E;
+
+ explicit bad_expected_access( error_type error )
+ : m_error( error )
+ {}
+
+ virtual char const * what() const noexcept override
+ {
+ return "bad_expected_access";
+ }
+
+ nsel_constexpr14 error_type & error() &
+ {
+ return m_error;
+ }
+
+ constexpr error_type const & error() const &
+ {
+ return m_error;
+ }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+ nsel_constexpr14 error_type && error() &&
+ {
+ return std::move( m_error );
+ }
+
+ constexpr error_type const && error() const &&
+ {
+ return std::move( m_error );
+ }
+
+#endif
+
+private:
+ error_type m_error;
+};
+
+#endif // nsel_CONFIG_NO_EXCEPTIONS
+
+/// x.x.8 unexpect tag, in_place_unexpected tag: construct an error
+
+struct unexpect_t{};
+using in_place_unexpected_t = unexpect_t;
+
+nsel_inline17 constexpr unexpect_t unexpect{};
+nsel_inline17 constexpr unexpect_t in_place_unexpected{};
+
+/// class error_traits
+
+#if nsel_CONFIG_NO_EXCEPTIONS
+
+namespace detail {
+ inline bool text( char const * /*text*/ ) { return true; }
+}
+
+template< typename Error >
+struct error_traits
+{
+ static void rethrow( Error const & /*e*/ )
+ {
+#if nsel_CONFIG_NO_EXCEPTIONS_SEH
+ RaiseException( EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, NULL );
+#else
+ assert( false && detail::text("throw bad_expected_access<Error>{ e };") );
+#endif
+ }
+};
+
+template<>
+struct error_traits< std::exception_ptr >
+{
+ static void rethrow( std::exception_ptr const & /*e*/ )
+ {
+#if nsel_CONFIG_NO_EXCEPTIONS_SEH
+ RaiseException( EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, NULL );
+#else
+ assert( false && detail::text("throw bad_expected_access<std::exception_ptr>{ e };") );
+#endif
+ }
+};
+
+template<>
+struct error_traits< std::error_code >
+{
+ static void rethrow( std::error_code const & /*e*/ )
+ {
+#if nsel_CONFIG_NO_EXCEPTIONS_SEH
+ RaiseException( EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, NULL );
+#else
+ assert( false && detail::text("throw std::system_error( e );") );
+#endif
+ }
+};
+
+#else // nsel_CONFIG_NO_EXCEPTIONS
+
+template< typename Error >
+struct error_traits
+{
+ static void rethrow( Error const & e )
+ {
+ throw bad_expected_access<Error>{ e };
+ }
+};
+
+template<>
+struct error_traits< std::exception_ptr >
+{
+ static void rethrow( std::exception_ptr const & e )
+ {
+ std::rethrow_exception( e );
+ }
+};
+
+template<>
+struct error_traits< std::error_code >
+{
+ static void rethrow( std::error_code const & e )
+ {
+ throw std::system_error( e );
+ }
+};
+
+#endif // nsel_CONFIG_NO_EXCEPTIONS
+
+} // namespace expected_lite
+
+// provide nonstd::unexpected_type:
+
+using expected_lite::unexpected_type;
+
+namespace expected_lite {
+
+/// class expected
+
+#if nsel_P0323R <= 2
+template< typename T, typename E = std::exception_ptr >
+class expected
+#else
+template< typename T, typename E >
+class expected
+#endif // nsel_P0323R
+{
+private:
+ template< typename, typename > friend class expected;
+
+public:
+ using value_type = T;
+ using error_type = E;
+ using unexpected_type = nonstd::unexpected_type<E>;
+
+ template< typename U >
+ struct rebind
+ {
+ using type = expected<U, error_type>;
+ };
+
+ // x.x.4.1 constructors
+
+ nsel_REQUIRES_0(
+ std::is_default_constructible<T>::value
+ )
+ nsel_constexpr14 expected()
+ : contained( true )
+ {
+ contained.construct_value( value_type() );
+ }
+
+ nsel_constexpr14 expected( expected const & ) = default;
+ nsel_constexpr14 expected( expected && ) = default;
+
+ template< typename U, typename G
+ nsel_REQUIRES_T(
+ std::is_constructible< T, U const &>::value
+ && std::is_constructible<E, G const &>::value
+ && !std::is_constructible<T, expected<U, G> & >::value
+ && !std::is_constructible<T, expected<U, G> && >::value
+ && !std::is_constructible<T, expected<U, G> const & >::value
+ && !std::is_constructible<T, expected<U, G> const && >::value
+ && !std::is_convertible< expected<U, G> & , T>::value
+ && !std::is_convertible< expected<U, G> &&, T>::value
+ && !std::is_convertible< expected<U, G> const & , T>::value
+ && !std::is_convertible< expected<U, G> const &&, T>::value
+ && (!std::is_convertible<U const &, T>::value || !std::is_convertible<G const &, E>::value ) /*=> explicit */
+ )
+ >
+ nsel_constexpr14 explicit expected( expected<U, G> const & other )
+ : contained( other.has_value() )
+ {
+ if ( has_value() ) contained.construct_value( T{ other.contained.value() } );
+ else contained.construct_error( E{ other.contained.error() } );
+ }
+
+ template< typename U, typename G
+ nsel_REQUIRES_T(
+ std::is_constructible< T, U const &>::value
+ && std::is_constructible<E, G const &>::value
+ && !std::is_constructible<T, expected<U, G> & >::value
+ && !std::is_constructible<T, expected<U, G> && >::value
+ && !std::is_constructible<T, expected<U, G> const & >::value
+ && !std::is_constructible<T, expected<U, G> const && >::value
+ && !std::is_convertible< expected<U, G> & , T>::value
+ && !std::is_convertible< expected<U, G> &&, T>::value
+ && !std::is_convertible< expected<U, G> const &, T>::value
+ && !std::is_convertible< expected<U, G> const &&, T>::value
+ && !(!std::is_convertible<U const &, T>::value || !std::is_convertible<G const &, E>::value ) /*=> non-explicit */
+ )
+ >
+ nsel_constexpr14 /*non-explicit*/ expected( expected<U, G> const & other )
+ : contained( other.has_value() )
+ {
+ if ( has_value() ) contained.construct_value( other.contained.value() );
+ else contained.construct_error( other.contained.error() );
+ }
+
+ template< typename U, typename G
+ nsel_REQUIRES_T(
+ std::is_constructible< T, U>::value
+ && std::is_constructible<E, G>::value
+ && !std::is_constructible<T, expected<U, G> & >::value
+ && !std::is_constructible<T, expected<U, G> && >::value
+ && !std::is_constructible<T, expected<U, G> const & >::value
+ && !std::is_constructible<T, expected<U, G> const && >::value
+ && !std::is_convertible< expected<U, G> & , T>::value
+ && !std::is_convertible< expected<U, G> &&, T>::value
+ && !std::is_convertible< expected<U, G> const & , T>::value
+ && !std::is_convertible< expected<U, G> const &&, T>::value
+ && (!std::is_convertible<U, T>::value || !std::is_convertible<G, E>::value ) /*=> explicit */
+ )
+ >
+ nsel_constexpr14 explicit expected( expected<U, G> && other )
+ : contained( other.has_value() )
+ {
+ if ( has_value() ) contained.construct_value( T{ std::move( other.contained.value() ) } );
+ else contained.construct_error( E{ std::move( other.contained.error() ) } );
+ }
+
+ template< typename U, typename G
+ nsel_REQUIRES_T(
+ std::is_constructible< T, U>::value
+ && std::is_constructible<E, G>::value
+ && !std::is_constructible<T, expected<U, G> & >::value
+ && !std::is_constructible<T, expected<U, G> && >::value
+ && !std::is_constructible<T, expected<U, G> const & >::value
+ && !std::is_constructible<T, expected<U, G> const && >::value
+ && !std::is_convertible< expected<U, G> & , T>::value
+ && !std::is_convertible< expected<U, G> &&, T>::value
+ && !std::is_convertible< expected<U, G> const & , T>::value
+ && !std::is_convertible< expected<U, G> const &&, T>::value
+ && !(!std::is_convertible<U, T>::value || !std::is_convertible<G, E>::value ) /*=> non-explicit */
+ )
+ >
+ nsel_constexpr14 /*non-explicit*/ expected( expected<U, G> && other )
+ : contained( other.has_value() )
+ {
+ if ( has_value() ) contained.construct_value( std::move( other.contained.value() ) );
+ else contained.construct_error( std::move( other.contained.error() ) );
+ }
+
+ template< typename U = T
+ nsel_REQUIRES_T(
+ std::is_copy_constructible<U>::value
+ )
+ >
+ nsel_constexpr14 expected( value_type const & value )
+ : contained( true )
+ {
+ contained.construct_value( value );
+ }
+
+ template< typename U = T
+ nsel_REQUIRES_T(
+ std::is_constructible<T,U&&>::value
+ && !std::is_same<typename std20::remove_cvref<U>::type, nonstd_lite_in_place_t(U)>::value
+ && !std::is_same< expected<T,E> , typename std20::remove_cvref<U>::type>::value
+ && !std::is_same<nonstd::unexpected_type<E>, typename std20::remove_cvref<U>::type>::value
+ && !std::is_convertible<U&&,T>::value /*=> explicit */
+ )
+ >
+ nsel_constexpr14 explicit expected( U && value ) noexcept
+ (
+ std::is_nothrow_move_constructible<U>::value &&
+ std::is_nothrow_move_constructible<E>::value
+ )
+ : contained( true )
+ {
+ contained.construct_value( T{ std::forward<U>( value ) } );
+ }
+
+ template< typename U = T
+ nsel_REQUIRES_T(
+ std::is_constructible<T,U&&>::value
+ && !std::is_same<typename std20::remove_cvref<U>::type, nonstd_lite_in_place_t(U)>::value
+ && !std::is_same< expected<T,E> , typename std20::remove_cvref<U>::type>::value
+ && !std::is_same<nonstd::unexpected_type<E>, typename std20::remove_cvref<U>::type>::value
+ && std::is_convertible<U&&,T>::value /*=> non-explicit */
+ )
+ >
+ nsel_constexpr14 /*non-explicit*/ expected( U && value ) noexcept
+ (
+ std::is_nothrow_move_constructible<U>::value &&
+ std::is_nothrow_move_constructible<E>::value
+ )
+ : contained( true )
+ {
+ contained.construct_value( std::forward<U>( value ) );
+ }
+
+ // construct error:
+
+ template< typename G = E
+ nsel_REQUIRES_T(
+ std::is_constructible<E, G const & >::value
+ && !std::is_convertible< G const &, E>::value /*=> explicit */
+ )
+ >
+ nsel_constexpr14 explicit expected( nonstd::unexpected_type<G> const & error )
+ : contained( false )
+ {
+ contained.construct_error( E{ error.value() } );
+ }
+
+ template< typename G = E
+ nsel_REQUIRES_T(
+ std::is_constructible<E, G const & >::value
+ && std::is_convertible< G const &, E>::value /*=> non-explicit */
+ )
+ >
+ nsel_constexpr14 /*non-explicit*/ expected( nonstd::unexpected_type<G> const & error )
+ : contained( false )
+ {
+ contained.construct_error( error.value() );
+ }
+
+ template< typename G = E
+ nsel_REQUIRES_T(
+ std::is_constructible<E, G&& >::value
+ && !std::is_convertible< G&&, E>::value /*=> explicit */
+ )
+ >
+ nsel_constexpr14 explicit expected( nonstd::unexpected_type<G> && error )
+ : contained( false )
+ {
+ contained.construct_error( E{ std::move( error.value() ) } );
+ }
+
+ template< typename G = E
+ nsel_REQUIRES_T(
+ std::is_constructible<E, G&& >::value
+ && std::is_convertible< G&&, E>::value /*=> non-explicit */
+ )
+ >
+ nsel_constexpr14 /*non-explicit*/ expected( nonstd::unexpected_type<G> && error )
+ : contained( false )
+ {
+ contained.construct_error( std::move( error.value() ) );
+ }
+
+ // in-place construction, value
+
+ template< typename... Args
+ nsel_REQUIRES_T(
+ std::is_constructible<T, Args&&...>::value
+ )
+ >
+ nsel_constexpr14 explicit expected( nonstd_lite_in_place_t(T), Args&&... args )
+ : contained( true )
+ {
+ contained.emplace_value( std::forward<Args>( args )... );
+ }
+
+ template< typename U, typename... Args
+ nsel_REQUIRES_T(
+ std::is_constructible<T, std::initializer_list<U>, Args&&...>::value
+ )
+ >
+ nsel_constexpr14 explicit expected( nonstd_lite_in_place_t(T), std::initializer_list<U> il, Args&&... args )
+ : contained( true )
+ {
+ contained.emplace_value( il, std::forward<Args>( args )... );
+ }
+
+ // in-place construction, error
+
+ template< typename... Args
+ nsel_REQUIRES_T(
+ std::is_constructible<E, Args&&...>::value
+ )
+ >
+ nsel_constexpr14 explicit expected( unexpect_t, Args&&... args )
+ : contained( false )
+ {
+ contained.emplace_error( std::forward<Args>( args )... );
+ }
+
+ template< typename U, typename... Args
+ nsel_REQUIRES_T(
+ std::is_constructible<E, std::initializer_list<U>, Args&&...>::value
+ )
+ >
+ nsel_constexpr14 explicit expected( unexpect_t, std::initializer_list<U> il, Args&&... args )
+ : contained( false )
+ {
+ contained.emplace_error( il, std::forward<Args>( args )... );
+ }
+
+ // x.x.4.2 destructor
+
+ // TODO: ~expected: triviality
+ // Effects: If T is not cv void and is_trivially_destructible_v<T> is false and bool(*this), calls val.~T(). If is_trivially_destructible_v<E> is false and !bool(*this), calls unexpect.~unexpected<E>().
+ // Remarks: If either T is cv void or is_trivially_destructible_v<T> is true, and is_trivially_destructible_v<E> is true, then this destructor shall be a trivial destructor.
+
+ ~expected()
+ {
+ if ( has_value() ) contained.destruct_value();
+ else contained.destruct_error();
+ }
+
+ // x.x.4.3 assignment
+
+ expected & operator=( expected const & other )
+ {
+ expected( other ).swap( *this );
+ return *this;
+ }
+
+ expected & operator=( expected && other ) noexcept
+ (
+ std::is_nothrow_move_constructible< T>::value
+ && std::is_nothrow_move_assignable< T>::value
+ && std::is_nothrow_move_constructible<E>::value // added for missing
+ && std::is_nothrow_move_assignable< E>::value ) // nothrow above
+ {
+ expected( std::move( other ) ).swap( *this );
+ return *this;
+ }
+
+ template< typename U
+ nsel_REQUIRES_T(
+ !std::is_same<expected<T,E>, typename std20::remove_cvref<U>::type>::value
+ && std17::conjunction<std::is_scalar<T>, std::is_same<T, std::decay<U>> >::value
+ && std::is_constructible<T ,U>::value
+ && std::is_assignable< T&,U>::value
+ && std::is_nothrow_move_constructible<E>::value )
+ >
+ expected & operator=( U && value )
+ {
+ expected( std::forward<U>( value ) ).swap( *this );
+ return *this;
+ }
+
+ template< typename G = E
+ nsel_REQUIRES_T(
+ std::is_constructible<E, G const&>::value &&
+ std::is_copy_constructible<G>::value // TODO: std::is_nothrow_copy_constructible<G>
+ && std::is_copy_assignable<G>::value
+ )
+ >
+ expected & operator=( nonstd::unexpected_type<G> const & error )
+ {
+ expected( unexpect, error.value() ).swap( *this );
+ return *this;
+ }
+
+ template< typename G = E
+ nsel_REQUIRES_T(
+ std::is_constructible<E, G&&>::value &&
+ std::is_move_constructible<G>::value // TODO: std::is_nothrow_move_constructible<G>
+ && std::is_move_assignable<G>::value
+ )
+ >
+ expected & operator=( nonstd::unexpected_type<G> && error )
+ {
+ expected( unexpect, std::move( error.value() ) ).swap( *this );
+ return *this;
+ }
+
+ template< typename... Args
+ nsel_REQUIRES_T(
+ std::is_nothrow_constructible<T, Args&&...>::value
+ )
+ >
+ value_type & emplace( Args &&... args )
+ {
+ expected( nonstd_lite_in_place(T), std::forward<Args>(args)... ).swap( *this );
+ return value();
+ }
+
+ template< typename U, typename... Args
+ nsel_REQUIRES_T(
+ std::is_nothrow_constructible<T, std::initializer_list<U>&, Args&&...>::value
+ )
+ >
+ value_type & emplace( std::initializer_list<U> il, Args &&... args )
+ {
+ expected( nonstd_lite_in_place(T), il, std::forward<Args>(args)... ).swap( *this );
+ return value();
+ }
+
+ // x.x.4.4 swap
+
+ template< typename U=T, typename G=E >
+ nsel_REQUIRES_R( void,
+ std17::is_swappable< U>::value
+ && std17::is_swappable<G>::value
+ && ( std::is_move_constructible<U>::value || std::is_move_constructible<G>::value )
+ )
+ swap( expected & other ) noexcept
+ (
+ std::is_nothrow_move_constructible<T>::value && std17::is_nothrow_swappable<T&>::value &&
+ std::is_nothrow_move_constructible<E>::value && std17::is_nothrow_swappable<E&>::value
+ )
+ {
+ using std::swap;
+
+ if ( bool(*this) && bool(other) ) { swap( contained.value(), other.contained.value() ); }
+ else if ( ! bool(*this) && ! bool(other) ) { swap( contained.error(), other.contained.error() ); }
+ else if ( bool(*this) && ! bool(other) ) { error_type t( std::move( other.error() ) );
+ other.contained.destruct_error();
+ other.contained.construct_value( std::move( contained.value() ) );
+ contained.destruct_value();
+ contained.construct_error( std::move( t ) );
+ bool has_value = contained.has_value();
+ bool other_has_value = other.has_value();
+ other.contained.set_has_value(has_value);
+ contained.set_has_value(other_has_value);
+ }
+ else if ( ! bool(*this) && bool(other) ) { other.swap( *this ); }
+ }
+
+ // x.x.4.5 observers
+
+ constexpr value_type const * operator ->() const
+ {
+ return assert( has_value() ), contained.value_ptr();
+ }
+
+ value_type * operator ->()
+ {
+ return assert( has_value() ), contained.value_ptr();
+ }
+
+ constexpr value_type const & operator *() const &
+ {
+ return assert( has_value() ), contained.value();
+ }
+
+ value_type & operator *() &
+ {
+ return assert( has_value() ), contained.value();
+ }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+ constexpr value_type const && operator *() const &&
+ {
+ return std::move( ( assert( has_value() ), contained.value() ) );
+ }
+
+ nsel_constexpr14 value_type && operator *() &&
+ {
+ return std::move( ( assert( has_value() ), contained.value() ) );
+ }
+
+#endif
+
+ constexpr explicit operator bool() const noexcept
+ {
+ return has_value();
+ }
+
+ constexpr bool has_value() const noexcept
+ {
+ return contained.has_value();
+ }
+
+ constexpr value_type const & value() const &
+ {
+ return has_value()
+ ? ( contained.value() )
+ : ( error_traits<error_type>::rethrow( contained.error() ), contained.value() );
+ }
+
+ value_type & value() &
+ {
+ return has_value()
+ ? ( contained.value() )
+ : ( error_traits<error_type>::rethrow( contained.error() ), contained.value() );
+ }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+ constexpr value_type const && value() const &&
+ {
+ return std::move( has_value()
+ ? ( contained.value() )
+ : ( error_traits<error_type>::rethrow( contained.error() ), contained.value() ) );
+ }
+
+ nsel_constexpr14 value_type && value() &&
+ {
+ return std::move( has_value()
+ ? ( contained.value() )
+ : ( error_traits<error_type>::rethrow( contained.error() ), contained.value() ) );
+ }
+
+#endif
+
+ constexpr error_type const & error() const &
+ {
+ return assert( ! has_value() ), contained.error();
+ }
+
+ error_type & error() &
+ {
+ return assert( ! has_value() ), contained.error();
+ }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+ constexpr error_type const && error() const &&
+ {
+ return std::move( ( assert( ! has_value() ), contained.error() ) );
+ }
+
+ error_type && error() &&
+ {
+ return std::move( ( assert( ! has_value() ), contained.error() ) );
+ }
+
+#endif
+
+ constexpr unexpected_type get_unexpected() const
+ {
+ return make_unexpected( contained.error() );
+ }
+
+ template< typename Ex >
+ bool has_exception() const
+ {
+ using ContainedEx = typename std::remove_reference< decltype( get_unexpected().value() ) >::type;
+ return ! has_value() && std::is_base_of< Ex, ContainedEx>::value;
+ }
+
+ template< typename U
+ nsel_REQUIRES_T(
+ std::is_copy_constructible< T>::value
+ && std::is_convertible<U&&, T>::value
+ )
+ >
+ value_type value_or( U && v ) const &
+ {
+ return has_value()
+ ? contained.value()
+ : static_cast<T>( std::forward<U>( v ) );
+ }
+
+ template< typename U
+ nsel_REQUIRES_T(
+ std::is_move_constructible< T>::value
+ && std::is_convertible<U&&, T>::value
+ )
+ >
+ value_type value_or( U && v ) &&
+ {
+ return has_value()
+ ? std::move( contained.value() )
+ : static_cast<T>( std::forward<U>( v ) );
+ }
+
+ // unwrap()
+
+// template <class U, class E>
+// constexpr expected<U,E> expected<expected<U,E>,E>::unwrap() const&;
+
+// template <class T, class E>
+// constexpr expected<T,E> expected<T,E>::unwrap() const&;
+
+// template <class U, class E>
+// expected<U,E> expected<expected<U,E>, E>::unwrap() &&;
+
+// template <class T, class E>
+// template expected<T,E> expected<T,E>::unwrap() &&;
+
+ // factories
+
+// template< typename Ex, typename F>
+// expected<T,E> catch_exception(F&& f);
+
+// template< typename F>
+// expected<decltype(func(declval<T>())),E> map(F&& func) ;
+
+// template< typename F>
+// 'see below' bind(F&& func);
+
+// template< typename F>
+// expected<T,E> catch_error(F&& f);
+
+// template< typename F>
+// 'see below' then(F&& func);
+
+private:
+ detail::storage_t
+ <
+ T
+ ,E
+ , std::is_copy_constructible<T>::value && std::is_copy_constructible<E>::value
+ , std::is_move_constructible<T>::value && std::is_move_constructible<E>::value
+ >
+ contained;
+};
+
+/// class expected, void specialization
+
+template< typename E >
+class expected<void, E>
+{
+private:
+ template< typename, typename > friend class expected;
+
+public:
+ using value_type = void;
+ using error_type = E;
+ using unexpected_type = nonstd::unexpected_type<E>;
+
+ // x.x.4.1 constructors
+
+ constexpr expected() noexcept
+ : contained( true )
+ {}
+
+ nsel_constexpr14 expected( expected const & other ) = default;
+ nsel_constexpr14 expected( expected && other ) = default;
+
+ constexpr explicit expected( nonstd_lite_in_place_t(void) )
+ : contained( true )
+ {}
+
+ template< typename G = E
+ nsel_REQUIRES_T(
+ !std::is_convertible<G const &, E>::value /*=> explicit */
+ )
+ >
+ nsel_constexpr14 explicit expected( nonstd::unexpected_type<G> const & error )
+ : contained( false )
+ {
+ contained.construct_error( E{ error.value() } );
+ }
+
+ template< typename G = E
+ nsel_REQUIRES_T(
+ std::is_convertible<G const &, E>::value /*=> non-explicit */
+ )
+ >
+ nsel_constexpr14 /*non-explicit*/ expected( nonstd::unexpected_type<G> const & error )
+ : contained( false )
+ {
+ contained.construct_error( error.value() );
+ }
+
+ template< typename G = E
+ nsel_REQUIRES_T(
+ !std::is_convertible<G&&, E>::value /*=> explicit */
+ )
+ >
+ nsel_constexpr14 explicit expected( nonstd::unexpected_type<G> && error )
+ : contained( false )
+ {
+ contained.construct_error( E{ std::move( error.value() ) } );
+ }
+
+ template< typename G = E
+ nsel_REQUIRES_T(
+ std::is_convertible<G&&, E>::value /*=> non-explicit */
+ )
+ >
+ nsel_constexpr14 /*non-explicit*/ expected( nonstd::unexpected_type<G> && error )
+ : contained( false )
+ {
+ contained.construct_error( std::move( error.value() ) );
+ }
+
+ template< typename... Args
+ nsel_REQUIRES_T(
+ std::is_constructible<E, Args&&...>::value
+ )
+ >
+ nsel_constexpr14 explicit expected( unexpect_t, Args&&... args )
+ : contained( false )
+ {
+ contained.emplace_error( std::forward<Args>( args )... );
+ }
+
+ template< typename U, typename... Args
+ nsel_REQUIRES_T(
+ std::is_constructible<E, std::initializer_list<U>, Args&&...>::value
+ )
+ >
+ nsel_constexpr14 explicit expected( unexpect_t, std::initializer_list<U> il, Args&&... args )
+ : contained( false )
+ {
+ contained.emplace_error( il, std::forward<Args>( args )... );
+ }
+
+ // destructor
+
+ ~expected()
+ {
+ if ( ! has_value() )
+ {
+ contained.destruct_error();
+ }
+ }
+
+ // x.x.4.3 assignment
+
+ expected & operator=( expected const & other )
+ {
+ expected( other ).swap( *this );
+ return *this;
+ }
+
+ expected & operator=( expected && other ) noexcept
+ (
+ std::is_nothrow_move_assignable<E>::value &&
+ std::is_nothrow_move_constructible<E>::value )
+ {
+ expected( std::move( other ) ).swap( *this );
+ return *this;
+ }
+
+ void emplace()
+ {
+ expected().swap( *this );
+ }
+
+ // x.x.4.4 swap
+
+ template< typename G = E >
+ nsel_REQUIRES_R( void,
+ std17::is_swappable<G>::value
+ && std::is_move_constructible<G>::value
+ )
+ swap( expected & other ) noexcept
+ (
+ std::is_nothrow_move_constructible<E>::value && std17::is_nothrow_swappable<E&>::value
+ )
+ {
+ using std::swap;
+
+ if ( ! bool(*this) && ! bool(other) ) { swap( contained.error(), other.contained.error() ); }
+ else if ( bool(*this) && ! bool(other) ) { contained.construct_error( std::move( other.error() ) );
+ bool has_value = contained.has_value();
+ bool other_has_value = other.has_value();
+ other.contained.set_has_value(has_value);
+ contained.set_has_value(other_has_value);
+ }
+ else if ( ! bool(*this) && bool(other) ) { other.swap( *this ); }
+ }
+
+ // x.x.4.5 observers
+
+ constexpr explicit operator bool() const noexcept
+ {
+ return has_value();
+ }
+
+ constexpr bool has_value() const noexcept
+ {
+ return contained.has_value();
+ }
+
+ void value() const
+ {
+ if ( ! has_value() )
+ {
+ error_traits<error_type>::rethrow( contained.error() );
+ }
+ }
+
+ constexpr error_type const & error() const &
+ {
+ return assert( ! has_value() ), contained.error();
+ }
+
+ error_type & error() &
+ {
+ return assert( ! has_value() ), contained.error();
+ }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+ constexpr error_type const && error() const &&
+ {
+ return std::move( ( assert( ! has_value() ), contained.error() ) );
+ }
+
+ error_type && error() &&
+ {
+ return std::move( ( assert( ! has_value() ), contained.error() ) );
+ }
+
+#endif
+
+ constexpr unexpected_type get_unexpected() const
+ {
+ return make_unexpected( contained.error() );
+ }
+
+ template< typename Ex >
+ bool has_exception() const
+ {
+ using ContainedEx = typename std::remove_reference< decltype( get_unexpected().value() ) >::type;
+ return ! has_value() && std::is_base_of< Ex, ContainedEx>::value;
+ }
+
+// template constexpr 'see below' unwrap() const&;
+//
+// template 'see below' unwrap() &&;
+
+ // factories
+
+// template< typename Ex, typename F>
+// expected<void,E> catch_exception(F&& f);
+//
+// template< typename F>
+// expected<decltype(func()), E> map(F&& func) ;
+//
+// template< typename F>
+// 'see below' bind(F&& func) ;
+//
+// template< typename F>
+// expected<void,E> catch_error(F&& f);
+//
+// template< typename F>
+// 'see below' then(F&& func);
+
+private:
+ detail::storage_t
+ <
+ void
+ , E
+ , std::is_copy_constructible<E>::value
+ , std::is_move_constructible<E>::value
+ >
+ contained;
+};
+
+// x.x.4.6 expected<>: comparison operators
+
+template< typename T1, typename E1, typename T2, typename E2 >
+constexpr bool operator==( expected<T1,E1> const & x, expected<T2,E2> const & y )
+{
+ return bool(x) != bool(y) ? false : bool(x) == false ? x.error() == y.error() : *x == *y;
+}
+
+template< typename T1, typename E1, typename T2, typename E2 >
+constexpr bool operator!=( expected<T1,E1> const & x, expected<T2,E2> const & y )
+{
+ return !(x == y);
+}
+
+template< typename E1, typename E2 >
+constexpr bool operator==( expected<void,E1> const & x, expected<void,E1> const & y )
+{
+ return bool(x) != bool(y) ? false : bool(x) == false ? x.error() == y.error() : true;
+}
+
+#if nsel_P0323R <= 2
+
+template< typename T, typename E >
+constexpr bool operator<( expected<T,E> const & x, expected<T,E> const & y )
+{
+ return (!y) ? false : (!x) ? true : *x < *y;
+}
+
+template< typename T, typename E >
+constexpr bool operator>( expected<T,E> const & x, expected<T,E> const & y )
+{
+ return (y < x);
+}
+
+template< typename T, typename E >
+constexpr bool operator<=( expected<T,E> const & x, expected<T,E> const & y )
+{
+ return !(y < x);
+}
+
+template< typename T, typename E >
+constexpr bool operator>=( expected<T,E> const & x, expected<T,E> const & y )
+{
+ return !(x < y);
+}
+
+#endif
+
+// x.x.4.7 expected: comparison with T
+
+template< typename T1, typename E1, typename T2 >
+constexpr bool operator==( expected<T1,E1> const & x, T2 const & v )
+{
+ return bool(x) ? *x == v : false;
+}
+
+template< typename T1, typename E1, typename T2 >
+constexpr bool operator==(T2 const & v, expected<T1,E1> const & x )
+{
+ return bool(x) ? v == *x : false;
+}
+
+template< typename T1, typename E1, typename T2 >
+constexpr bool operator!=( expected<T1,E1> const & x, T2 const & v )
+{
+ return bool(x) ? *x != v : true;
+}
+
+template< typename T1, typename E1, typename T2 >
+constexpr bool operator!=( T2 const & v, expected<T1,E1> const & x )
+{
+ return bool(x) ? v != *x : true;
+}
+
+#if nsel_P0323R <= 2
+
+template< typename T, typename E >
+constexpr bool operator<( expected<T,E> const & x, T const & v )
+{
+ return bool(x) ? *x < v : true;
+}
+
+template< typename T, typename E >
+constexpr bool operator<( T const & v, expected<T,E> const & x )
+{
+ return bool(x) ? v < *x : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator>( T const & v, expected<T,E> const & x )
+{
+ return bool(x) ? *x < v : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator>( expected<T,E> const & x, T const & v )
+{
+ return bool(x) ? v < *x : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator<=( T const & v, expected<T,E> const & x )
+{
+ return bool(x) ? ! ( *x < v ) : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator<=( expected<T,E> const & x, T const & v )
+{
+ return bool(x) ? ! ( v < *x ) : true;
+}
+
+template< typename T, typename E >
+constexpr bool operator>=( expected<T,E> const & x, T const & v )
+{
+ return bool(x) ? ! ( *x < v ) : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator>=( T const & v, expected<T,E> const & x )
+{
+ return bool(x) ? ! ( v < *x ) : true;
+}
+
+#endif // nsel_P0323R
+
+// x.x.4.8 expected: comparison with unexpected_type
+
+template< typename T1, typename E1 , typename E2 >
+constexpr bool operator==( expected<T1,E1> const & x, unexpected_type<E2> const & u )
+{
+ return (!x) ? x.get_unexpected() == u : false;
+}
+
+template< typename T1, typename E1 , typename E2 >
+constexpr bool operator==( unexpected_type<E2> const & u, expected<T1,E1> const & x )
+{
+ return ( x == u );
+}
+
+template< typename T1, typename E1 , typename E2 >
+constexpr bool operator!=( expected<T1,E1> const & x, unexpected_type<E2> const & u )
+{
+ return ! ( x == u );
+}
+
+template< typename T1, typename E1 , typename E2 >
+constexpr bool operator!=( unexpected_type<E2> const & u, expected<T1,E1> const & x )
+{
+ return ! ( x == u );
+}
+
+#if nsel_P0323R <= 2
+
+template< typename T, typename E >
+constexpr bool operator<( expected<T,E> const & x, unexpected_type<E> const & u )
+{
+ return (!x) ? ( x.get_unexpected() < u ) : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator<( unexpected_type<E> const & u, expected<T,E> const & x )
+{
+ return (!x) ? ( u < x.get_unexpected() ) : true ;
+}
+
+template< typename T, typename E >
+constexpr bool operator>( expected<T,E> const & x, unexpected_type<E> const & u )
+{
+ return ( u < x );
+}
+
+template< typename T, typename E >
+constexpr bool operator>( unexpected_type<E> const & u, expected<T,E> const & x )
+{
+ return ( x < u );
+}
+
+template< typename T, typename E >
+constexpr bool operator<=( expected<T,E> const & x, unexpected_type<E> const & u )
+{
+ return ! ( u < x );
+}
+
+template< typename T, typename E >
+constexpr bool operator<=( unexpected_type<E> const & u, expected<T,E> const & x)
+{
+ return ! ( x < u );
+}
+
+template< typename T, typename E >
+constexpr bool operator>=( expected<T,E> const & x, unexpected_type<E> const & u )
+{
+ return ! ( u > x );
+}
+
+template< typename T, typename E >
+constexpr bool operator>=( unexpected_type<E> const & u, expected<T,E> const & x )
+{
+ return ! ( x > u );
+}
+
+#endif // nsel_P0323R
+
+/// x.x.x Specialized algorithms
+
+template< typename T, typename E
+ nsel_REQUIRES_T(
+ ( std::is_void<T>::value || std::is_move_constructible<T>::value )
+ && std::is_move_constructible<E>::value
+ && std17::is_swappable<T>::value
+ && std17::is_swappable<E>::value )
+>
+void swap( expected<T,E> & x, expected<T,E> & y ) noexcept ( noexcept ( x.swap(y) ) )
+{
+ x.swap( y );
+}
+
+#if nsel_P0323R <= 3
+
+template< typename T >
+constexpr auto make_expected( T && v ) -> expected< typename std::decay<T>::type >
+{
+ return expected< typename std::decay<T>::type >( std::forward<T>( v ) );
+}
+
+// expected<void> specialization:
+
+auto inline make_expected() -> expected<void>
+{
+ return expected<void>( in_place );
+}
+
+template< typename T >
+constexpr auto make_expected_from_current_exception() -> expected<T>
+{
+ return expected<T>( make_unexpected_from_current_exception() );
+}
+
+template< typename T >
+auto make_expected_from_exception( std::exception_ptr v ) -> expected<T>
+{
+ return expected<T>( unexpected_type<std::exception_ptr>( std::forward<std::exception_ptr>( v ) ) );
+}
+
+template< typename T, typename E >
+constexpr auto make_expected_from_error( E e ) -> expected<T, typename std::decay<E>::type>
+{
+ return expected<T, typename std::decay<E>::type>( make_unexpected( e ) );
+}
+
+template< typename F
+ nsel_REQUIRES_T( ! std::is_same<typename std::result_of<F()>::type, void>::value )
+>
+/*nsel_constexpr14*/
+auto make_expected_from_call( F f ) -> expected< typename std::result_of<F()>::type >
+{
+ try
+ {
+ return make_expected( f() );
+ }
+ catch (...)
+ {
+ return make_unexpected_from_current_exception();
+ }
+}
+
+template< typename F
+ nsel_REQUIRES_T( std::is_same<typename std::result_of<F()>::type, void>::value )
+>
+/*nsel_constexpr14*/
+auto make_expected_from_call( F f ) -> expected<void>
+{
+ try
+ {
+ f();
+ return make_expected();
+ }
+ catch (...)
+ {
+ return make_unexpected_from_current_exception();
+ }
+}
+
+#endif // nsel_P0323R
+
+} // namespace expected_lite
+
+using namespace expected_lite;
+
+// using expected_lite::expected;
+// using ...
+
+} // namespace nonstd
+
+namespace std {
+
+// expected: hash support
+
+template< typename T, typename E >
+struct hash< nonstd::expected<T,E> >
+{
+ using result_type = std::size_t;
+ using argument_type = nonstd::expected<T,E>;
+
+ constexpr result_type operator()(argument_type const & arg) const
+ {
+ return arg ? std::hash<T>{}(*arg) : result_type{};
+ }
+};
+
+// TBD - ?? remove? see spec.
+template< typename T, typename E >
+struct hash< nonstd::expected<T&,E> >
+{
+ using result_type = std::size_t;
+ using argument_type = nonstd::expected<T&,E>;
+
+ constexpr result_type operator()(argument_type const & arg) const
+ {
+ return arg ? std::hash<T>{}(*arg) : result_type{};
+ }
+};
+
+// TBD - implement
+// bool(e), hash<expected<void,E>>()(e) shall evaluate to the hashing true;
+// otherwise it evaluates to an unspecified value if E is exception_ptr or
+// a combination of hashing false and hash<E>()(e.error()).
+
+template< typename E >
+struct hash< nonstd::expected<void,E> >
+{
+};
+
+} // namespace std
+
+namespace nonstd {
+
+// void unexpected() is deprecated && removed in C++17
+
+#if nsel_CPP17_OR_GREATER || nsel_COMPILER_MSVC_VERSION > 141
+template< typename E >
+using unexpected = unexpected_type<E>;
+#endif
+
+} // namespace nonstd
+
+#undef nsel_REQUIRES
+#undef nsel_REQUIRES_0
+#undef nsel_REQUIRES_T
+
+nsel_RESTORE_WARNINGS()
+
+#endif // nsel_USES_STD_EXPECTED
+
+#endif // NONSTD_EXPECTED_LITE_HPP
+++ /dev/null
-//
-// Copyright (c) 2014-2018 Martin Moene
-//
-// https://github.com/martinmoene/optional-lite
-//
-// Distributed under the Boost Software License, Version 1.0.
-// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-
-#pragma once
-
-#ifndef NONSTD_OPTIONAL_LITE_HPP
-#define NONSTD_OPTIONAL_LITE_HPP
-
-#define optional_lite_MAJOR 3
-#define optional_lite_MINOR 4
-#define optional_lite_PATCH 0
-
-#define optional_lite_VERSION optional_STRINGIFY(optional_lite_MAJOR) "." optional_STRINGIFY(optional_lite_MINOR) "." optional_STRINGIFY(optional_lite_PATCH)
-
-#define optional_STRINGIFY( x ) optional_STRINGIFY_( x )
-#define optional_STRINGIFY_( x ) #x
-
-// optional-lite configuration:
-
-#define optional_OPTIONAL_DEFAULT 0
-#define optional_OPTIONAL_NONSTD 1
-#define optional_OPTIONAL_STD 2
-
-// tweak header support:
-
-#ifdef __has_include
-# if __has_include(<nonstd/optional.tweak.hpp>)
-# include <nonstd/optional.tweak.hpp>
-# endif
-#define optional_HAVE_TWEAK_HEADER 1
-#else
-#define optional_HAVE_TWEAK_HEADER 0
-//# pragma message("optional.hpp: Note: Tweak header not supported.")
-#endif
-
-// optional selection and configuration:
-
-#if !defined( optional_CONFIG_SELECT_OPTIONAL )
-# define optional_CONFIG_SELECT_OPTIONAL ( optional_HAVE_STD_OPTIONAL ? optional_OPTIONAL_STD : optional_OPTIONAL_NONSTD )
-#endif
-
-// Control presence of exception handling (try and auto discover):
-
-#ifndef optional_CONFIG_NO_EXCEPTIONS
-# if _MSC_VER
-# include <cstddef> // for _HAS_EXCEPTIONS
-# endif
-# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS)
-# define optional_CONFIG_NO_EXCEPTIONS 0
-# else
-# define optional_CONFIG_NO_EXCEPTIONS 1
-# endif
-#endif
-
-// C++ language version detection (C++20 is speculative):
-// Note: VC14.0/1900 (VS2015) lacks too much from C++14.
-
-#ifndef optional_CPLUSPLUS
-# if defined(_MSVC_LANG ) && !defined(__clang__)
-# define optional_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG )
-# else
-# define optional_CPLUSPLUS __cplusplus
-# endif
-#endif
-
-#define optional_CPP98_OR_GREATER ( optional_CPLUSPLUS >= 199711L )
-#define optional_CPP11_OR_GREATER ( optional_CPLUSPLUS >= 201103L )
-#define optional_CPP11_OR_GREATER_ ( optional_CPLUSPLUS >= 201103L )
-#define optional_CPP14_OR_GREATER ( optional_CPLUSPLUS >= 201402L )
-#define optional_CPP17_OR_GREATER ( optional_CPLUSPLUS >= 201703L )
-#define optional_CPP20_OR_GREATER ( optional_CPLUSPLUS >= 202000L )
-
-// C++ language version (represent 98 as 3):
-
-#define optional_CPLUSPLUS_V ( optional_CPLUSPLUS / 100 - (optional_CPLUSPLUS > 200000 ? 2000 : 1994) )
-
-// Use C++17 std::optional if available and requested:
-
-#if optional_CPP17_OR_GREATER && defined(__has_include )
-# if __has_include( <optional> )
-# define optional_HAVE_STD_OPTIONAL 1
-# else
-# define optional_HAVE_STD_OPTIONAL 0
-# endif
-#else
-# define optional_HAVE_STD_OPTIONAL 0
-#endif
-
-#define optional_USES_STD_OPTIONAL ( (optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_STD) || ((optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_DEFAULT) && optional_HAVE_STD_OPTIONAL) )
-
-//
-// in_place: code duplicated in any-lite, expected-lite, optional-lite, value-ptr-lite, variant-lite:
-//
-
-#ifndef nonstd_lite_HAVE_IN_PLACE_TYPES
-#define nonstd_lite_HAVE_IN_PLACE_TYPES 1
-
-// C++17 std::in_place in <utility>:
-
-#if optional_CPP17_OR_GREATER
-
-#include <utility>
-
-namespace nonstd {
-
-using std::in_place;
-using std::in_place_type;
-using std::in_place_index;
-using std::in_place_t;
-using std::in_place_type_t;
-using std::in_place_index_t;
-
-#define nonstd_lite_in_place_t( T) std::in_place_t
-#define nonstd_lite_in_place_type_t( T) std::in_place_type_t<T>
-#define nonstd_lite_in_place_index_t(K) std::in_place_index_t<K>
-
-#define nonstd_lite_in_place( T) std::in_place_t{}
-#define nonstd_lite_in_place_type( T) std::in_place_type_t<T>{}
-#define nonstd_lite_in_place_index(K) std::in_place_index_t<K>{}
-
-} // namespace nonstd
-
-#else // optional_CPP17_OR_GREATER
-
-#include <cstddef>
-
-namespace nonstd {
-namespace detail {
-
-template< class T >
-struct in_place_type_tag {};
-
-template< std::size_t K >
-struct in_place_index_tag {};
-
-} // namespace detail
-
-struct in_place_t {};
-
-template< class T >
-inline in_place_t in_place( detail::in_place_type_tag<T> /*unused*/ = detail::in_place_type_tag<T>() )
-{
- return in_place_t();
-}
-
-template< std::size_t K >
-inline in_place_t in_place( detail::in_place_index_tag<K> /*unused*/ = detail::in_place_index_tag<K>() )
-{
- return in_place_t();
-}
-
-template< class T >
-inline in_place_t in_place_type( detail::in_place_type_tag<T> /*unused*/ = detail::in_place_type_tag<T>() )
-{
- return in_place_t();
-}
-
-template< std::size_t K >
-inline in_place_t in_place_index( detail::in_place_index_tag<K> /*unused*/ = detail::in_place_index_tag<K>() )
-{
- return in_place_t();
-}
-
-// mimic templated typedef:
-
-#define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag<T> )
-#define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag<T> )
-#define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag<K> )
-
-#define nonstd_lite_in_place( T) nonstd::in_place_type<T>
-#define nonstd_lite_in_place_type( T) nonstd::in_place_type<T>
-#define nonstd_lite_in_place_index(K) nonstd::in_place_index<K>
-
-} // namespace nonstd
-
-#endif // optional_CPP17_OR_GREATER
-#endif // nonstd_lite_HAVE_IN_PLACE_TYPES
-
-//
-// Using std::optional:
-//
-
-#if optional_USES_STD_OPTIONAL
-
-#include <optional>
-
-namespace nonstd {
-
- using std::optional;
- using std::bad_optional_access;
- using std::hash;
-
- using std::nullopt;
- using std::nullopt_t;
-
- using std::operator==;
- using std::operator!=;
- using std::operator<;
- using std::operator<=;
- using std::operator>;
- using std::operator>=;
- using std::make_optional;
- using std::swap;
-}
-
-#else // optional_USES_STD_OPTIONAL
-
-#include <cassert>
-#include <utility>
-
-// optional-lite alignment configuration:
-
-#ifndef optional_CONFIG_MAX_ALIGN_HACK
-# define optional_CONFIG_MAX_ALIGN_HACK 0
-#endif
-
-#ifndef optional_CONFIG_ALIGN_AS
-// no default, used in #if defined()
-#endif
-
-#ifndef optional_CONFIG_ALIGN_AS_FALLBACK
-# define optional_CONFIG_ALIGN_AS_FALLBACK double
-#endif
-
-// Compiler warning suppression:
-
-#if defined(__clang__)
-# pragma clang diagnostic push
-# pragma clang diagnostic ignored "-Wundef"
-#elif defined(__GNUC__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wundef"
-#elif defined(_MSC_VER )
-# pragma warning( push )
-#endif
-
-// half-open range [lo..hi):
-#define optional_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) )
-
-// Compiler versions:
-//
-// MSVC++ 6.0 _MSC_VER == 1200 optional_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0)
-// MSVC++ 7.0 _MSC_VER == 1300 optional_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002)
-// MSVC++ 7.1 _MSC_VER == 1310 optional_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003)
-// MSVC++ 8.0 _MSC_VER == 1400 optional_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005)
-// MSVC++ 9.0 _MSC_VER == 1500 optional_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008)
-// MSVC++ 10.0 _MSC_VER == 1600 optional_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010)
-// MSVC++ 11.0 _MSC_VER == 1700 optional_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012)
-// MSVC++ 12.0 _MSC_VER == 1800 optional_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013)
-// MSVC++ 14.0 _MSC_VER == 1900 optional_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015)
-// MSVC++ 14.1 _MSC_VER >= 1910 optional_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017)
-// MSVC++ 14.2 _MSC_VER >= 1920 optional_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019)
-
-#if defined(_MSC_VER ) && !defined(__clang__)
-# define optional_COMPILER_MSVC_VER (_MSC_VER )
-# define optional_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) )
-#else
-# define optional_COMPILER_MSVC_VER 0
-# define optional_COMPILER_MSVC_VERSION 0
-#endif
-
-#define optional_COMPILER_VERSION( major, minor, patch ) ( 10 * (10 * (major) + (minor) ) + (patch) )
-
-#if defined(__GNUC__) && !defined(__clang__)
-# define optional_COMPILER_GNUC_VERSION optional_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
-#else
-# define optional_COMPILER_GNUC_VERSION 0
-#endif
-
-#if defined(__clang__)
-# define optional_COMPILER_CLANG_VERSION optional_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
-#else
-# define optional_COMPILER_CLANG_VERSION 0
-#endif
-
-#if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 70, 140 )
-# pragma warning( disable: 4345 ) // initialization behavior changed
-#endif
-
-#if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 70, 150 )
-# pragma warning( disable: 4814 ) // in C++14 'constexpr' will not imply 'const'
-#endif
-
-// Presence of language and library features:
-
-#define optional_HAVE(FEATURE) ( optional_HAVE_##FEATURE )
-
-#ifdef _HAS_CPP0X
-# define optional_HAS_CPP0X _HAS_CPP0X
-#else
-# define optional_HAS_CPP0X 0
-#endif
-
-// Unless defined otherwise below, consider VC14 as C++11 for optional-lite:
-
-#if optional_COMPILER_MSVC_VER >= 1900
-# undef optional_CPP11_OR_GREATER
-# define optional_CPP11_OR_GREATER 1
-#endif
-
-#define optional_CPP11_90 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1500)
-#define optional_CPP11_100 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1600)
-#define optional_CPP11_110 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1700)
-#define optional_CPP11_120 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1800)
-#define optional_CPP11_140 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1900)
-#define optional_CPP11_141 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1910)
-
-#define optional_CPP14_000 (optional_CPP14_OR_GREATER)
-#define optional_CPP17_000 (optional_CPP17_OR_GREATER)
-
-// gcc >= 4.9, msvc >= vc14.1 (vs17):
-#define optional_CPP11_140_G490 ((optional_CPP11_OR_GREATER_ && optional_COMPILER_GNUC_VERSION >= 490) || (optional_COMPILER_MSVC_VER >= 1910))
-
-// clang >= 3.5, msvc >= vc11 (vs12):
-#define optional_CPP11_110_C350 ( optional_CPP11_110 && !optional_BETWEEN( optional_COMPILER_CLANG_VERSION, 1, 350 ) )
-
-// clang >= 3.5, gcc >= 5.0, msvc >= vc11 (vs12):
-#define optional_CPP11_110_C350_G500 \
- ( optional_CPP11_110 && \
- !( optional_BETWEEN( optional_COMPILER_CLANG_VERSION, 1, 350 ) \
- || optional_BETWEEN( optional_COMPILER_GNUC_VERSION , 1, 500 ) ) )
-
-// Presence of C++11 language features:
-
-#define optional_HAVE_CONSTEXPR_11 optional_CPP11_140
-#define optional_HAVE_IS_DEFAULT optional_CPP11_140
-#define optional_HAVE_NOEXCEPT optional_CPP11_140
-#define optional_HAVE_NULLPTR optional_CPP11_100
-#define optional_HAVE_REF_QUALIFIER optional_CPP11_140_G490
-#define optional_HAVE_INITIALIZER_LIST optional_CPP11_140
-
-// Presence of C++14 language features:
-
-#define optional_HAVE_CONSTEXPR_14 optional_CPP14_000
-
-// Presence of C++17 language features:
-
-#define optional_HAVE_NODISCARD optional_CPP17_000
-
-// Presence of C++ library features:
-
-#define optional_HAVE_CONDITIONAL optional_CPP11_120
-#define optional_HAVE_REMOVE_CV optional_CPP11_120
-#define optional_HAVE_TYPE_TRAITS optional_CPP11_90
-
-#define optional_HAVE_TR1_TYPE_TRAITS (!! optional_COMPILER_GNUC_VERSION )
-#define optional_HAVE_TR1_ADD_POINTER (!! optional_COMPILER_GNUC_VERSION )
-
-#define optional_HAVE_IS_ASSIGNABLE optional_CPP11_110_C350
-#define optional_HAVE_IS_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350
-#define optional_HAVE_IS_NOTHROW_MOVE_ASSIGNABLE optional_CPP11_110_C350
-#define optional_HAVE_IS_NOTHROW_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350
-#define optional_HAVE_IS_TRIVIALLY_COPY_CONSTRUCTIBLE optional_CPP11_110_C350_G500
-#define optional_HAVE_IS_TRIVIALLY_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350_G500
-
-// C++ feature usage:
-
-#if optional_HAVE( CONSTEXPR_11 )
-# define optional_constexpr constexpr
-#else
-# define optional_constexpr /*constexpr*/
-#endif
-
-#if optional_HAVE( IS_DEFAULT )
-# define optional_is_default = default;
-#else
-# define optional_is_default {}
-#endif
-
-#if optional_HAVE( CONSTEXPR_14 )
-# define optional_constexpr14 constexpr
-#else
-# define optional_constexpr14 /*constexpr*/
-#endif
-
-#if optional_HAVE( NODISCARD )
-# define optional_nodiscard [[nodiscard]]
-#else
-# define optional_nodiscard /*[[nodiscard]]*/
-#endif
-
-#if optional_HAVE( NOEXCEPT )
-# define optional_noexcept noexcept
-#else
-# define optional_noexcept /*noexcept*/
-#endif
-
-#if optional_HAVE( NULLPTR )
-# define optional_nullptr nullptr
-#else
-# define optional_nullptr NULL
-#endif
-
-#if optional_HAVE( REF_QUALIFIER )
-// NOLINTNEXTLINE( bugprone-macro-parentheses )
-# define optional_ref_qual &
-# define optional_refref_qual &&
-#else
-# define optional_ref_qual /*&*/
-# define optional_refref_qual /*&&*/
-#endif
-
-// additional includes:
-
-#if optional_CONFIG_NO_EXCEPTIONS
-// already included: <cassert>
-#else
-# include <stdexcept>
-#endif
-
-#if optional_CPP11_OR_GREATER
-# include <functional>
-#endif
-
-#if optional_HAVE( INITIALIZER_LIST )
-# include <initializer_list>
-#endif
-
-#if optional_HAVE( TYPE_TRAITS )
-# include <type_traits>
-#elif optional_HAVE( TR1_TYPE_TRAITS )
-# include <tr1/type_traits>
-#endif
-
-// Method enabling
-
-#if optional_CPP11_OR_GREATER
-
-#define optional_REQUIRES_0(...) \
- template< bool B = (__VA_ARGS__), typename std::enable_if<B, int>::type = 0 >
-
-#define optional_REQUIRES_T(...) \
- , typename std::enable_if< (__VA_ARGS__), int >::type = 0
-
-#define optional_REQUIRES_R(R, ...) \
- typename std::enable_if< (__VA_ARGS__), R>::type
-
-#define optional_REQUIRES_A(...) \
- , typename std::enable_if< (__VA_ARGS__), void*>::type = nullptr
-
-#endif
-
-//
-// optional:
-//
-
-namespace nonstd { namespace optional_lite {
-
-namespace std11 {
-
-template< class T, T v > struct integral_constant { enum { value = v }; };
-template< bool B > struct bool_constant : integral_constant<bool, B>{};
-
-typedef bool_constant< true > true_type;
-typedef bool_constant< false > false_type;
-
-#if optional_CPP11_OR_GREATER
- using std::move;
-#else
- template< typename T > T & move( T & t ) { return t; }
-#endif
-
-#if optional_HAVE( CONDITIONAL )
- using std::conditional;
-#else
- template< bool B, typename T, typename F > struct conditional { typedef T type; };
- template< typename T, typename F > struct conditional<false, T, F> { typedef F type; };
-#endif // optional_HAVE_CONDITIONAL
-
-#if optional_HAVE( IS_ASSIGNABLE )
- using std::is_assignable;
-#else
- template< class T, class U > struct is_assignable : std11::true_type{};
-#endif
-
-#if optional_HAVE( IS_MOVE_CONSTRUCTIBLE )
- using std::is_move_constructible;
-#else
- template< class T > struct is_move_constructible : std11::true_type{};
-#endif
-
-#if optional_HAVE( IS_NOTHROW_MOVE_ASSIGNABLE )
- using std::is_nothrow_move_assignable;
-#else
- template< class T > struct is_nothrow_move_assignable : std11::true_type{};
-#endif
-
-#if optional_HAVE( IS_NOTHROW_MOVE_CONSTRUCTIBLE )
- using std::is_nothrow_move_constructible;
-#else
- template< class T > struct is_nothrow_move_constructible : std11::true_type{};
-#endif
-
-#if optional_HAVE( IS_TRIVIALLY_COPY_CONSTRUCTIBLE )
- using std::is_trivially_copy_constructible;
-#else
- template< class T > struct is_trivially_copy_constructible : std11::true_type{};
-#endif
-
-#if optional_HAVE( IS_TRIVIALLY_MOVE_CONSTRUCTIBLE )
- using std::is_trivially_move_constructible;
-#else
- template< class T > struct is_trivially_move_constructible : std11::true_type{};
-#endif
-
-} // namespace std11
-
-#if optional_CPP11_OR_GREATER
-
-/// type traits C++17:
-
-namespace std17 {
-
-#if optional_CPP17_OR_GREATER
-
-using std::is_swappable;
-using std::is_nothrow_swappable;
-
-#elif optional_CPP11_OR_GREATER
-
-namespace detail {
-
-using std::swap;
-
-struct is_swappable
-{
- template< typename T, typename = decltype( swap( std::declval<T&>(), std::declval<T&>() ) ) >
- static std11::true_type test( int /*unused*/ );
-
- template< typename >
- static std11::false_type test(...);
-};
-
-struct is_nothrow_swappable
-{
- // wrap noexcept(expr) in separate function as work-around for VC140 (VS2015):
-
- template< typename T >
- static constexpr bool satisfies()
- {
- return noexcept( swap( std::declval<T&>(), std::declval<T&>() ) );
- }
-
- template< typename T >
- static auto test( int /*unused*/ ) -> std11::integral_constant<bool, satisfies<T>()>{}
-
- template< typename >
- static auto test(...) -> std11::false_type;
-};
-
-} // namespace detail
-
-// is [nothow] swappable:
-
-template< typename T >
-struct is_swappable : decltype( detail::is_swappable::test<T>(0) ){};
-
-template< typename T >
-struct is_nothrow_swappable : decltype( detail::is_nothrow_swappable::test<T>(0) ){};
-
-#endif // optional_CPP17_OR_GREATER
-
-} // namespace std17
-
-/// type traits C++20:
-
-namespace std20 {
-
-template< typename T >
-struct remove_cvref
-{
- typedef typename std::remove_cv< typename std::remove_reference<T>::type >::type type;
-};
-
-} // namespace std20
-
-#endif // optional_CPP11_OR_GREATER
-
-/// class optional
-
-template< typename T >
-class optional;
-
-namespace detail {
-
-// C++11 emulation:
-
-struct nulltype{};
-
-template< typename Head, typename Tail >
-struct typelist
-{
- typedef Head head;
- typedef Tail tail;
-};
-
-#if optional_CONFIG_MAX_ALIGN_HACK
-
-// Max align, use most restricted type for alignment:
-
-#define optional_UNIQUE( name ) optional_UNIQUE2( name, __LINE__ )
-#define optional_UNIQUE2( name, line ) optional_UNIQUE3( name, line )
-#define optional_UNIQUE3( name, line ) name ## line
-
-#define optional_ALIGN_TYPE( type ) \
- type optional_UNIQUE( _t ); struct_t< type > optional_UNIQUE( _st )
-
-template< typename T >
-struct struct_t { T _; };
-
-union max_align_t
-{
- optional_ALIGN_TYPE( char );
- optional_ALIGN_TYPE( short int );
- optional_ALIGN_TYPE( int );
- optional_ALIGN_TYPE( long int );
- optional_ALIGN_TYPE( float );
- optional_ALIGN_TYPE( double );
- optional_ALIGN_TYPE( long double );
- optional_ALIGN_TYPE( char * );
- optional_ALIGN_TYPE( short int * );
- optional_ALIGN_TYPE( int * );
- optional_ALIGN_TYPE( long int * );
- optional_ALIGN_TYPE( float * );
- optional_ALIGN_TYPE( double * );
- optional_ALIGN_TYPE( long double * );
- optional_ALIGN_TYPE( void * );
-
-#ifdef HAVE_LONG_LONG
- optional_ALIGN_TYPE( long long );
-#endif
-
- struct Unknown;
-
- Unknown ( * optional_UNIQUE(_) )( Unknown );
- Unknown * Unknown::* optional_UNIQUE(_);
- Unknown ( Unknown::* optional_UNIQUE(_) )( Unknown );
-
- struct_t< Unknown ( * )( Unknown) > optional_UNIQUE(_);
- struct_t< Unknown * Unknown::* > optional_UNIQUE(_);
- struct_t< Unknown ( Unknown::* )(Unknown) > optional_UNIQUE(_);
-};
-
-#undef optional_UNIQUE
-#undef optional_UNIQUE2
-#undef optional_UNIQUE3
-
-#undef optional_ALIGN_TYPE
-
-#elif defined( optional_CONFIG_ALIGN_AS ) // optional_CONFIG_MAX_ALIGN_HACK
-
-// Use user-specified type for alignment:
-
-#define optional_ALIGN_AS( unused ) \
- optional_CONFIG_ALIGN_AS
-
-#else // optional_CONFIG_MAX_ALIGN_HACK
-
-// Determine POD type to use for alignment:
-
-#define optional_ALIGN_AS( to_align ) \
- typename type_of_size< alignment_types, alignment_of< to_align >::value >::type
-
-template< typename T >
-struct alignment_of;
-
-template< typename T >
-struct alignment_of_hack
-{
- char c;
- T t;
- alignment_of_hack();
-};
-
-template< size_t A, size_t S >
-struct alignment_logic
-{
- enum { value = A < S ? A : S };
-};
-
-template< typename T >
-struct alignment_of
-{
- enum { value = alignment_logic<
- sizeof( alignment_of_hack<T> ) - sizeof(T), sizeof(T) >::value };
-};
-
-template< typename List, size_t N >
-struct type_of_size
-{
- typedef typename std11::conditional<
- N == sizeof( typename List::head ),
- typename List::head,
- typename type_of_size<typename List::tail, N >::type >::type type;
-};
-
-template< size_t N >
-struct type_of_size< nulltype, N >
-{
- typedef optional_CONFIG_ALIGN_AS_FALLBACK type;
-};
-
-template< typename T>
-struct struct_t { T _; };
-
-#define optional_ALIGN_TYPE( type ) \
- typelist< type , typelist< struct_t< type >
-
-struct Unknown;
-
-typedef
- optional_ALIGN_TYPE( char ),
- optional_ALIGN_TYPE( short ),
- optional_ALIGN_TYPE( int ),
- optional_ALIGN_TYPE( long ),
- optional_ALIGN_TYPE( float ),
- optional_ALIGN_TYPE( double ),
- optional_ALIGN_TYPE( long double ),
-
- optional_ALIGN_TYPE( char *),
- optional_ALIGN_TYPE( short * ),
- optional_ALIGN_TYPE( int * ),
- optional_ALIGN_TYPE( long * ),
- optional_ALIGN_TYPE( float * ),
- optional_ALIGN_TYPE( double * ),
- optional_ALIGN_TYPE( long double * ),
-
- optional_ALIGN_TYPE( Unknown ( * )( Unknown ) ),
- optional_ALIGN_TYPE( Unknown * Unknown::* ),
- optional_ALIGN_TYPE( Unknown ( Unknown::* )( Unknown ) ),
-
- nulltype
- > > > > > > > > > > > > > >
- > > > > > > > > > > > > > >
- > > > > > >
- alignment_types;
-
-#undef optional_ALIGN_TYPE
-
-#endif // optional_CONFIG_MAX_ALIGN_HACK
-
-/// C++03 constructed union to hold value.
-
-template< typename T >
-union storage_t
-{
-//private:
-// template< typename > friend class optional;
-
- typedef T value_type;
-
- storage_t() optional_is_default
-
- explicit storage_t( value_type const & v )
- {
- construct_value( v );
- }
-
- void construct_value( value_type const & v )
- {
- ::new( value_ptr() ) value_type( v );
- }
-
-#if optional_CPP11_OR_GREATER
-
- explicit storage_t( value_type && v )
- {
- construct_value( std::move( v ) );
- }
-
- void construct_value( value_type && v )
- {
- ::new( value_ptr() ) value_type( std::move( v ) );
- }
-
- template< class... Args >
- storage_t( nonstd_lite_in_place_t(T), Args&&... args )
- {
- emplace( std::forward<Args>(args)... );
- }
-
- template< class... Args >
- void emplace( Args&&... args )
- {
- ::new( value_ptr() ) value_type( std::forward<Args>(args)... );
- }
-
- template< class U, class... Args >
- void emplace( std::initializer_list<U> il, Args&&... args )
- {
- ::new( value_ptr() ) value_type( il, std::forward<Args>(args)... );
- }
-
-#endif
-
- void destruct_value()
- {
- value_ptr()->~T();
- }
-
- optional_nodiscard value_type const * value_ptr() const
- {
- return as<value_type>();
- }
-
- value_type * value_ptr()
- {
- return as<value_type>();
- }
-
- optional_nodiscard value_type const & value() const optional_ref_qual
- {
- return * value_ptr();
- }
-
- value_type & value() optional_ref_qual
- {
- return * value_ptr();
- }
-
-#if optional_HAVE( REF_QUALIFIER )
-
- optional_nodiscard value_type const && value() const optional_refref_qual
- {
- return std::move( value() );
- }
-
- value_type && value() optional_refref_qual
- {
- return std::move( value() );
- }
-
-#endif
-
-#if optional_CPP11_OR_GREATER
-
- using aligned_storage_t = typename std::aligned_storage< sizeof(value_type), alignof(value_type) >::type;
- aligned_storage_t data;
-
-#elif optional_CONFIG_MAX_ALIGN_HACK
-
- typedef struct { unsigned char data[ sizeof(value_type) ]; } aligned_storage_t;
-
- max_align_t hack;
- aligned_storage_t data;
-
-#else
- typedef optional_ALIGN_AS(value_type) align_as_type;
-
- typedef struct { align_as_type data[ 1 + ( sizeof(value_type) - 1 ) / sizeof(align_as_type) ]; } aligned_storage_t;
- aligned_storage_t data;
-
-# undef optional_ALIGN_AS
-
-#endif // optional_CONFIG_MAX_ALIGN_HACK
-
- optional_nodiscard void * ptr() optional_noexcept
- {
- return &data;
- }
-
- optional_nodiscard void const * ptr() const optional_noexcept
- {
- return &data;
- }
-
- template <typename U>
- optional_nodiscard U * as()
- {
- return reinterpret_cast<U*>( ptr() );
- }
-
- template <typename U>
- optional_nodiscard U const * as() const
- {
- return reinterpret_cast<U const *>( ptr() );
- }
-};
-
-} // namespace detail
-
-/// disengaged state tag
-
-struct nullopt_t
-{
- struct init{};
- explicit optional_constexpr nullopt_t( init /*unused*/ ) optional_noexcept {}
-};
-
-#if optional_HAVE( CONSTEXPR_11 )
-constexpr nullopt_t nullopt{ nullopt_t::init{} };
-#else
-// extra parenthesis to prevent the most vexing parse:
-const nullopt_t nullopt(( nullopt_t::init() ));
-#endif
-
-/// optional access error
-
-#if ! optional_CONFIG_NO_EXCEPTIONS
-
-class bad_optional_access : public std::logic_error
-{
-public:
- explicit bad_optional_access()
- : logic_error( "bad optional access" ) {}
-};
-
-#endif //optional_CONFIG_NO_EXCEPTIONS
-
-/// optional
-
-template< typename T>
-class optional
-{
-private:
- template< typename > friend class optional;
-
- typedef void (optional::*safe_bool)() const;
-
-public:
- typedef T value_type;
-
- // x.x.3.1, constructors
-
- // 1a - default construct
- optional_constexpr optional() optional_noexcept
- : has_value_( false )
- , contained()
- {}
-
- // 1b - construct explicitly empty
- // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions )
- optional_constexpr optional( nullopt_t /*unused*/ ) optional_noexcept
- : has_value_( false )
- , contained()
- {}
-
- // 2 - copy-construct
-#if optional_CPP11_OR_GREATER
- // template< typename U = T
- // optional_REQUIRES_T(
- // std::is_copy_constructible<U>::value
- // || std11::is_trivially_copy_constructible<U>::value
- // )
- // >
-#endif
- optional_constexpr14 optional( optional const & other )
- : has_value_( other.has_value() )
- {
- if ( other.has_value() )
- {
- contained.construct_value( other.contained.value() );
- }
- }
-
-#if optional_CPP11_OR_GREATER
-
- // 3 (C++11) - move-construct from optional
- template< typename U = T
- optional_REQUIRES_T(
- std11::is_move_constructible<U>::value
- || std11::is_trivially_move_constructible<U>::value
- )
- >
- optional_constexpr14 optional( optional && other )
- // NOLINTNEXTLINE( performance-noexcept-move-constructor )
- noexcept( std11::is_nothrow_move_constructible<T>::value )
- : has_value_( other.has_value() )
- {
- if ( other.has_value() )
- {
- contained.construct_value( std::move( other.contained.value() ) );
- }
- }
-
- // 4a (C++11) - explicit converting copy-construct from optional
- template< typename U
- optional_REQUIRES_T(
- std::is_constructible<T, U const &>::value
- && !std::is_constructible<T, optional<U> & >::value
- && !std::is_constructible<T, optional<U> && >::value
- && !std::is_constructible<T, optional<U> const & >::value
- && !std::is_constructible<T, optional<U> const && >::value
- && !std::is_convertible< optional<U> & , T>::value
- && !std::is_convertible< optional<U> && , T>::value
- && !std::is_convertible< optional<U> const & , T>::value
- && !std::is_convertible< optional<U> const &&, T>::value
- && !std::is_convertible< U const & , T>::value /*=> explicit */
- )
- >
- explicit optional( optional<U> const & other )
- : has_value_( other.has_value() )
- {
- if ( other.has_value() )
- {
- contained.construct_value( T{ other.contained.value() } );
- }
- }
-#endif // optional_CPP11_OR_GREATER
-
- // 4b (C++98 and later) - non-explicit converting copy-construct from optional
- template< typename U
-#if optional_CPP11_OR_GREATER
- optional_REQUIRES_T(
- std::is_constructible<T, U const &>::value
- && !std::is_constructible<T, optional<U> & >::value
- && !std::is_constructible<T, optional<U> && >::value
- && !std::is_constructible<T, optional<U> const & >::value
- && !std::is_constructible<T, optional<U> const && >::value
- && !std::is_convertible< optional<U> & , T>::value
- && !std::is_convertible< optional<U> && , T>::value
- && !std::is_convertible< optional<U> const & , T>::value
- && !std::is_convertible< optional<U> const &&, T>::value
- && std::is_convertible< U const & , T>::value /*=> non-explicit */
- )
-#endif // optional_CPP11_OR_GREATER
- >
- // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions )
- /*non-explicit*/ optional( optional<U> const & other )
- : has_value_( other.has_value() )
- {
- if ( other.has_value() )
- {
- contained.construct_value( other.contained.value() );
- }
- }
-
-#if optional_CPP11_OR_GREATER
-
- // 5a (C++11) - explicit converting move-construct from optional
- template< typename U
- optional_REQUIRES_T(
- std::is_constructible<T, U &&>::value
- && !std::is_constructible<T, optional<U> & >::value
- && !std::is_constructible<T, optional<U> && >::value
- && !std::is_constructible<T, optional<U> const & >::value
- && !std::is_constructible<T, optional<U> const && >::value
- && !std::is_convertible< optional<U> & , T>::value
- && !std::is_convertible< optional<U> && , T>::value
- && !std::is_convertible< optional<U> const & , T>::value
- && !std::is_convertible< optional<U> const &&, T>::value
- && !std::is_convertible< U &&, T>::value /*=> explicit */
- )
- >
- explicit optional( optional<U> && other
- )
- : has_value_( other.has_value() )
- {
- if ( other.has_value() )
- {
- contained.construct_value( T{ std::move( other.contained.value() ) } );
- }
- }
-
- // 5a (C++11) - non-explicit converting move-construct from optional
- template< typename U
- optional_REQUIRES_T(
- std::is_constructible<T, U &&>::value
- && !std::is_constructible<T, optional<U> & >::value
- && !std::is_constructible<T, optional<U> && >::value
- && !std::is_constructible<T, optional<U> const & >::value
- && !std::is_constructible<T, optional<U> const && >::value
- && !std::is_convertible< optional<U> & , T>::value
- && !std::is_convertible< optional<U> && , T>::value
- && !std::is_convertible< optional<U> const & , T>::value
- && !std::is_convertible< optional<U> const &&, T>::value
- && std::is_convertible< U &&, T>::value /*=> non-explicit */
- )
- >
- // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions )
- /*non-explicit*/ optional( optional<U> && other )
- : has_value_( other.has_value() )
- {
- if ( other.has_value() )
- {
- contained.construct_value( std::move( other.contained.value() ) );
- }
- }
-
- // 6 (C++11) - in-place construct
- template< typename... Args
- optional_REQUIRES_T(
- std::is_constructible<T, Args&&...>::value
- )
- >
- optional_constexpr explicit optional( nonstd_lite_in_place_t(T), Args&&... args )
- : has_value_( true )
- , contained( T( std::forward<Args>(args)...) )
- {}
-
- // 7 (C++11) - in-place construct, initializer-list
- template< typename U, typename... Args
- optional_REQUIRES_T(
- std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value
- )
- >
- optional_constexpr explicit optional( nonstd_lite_in_place_t(T), std::initializer_list<U> il, Args&&... args )
- : has_value_( true )
- , contained( T( il, std::forward<Args>(args)...) )
- {}
-
- // 8a (C++11) - explicit move construct from value
- template< typename U = T
- optional_REQUIRES_T(
- std::is_constructible<T, U&&>::value
- && !std::is_same<typename std20::remove_cvref<U>::type, nonstd_lite_in_place_t(U)>::value
- && !std::is_same<typename std20::remove_cvref<U>::type, optional<T>>::value
- && !std::is_convertible<U&&, T>::value /*=> explicit */
- )
- >
- optional_constexpr explicit optional( U && value )
- : has_value_( true )
- , contained( nonstd_lite_in_place(T), std::forward<U>( value ) )
- {}
-
- // 8b (C++11) - non-explicit move construct from value
- template< typename U = T
- optional_REQUIRES_T(
- std::is_constructible<T, U&&>::value
- && !std::is_same<typename std20::remove_cvref<U>::type, nonstd_lite_in_place_t(U)>::value
- && !std::is_same<typename std20::remove_cvref<U>::type, optional<T>>::value
- && std::is_convertible<U&&, T>::value /*=> non-explicit */
- )
- >
- // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions )
- optional_constexpr /*non-explicit*/ optional( U && value )
- : has_value_( true )
- , contained( nonstd_lite_in_place(T), std::forward<U>( value ) )
- {}
-
-#else // optional_CPP11_OR_GREATER
-
- // 8 (C++98)
- optional( value_type const & value )
- : has_value_( true )
- , contained( value )
- {}
-
-#endif // optional_CPP11_OR_GREATER
-
- // x.x.3.2, destructor
-
- ~optional()
- {
- if ( has_value() )
- {
- contained.destruct_value();
- }
- }
-
- // x.x.3.3, assignment
-
- // 1 (C++98and later) - assign explicitly empty
- optional & operator=( nullopt_t /*unused*/) optional_noexcept
- {
- reset();
- return *this;
- }
-
- // 2 (C++98and later) - copy-assign from optional
-#if optional_CPP11_OR_GREATER
- // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator )
- optional_REQUIRES_R(
- optional &,
- true
-// std::is_copy_constructible<T>::value
-// && std::is_copy_assignable<T>::value
- )
- operator=( optional const & other )
- noexcept(
- std11::is_nothrow_move_assignable<T>::value
- && std11::is_nothrow_move_constructible<T>::value
- )
-#else
- optional & operator=( optional const & other )
-#endif
- {
- if ( (has_value() == true ) && (other.has_value() == false) ) { reset(); }
- else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( *other ); }
- else if ( (has_value() == true ) && (other.has_value() == true ) ) { contained.value() = *other; }
- return *this;
- }
-
-#if optional_CPP11_OR_GREATER
-
- // 3 (C++11) - move-assign from optional
- // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator )
- optional_REQUIRES_R(
- optional &,
- true
-// std11::is_move_constructible<T>::value
-// && std::is_move_assignable<T>::value
- )
- operator=( optional && other ) noexcept
- {
- if ( (has_value() == true ) && (other.has_value() == false) ) { reset(); }
- else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( std::move( *other ) ); }
- else if ( (has_value() == true ) && (other.has_value() == true ) ) { contained.value() = std::move( *other ); }
- return *this;
- }
-
- // 4 (C++11) - move-assign from value
- template< typename U = T >
- // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator )
- optional_REQUIRES_R(
- optional &,
- std::is_constructible<T , U>::value
- && std11::is_assignable<T&, U>::value
- && !std::is_same<typename std20::remove_cvref<U>::type, nonstd_lite_in_place_t(U)>::value
- && !std::is_same<typename std20::remove_cvref<U>::type, optional<T>>::value
- && !(std::is_scalar<T>::value && std::is_same<T, typename std::decay<U>::type>::value)
- )
- operator=( U && value )
- {
- if ( has_value() )
- {
- contained.value() = std::forward<U>( value );
- }
- else
- {
- initialize( T( std::forward<U>( value ) ) );
- }
- return *this;
- }
-
-#else // optional_CPP11_OR_GREATER
-
- // 4 (C++98) - copy-assign from value
- template< typename U /*= T*/ >
- optional & operator=( U const & value )
- {
- if ( has_value() ) contained.value() = value;
- else initialize( T( value ) );
- return *this;
- }
-
-#endif // optional_CPP11_OR_GREATER
-
- // 5 (C++98 and later) - converting copy-assign from optional
- template< typename U >
-#if optional_CPP11_OR_GREATER
- // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator )
- optional_REQUIRES_R(
- optional&,
- std::is_constructible< T , U const &>::value
- && std11::is_assignable< T&, U const &>::value
- && !std::is_constructible<T, optional<U> & >::value
- && !std::is_constructible<T, optional<U> && >::value
- && !std::is_constructible<T, optional<U> const & >::value
- && !std::is_constructible<T, optional<U> const && >::value
- && !std::is_convertible< optional<U> & , T>::value
- && !std::is_convertible< optional<U> && , T>::value
- && !std::is_convertible< optional<U> const & , T>::value
- && !std::is_convertible< optional<U> const &&, T>::value
- && !std11::is_assignable< T&, optional<U> & >::value
- && !std11::is_assignable< T&, optional<U> && >::value
- && !std11::is_assignable< T&, optional<U> const & >::value
- && !std11::is_assignable< T&, optional<U> const && >::value
- )
-#else
- optional&
-#endif // optional_CPP11_OR_GREATER
- operator=( optional<U> const & other )
- {
- return *this = optional( other );
- }
-
-#if optional_CPP11_OR_GREATER
-
- // 6 (C++11) - converting move-assign from optional
- template< typename U >
- // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator )
- optional_REQUIRES_R(
- optional&,
- std::is_constructible< T , U>::value
- && std11::is_assignable< T&, U>::value
- && !std::is_constructible<T, optional<U> & >::value
- && !std::is_constructible<T, optional<U> && >::value
- && !std::is_constructible<T, optional<U> const & >::value
- && !std::is_constructible<T, optional<U> const && >::value
- && !std::is_convertible< optional<U> & , T>::value
- && !std::is_convertible< optional<U> && , T>::value
- && !std::is_convertible< optional<U> const & , T>::value
- && !std::is_convertible< optional<U> const &&, T>::value
- && !std11::is_assignable< T&, optional<U> & >::value
- && !std11::is_assignable< T&, optional<U> && >::value
- && !std11::is_assignable< T&, optional<U> const & >::value
- && !std11::is_assignable< T&, optional<U> const && >::value
- )
- operator=( optional<U> && other )
- {
- return *this = optional( std::move( other ) );
- }
-
- // 7 (C++11) - emplace
- template< typename... Args
- optional_REQUIRES_T(
- std::is_constructible<T, Args&&...>::value
- )
- >
- T& emplace( Args&&... args )
- {
- *this = nullopt;
- contained.emplace( std::forward<Args>(args)... );
- has_value_ = true;
- return contained.value();
- }
-
- // 8 (C++11) - emplace, initializer-list
- template< typename U, typename... Args
- optional_REQUIRES_T(
- std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value
- )
- >
- T& emplace( std::initializer_list<U> il, Args&&... args )
- {
- *this = nullopt;
- contained.emplace( il, std::forward<Args>(args)... );
- has_value_ = true;
- return contained.value();
- }
-
-#endif // optional_CPP11_OR_GREATER
-
- // x.x.3.4, swap
-
- void swap( optional & other )
-#if optional_CPP11_OR_GREATER
- noexcept(
- std11::is_nothrow_move_constructible<T>::value
- && std17::is_nothrow_swappable<T>::value
- )
-#endif
- {
- using std::swap;
- if ( (has_value() == true ) && (other.has_value() == true ) ) { swap( **this, *other ); }
- else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( std11::move(*other) ); other.reset(); }
- else if ( (has_value() == true ) && (other.has_value() == false) ) { other.initialize( std11::move(**this) ); reset(); }
- }
-
- // x.x.3.5, observers
-
- optional_constexpr value_type const * operator ->() const
- {
- return assert( has_value() ),
- contained.value_ptr();
- }
-
- optional_constexpr14 value_type * operator ->()
- {
- return assert( has_value() ),
- contained.value_ptr();
- }
-
- optional_constexpr value_type const & operator *() const optional_ref_qual
- {
- return assert( has_value() ),
- contained.value();
- }
-
- optional_constexpr14 value_type & operator *() optional_ref_qual
- {
- return assert( has_value() ),
- contained.value();
- }
-
-#if optional_HAVE( REF_QUALIFIER )
-
- optional_constexpr value_type const && operator *() const optional_refref_qual
- {
- return std::move( **this );
- }
-
- optional_constexpr14 value_type && operator *() optional_refref_qual
- {
- return std::move( **this );
- }
-
-#endif
-
-#if optional_CPP11_OR_GREATER
- optional_constexpr explicit operator bool() const optional_noexcept
- {
- return has_value();
- }
-#else
- optional_constexpr operator safe_bool() const optional_noexcept
- {
- return has_value() ? &optional::this_type_does_not_support_comparisons : 0;
- }
-#endif
-
- // NOLINTNEXTLINE( modernize-use-nodiscard )
- /*optional_nodiscard*/ optional_constexpr bool has_value() const optional_noexcept
- {
- return has_value_;
- }
-
- // NOLINTNEXTLINE( modernize-use-nodiscard )
- /*optional_nodiscard*/ optional_constexpr14 value_type const & value() const optional_ref_qual
- {
-#if optional_CONFIG_NO_EXCEPTIONS
- assert( has_value() );
-#else
- if ( ! has_value() )
- {
- throw bad_optional_access();
- }
-#endif
- return contained.value();
- }
-
- optional_constexpr14 value_type & value() optional_ref_qual
- {
-#if optional_CONFIG_NO_EXCEPTIONS
- assert( has_value() );
-#else
- if ( ! has_value() )
- {
- throw bad_optional_access();
- }
-#endif
- return contained.value();
- }
-
-#if optional_HAVE( REF_QUALIFIER ) && ( !optional_COMPILER_GNUC_VERSION || optional_COMPILER_GNUC_VERSION >= 490 )
-
- // NOLINTNEXTLINE( modernize-use-nodiscard )
- /*optional_nodiscard*/ optional_constexpr value_type const && value() const optional_refref_qual
- {
- return std::move( value() );
- }
-
- optional_constexpr14 value_type && value() optional_refref_qual
- {
- return std::move( value() );
- }
-
-#endif
-
-#if optional_CPP11_OR_GREATER
-
- template< typename U >
- optional_constexpr value_type value_or( U && v ) const optional_ref_qual
- {
- return has_value() ? contained.value() : static_cast<T>(std::forward<U>( v ) );
- }
-
- template< typename U >
- optional_constexpr14 value_type value_or( U && v ) optional_refref_qual
- {
- return has_value() ? std::move( contained.value() ) : static_cast<T>(std::forward<U>( v ) );
- }
-
-#else
-
- template< typename U >
- optional_constexpr value_type value_or( U const & v ) const
- {
- return has_value() ? contained.value() : static_cast<value_type>( v );
- }
-
-#endif // optional_CPP11_OR_GREATER
-
- // x.x.3.6, modifiers
-
- void reset() optional_noexcept
- {
- if ( has_value() )
- {
- contained.destruct_value();
- }
-
- has_value_ = false;
- }
-
-private:
- void this_type_does_not_support_comparisons() const {}
-
- template< typename V >
- void initialize( V const & value )
- {
- assert( ! has_value() );
- contained.construct_value( value );
- has_value_ = true;
- }
-
-#if optional_CPP11_OR_GREATER
- template< typename V >
- void initialize( V && value )
- {
- assert( ! has_value() );
- contained.construct_value( std::move( value ) );
- has_value_ = true;
- }
-
-#endif
-
-private:
- bool has_value_;
- detail::storage_t< value_type > contained;
-
-};
-
-// Relational operators
-
-template< typename T, typename U >
-inline optional_constexpr bool operator==( optional<T> const & x, optional<U> const & y )
-{
- return bool(x) != bool(y) ? false : !bool( x ) ? true : *x == *y;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator!=( optional<T> const & x, optional<U> const & y )
-{
- return !(x == y);
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator<( optional<T> const & x, optional<U> const & y )
-{
- return (!y) ? false : (!x) ? true : *x < *y;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator>( optional<T> const & x, optional<U> const & y )
-{
- return (y < x);
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator<=( optional<T> const & x, optional<U> const & y )
-{
- return !(y < x);
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator>=( optional<T> const & x, optional<U> const & y )
-{
- return !(x < y);
-}
-
-// Comparison with nullopt
-
-template< typename T >
-inline optional_constexpr bool operator==( optional<T> const & x, nullopt_t /*unused*/ ) optional_noexcept
-{
- return (!x);
-}
-
-template< typename T >
-inline optional_constexpr bool operator==( nullopt_t /*unused*/, optional<T> const & x ) optional_noexcept
-{
- return (!x);
-}
-
-template< typename T >
-inline optional_constexpr bool operator!=( optional<T> const & x, nullopt_t /*unused*/ ) optional_noexcept
-{
- return bool(x);
-}
-
-template< typename T >
-inline optional_constexpr bool operator!=( nullopt_t /*unused*/, optional<T> const & x ) optional_noexcept
-{
- return bool(x);
-}
-
-template< typename T >
-inline optional_constexpr bool operator<( optional<T> const & /*unused*/, nullopt_t /*unused*/ ) optional_noexcept
-{
- return false;
-}
-
-template< typename T >
-inline optional_constexpr bool operator<( nullopt_t /*unused*/, optional<T> const & x ) optional_noexcept
-{
- return bool(x);
-}
-
-template< typename T >
-inline optional_constexpr bool operator<=( optional<T> const & x, nullopt_t /*unused*/ ) optional_noexcept
-{
- return (!x);
-}
-
-template< typename T >
-inline optional_constexpr bool operator<=( nullopt_t /*unused*/, optional<T> const & /*unused*/ ) optional_noexcept
-{
- return true;
-}
-
-template< typename T >
-inline optional_constexpr bool operator>( optional<T> const & x, nullopt_t /*unused*/ ) optional_noexcept
-{
- return bool(x);
-}
-
-template< typename T >
-inline optional_constexpr bool operator>( nullopt_t /*unused*/, optional<T> const & /*unused*/ ) optional_noexcept
-{
- return false;
-}
-
-template< typename T >
-inline optional_constexpr bool operator>=( optional<T> const & /*unused*/, nullopt_t /*unused*/ ) optional_noexcept
-{
- return true;
-}
-
-template< typename T >
-inline optional_constexpr bool operator>=( nullopt_t /*unused*/, optional<T> const & x ) optional_noexcept
-{
- return (!x);
-}
-
-// Comparison with T
-
-template< typename T, typename U >
-inline optional_constexpr bool operator==( optional<T> const & x, U const & v )
-{
- return bool(x) ? *x == v : false;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator==( U const & v, optional<T> const & x )
-{
- return bool(x) ? v == *x : false;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator!=( optional<T> const & x, U const & v )
-{
- return bool(x) ? *x != v : true;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator!=( U const & v, optional<T> const & x )
-{
- return bool(x) ? v != *x : true;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator<( optional<T> const & x, U const & v )
-{
- return bool(x) ? *x < v : true;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator<( U const & v, optional<T> const & x )
-{
- return bool(x) ? v < *x : false;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator<=( optional<T> const & x, U const & v )
-{
- return bool(x) ? *x <= v : true;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator<=( U const & v, optional<T> const & x )
-{
- return bool(x) ? v <= *x : false;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator>( optional<T> const & x, U const & v )
-{
- return bool(x) ? *x > v : false;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator>( U const & v, optional<T> const & x )
-{
- return bool(x) ? v > *x : true;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator>=( optional<T> const & x, U const & v )
-{
- return bool(x) ? *x >= v : false;
-}
-
-template< typename T, typename U >
-inline optional_constexpr bool operator>=( U const & v, optional<T> const & x )
-{
- return bool(x) ? v >= *x : true;
-}
-
-// Specialized algorithms
-
-template< typename T
-#if optional_CPP11_OR_GREATER
- optional_REQUIRES_T(
- std11::is_move_constructible<T>::value
- && std17::is_swappable<T>::value )
-#endif
->
-void swap( optional<T> & x, optional<T> & y )
-#if optional_CPP11_OR_GREATER
- noexcept( noexcept( x.swap(y) ) )
-#endif
-{
- x.swap( y );
-}
-
-#if optional_CPP11_OR_GREATER
-
-template< typename T >
-optional_constexpr optional< typename std::decay<T>::type > make_optional( T && value )
-{
- return optional< typename std::decay<T>::type >( std::forward<T>( value ) );
-}
-
-template< typename T, typename...Args >
-optional_constexpr optional<T> make_optional( Args&&... args )
-{
- return optional<T>( nonstd_lite_in_place(T), std::forward<Args>(args)...);
-}
-
-template< typename T, typename U, typename... Args >
-optional_constexpr optional<T> make_optional( std::initializer_list<U> il, Args&&... args )
-{
- return optional<T>( nonstd_lite_in_place(T), il, std::forward<Args>(args)...);
-}
-
-#else
-
-template< typename T >
-optional<T> make_optional( T const & value )
-{
- return optional<T>( value );
-}
-
-#endif // optional_CPP11_OR_GREATER
-
-} // namespace optional_lite
-
-using optional_lite::optional;
-using optional_lite::nullopt_t;
-using optional_lite::nullopt;
-
-#if ! optional_CONFIG_NO_EXCEPTIONS
-using optional_lite::bad_optional_access;
-#endif
-
-using optional_lite::make_optional;
-
-} // namespace nonstd
-
-#if optional_CPP11_OR_GREATER
-
-// specialize the std::hash algorithm:
-
-namespace std {
-
-template< class T >
-struct hash< nonstd::optional<T> >
-{
-public:
- std::size_t operator()( nonstd::optional<T> const & v ) const optional_noexcept
- {
- return bool( v ) ? std::hash<T>{}( *v ) : 0;
- }
-};
-
-} //namespace std
-
-#endif // optional_CPP11_OR_GREATER
-
-#if defined(__clang__)
-# pragma clang diagnostic pop
-#elif defined(__GNUC__)
-# pragma GCC diagnostic pop
-#elif defined(_MSC_VER )
-# pragma warning( pop )
-#endif
-
-#endif // optional_USES_STD_OPTIONAL
-
-#endif // NONSTD_OPTIONAL_LITE_HPP
--- /dev/null
+//
+// span for C++98 and later.
+// Based on http://wg21.link/p0122r7
+// For more information see https://github.com/martinmoene/span-lite
+//
+// Copyright 2018-2021 Martin Moene
+//
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef NONSTD_SPAN_HPP_INCLUDED
+#define NONSTD_SPAN_HPP_INCLUDED
+
+#define span_lite_MAJOR 0
+#define span_lite_MINOR 10
+#define span_lite_PATCH 3
+
+#define span_lite_VERSION span_STRINGIFY(span_lite_MAJOR) "." span_STRINGIFY(span_lite_MINOR) "." span_STRINGIFY(span_lite_PATCH)
+
+#define span_STRINGIFY( x ) span_STRINGIFY_( x )
+#define span_STRINGIFY_( x ) #x
+
+// span configuration:
+
+#define span_SPAN_DEFAULT 0
+#define span_SPAN_NONSTD 1
+#define span_SPAN_STD 2
+
+// tweak header support:
+
+#ifdef __has_include
+# if __has_include(<nonstd/span.tweak.hpp>)
+# include <nonstd/span.tweak.hpp>
+# endif
+#define span_HAVE_TWEAK_HEADER 1
+#else
+#define span_HAVE_TWEAK_HEADER 0
+//# pragma message("span.hpp: Note: Tweak header not supported.")
+#endif
+
+// span selection and configuration:
+
+#define span_HAVE( feature ) ( span_HAVE_##feature )
+
+#ifndef span_CONFIG_SELECT_SPAN
+# define span_CONFIG_SELECT_SPAN ( span_HAVE_STD_SPAN ? span_SPAN_STD : span_SPAN_NONSTD )
+#endif
+
+#ifndef span_CONFIG_EXTENT_TYPE
+# define span_CONFIG_EXTENT_TYPE std::size_t
+#endif
+
+#ifndef span_CONFIG_SIZE_TYPE
+# define span_CONFIG_SIZE_TYPE std::size_t
+#endif
+
+#ifdef span_CONFIG_INDEX_TYPE
+# error `span_CONFIG_INDEX_TYPE` is deprecated since v0.7.0; it is replaced by `span_CONFIG_SIZE_TYPE`.
+#endif
+
+// span configuration (features):
+
+#ifndef span_FEATURE_WITH_CONTAINER
+#ifdef span_FEATURE_WITH_CONTAINER_TO_STD
+# define span_FEATURE_WITH_CONTAINER span_IN_STD( span_FEATURE_WITH_CONTAINER_TO_STD )
+#else
+# define span_FEATURE_WITH_CONTAINER 0
+# define span_FEATURE_WITH_CONTAINER_TO_STD 0
+#endif
+#endif
+
+#ifndef span_FEATURE_CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE
+# define span_FEATURE_CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE 0
+#endif
+
+#ifndef span_FEATURE_MEMBER_AT
+# define span_FEATURE_MEMBER_AT 0
+#endif
+
+#ifndef span_FEATURE_MEMBER_BACK_FRONT
+# define span_FEATURE_MEMBER_BACK_FRONT 1
+#endif
+
+#ifndef span_FEATURE_MEMBER_CALL_OPERATOR
+# define span_FEATURE_MEMBER_CALL_OPERATOR 0
+#endif
+
+#ifndef span_FEATURE_MEMBER_SWAP
+# define span_FEATURE_MEMBER_SWAP 0
+#endif
+
+#ifndef span_FEATURE_NON_MEMBER_FIRST_LAST_SUB
+# define span_FEATURE_NON_MEMBER_FIRST_LAST_SUB 0
+#elif span_FEATURE_NON_MEMBER_FIRST_LAST_SUB
+# define span_FEATURE_NON_MEMBER_FIRST_LAST_SUB_SPAN 1
+# define span_FEATURE_NON_MEMBER_FIRST_LAST_SUB_CONTAINER 1
+#endif
+
+#ifndef span_FEATURE_NON_MEMBER_FIRST_LAST_SUB_SPAN
+# define span_FEATURE_NON_MEMBER_FIRST_LAST_SUB_SPAN 0
+#endif
+
+#ifndef span_FEATURE_NON_MEMBER_FIRST_LAST_SUB_CONTAINER
+# define span_FEATURE_NON_MEMBER_FIRST_LAST_SUB_CONTAINER 0
+#endif
+
+#ifndef span_FEATURE_COMPARISON
+# define span_FEATURE_COMPARISON 0 // Note: C++20 does not provide comparison
+#endif
+
+#ifndef span_FEATURE_SAME
+# define span_FEATURE_SAME 0
+#endif
+
+#if span_FEATURE_SAME && !span_FEATURE_COMPARISON
+# error `span_FEATURE_SAME` requires `span_FEATURE_COMPARISON`
+#endif
+
+#ifndef span_FEATURE_MAKE_SPAN
+#ifdef span_FEATURE_MAKE_SPAN_TO_STD
+# define span_FEATURE_MAKE_SPAN span_IN_STD( span_FEATURE_MAKE_SPAN_TO_STD )
+#else
+# define span_FEATURE_MAKE_SPAN 0
+# define span_FEATURE_MAKE_SPAN_TO_STD 0
+#endif
+#endif
+
+#ifndef span_FEATURE_BYTE_SPAN
+# define span_FEATURE_BYTE_SPAN 0
+#endif
+
+// Control presence of exception handling (try and auto discover):
+
+#ifndef span_CONFIG_NO_EXCEPTIONS
+# if defined(_MSC_VER)
+# include <cstddef> // for _HAS_EXCEPTIONS
+# endif
+# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS)
+# define span_CONFIG_NO_EXCEPTIONS 0
+# else
+# define span_CONFIG_NO_EXCEPTIONS 1
+# undef span_CONFIG_CONTRACT_VIOLATION_THROWS
+# undef span_CONFIG_CONTRACT_VIOLATION_TERMINATES
+# define span_CONFIG_CONTRACT_VIOLATION_THROWS 0
+# define span_CONFIG_CONTRACT_VIOLATION_TERMINATES 1
+# endif
+#endif
+
+// Control pre- and postcondition violation behaviour:
+
+#if defined( span_CONFIG_CONTRACT_LEVEL_ON )
+# define span_CONFIG_CONTRACT_LEVEL_MASK 0x11
+#elif defined( span_CONFIG_CONTRACT_LEVEL_OFF )
+# define span_CONFIG_CONTRACT_LEVEL_MASK 0x00
+#elif defined( span_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY )
+# define span_CONFIG_CONTRACT_LEVEL_MASK 0x01
+#elif defined( span_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY )
+# define span_CONFIG_CONTRACT_LEVEL_MASK 0x10
+#else
+# define span_CONFIG_CONTRACT_LEVEL_MASK 0x11
+#endif
+
+#if defined( span_CONFIG_CONTRACT_VIOLATION_THROWS )
+# define span_CONFIG_CONTRACT_VIOLATION_THROWS_V span_CONFIG_CONTRACT_VIOLATION_THROWS
+#else
+# define span_CONFIG_CONTRACT_VIOLATION_THROWS_V 0
+#endif
+
+#if defined( span_CONFIG_CONTRACT_VIOLATION_THROWS ) && span_CONFIG_CONTRACT_VIOLATION_THROWS && \
+ defined( span_CONFIG_CONTRACT_VIOLATION_TERMINATES ) && span_CONFIG_CONTRACT_VIOLATION_TERMINATES
+# error Please define none or one of span_CONFIG_CONTRACT_VIOLATION_THROWS and span_CONFIG_CONTRACT_VIOLATION_TERMINATES to 1, but not both.
+#endif
+
+// C++ language version detection (C++20 is speculative):
+// Note: VC14.0/1900 (VS2015) lacks too much from C++14.
+
+#ifndef span_CPLUSPLUS
+# if defined(_MSVC_LANG ) && !defined(__clang__)
+# define span_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG )
+# else
+# define span_CPLUSPLUS __cplusplus
+# endif
+#endif
+
+#define span_CPP98_OR_GREATER ( span_CPLUSPLUS >= 199711L )
+#define span_CPP11_OR_GREATER ( span_CPLUSPLUS >= 201103L )
+#define span_CPP14_OR_GREATER ( span_CPLUSPLUS >= 201402L )
+#define span_CPP17_OR_GREATER ( span_CPLUSPLUS >= 201703L )
+#define span_CPP20_OR_GREATER ( span_CPLUSPLUS >= 202000L )
+
+// C++ language version (represent 98 as 3):
+
+#define span_CPLUSPLUS_V ( span_CPLUSPLUS / 100 - (span_CPLUSPLUS > 200000 ? 2000 : 1994) )
+
+#define span_IN_STD( v ) ( ((v) == 98 ? 3 : (v)) >= span_CPLUSPLUS_V )
+
+#define span_CONFIG( feature ) ( span_CONFIG_##feature )
+#define span_FEATURE( feature ) ( span_FEATURE_##feature )
+#define span_FEATURE_TO_STD( feature ) ( span_IN_STD( span_FEATURE( feature##_TO_STD ) ) )
+
+// Use C++20 std::span if available and requested:
+
+#if span_CPP20_OR_GREATER && defined(__has_include )
+# if __has_include( <span> )
+# define span_HAVE_STD_SPAN 1
+# else
+# define span_HAVE_STD_SPAN 0
+# endif
+#else
+# define span_HAVE_STD_SPAN 0
+#endif
+
+#define span_USES_STD_SPAN ( (span_CONFIG_SELECT_SPAN == span_SPAN_STD) || ((span_CONFIG_SELECT_SPAN == span_SPAN_DEFAULT) && span_HAVE_STD_SPAN) )
+
+//
+// Use C++20 std::span:
+//
+
+#if span_USES_STD_SPAN
+
+#include <span>
+
+namespace nonstd {
+
+using std::span;
+
+// Note: C++20 does not provide comparison
+// using std::operator==;
+// using std::operator!=;
+// using std::operator<;
+// using std::operator<=;
+// using std::operator>;
+// using std::operator>=;
+} // namespace nonstd
+
+#else // span_USES_STD_SPAN
+
+#include <algorithm>
+
+// Compiler versions:
+//
+// MSVC++ 6.0 _MSC_VER == 1200 span_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0)
+// MSVC++ 7.0 _MSC_VER == 1300 span_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002)
+// MSVC++ 7.1 _MSC_VER == 1310 span_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003)
+// MSVC++ 8.0 _MSC_VER == 1400 span_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005)
+// MSVC++ 9.0 _MSC_VER == 1500 span_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008)
+// MSVC++ 10.0 _MSC_VER == 1600 span_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010)
+// MSVC++ 11.0 _MSC_VER == 1700 span_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012)
+// MSVC++ 12.0 _MSC_VER == 1800 span_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013)
+// MSVC++ 14.0 _MSC_VER == 1900 span_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015)
+// MSVC++ 14.1 _MSC_VER >= 1910 span_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017)
+// MSVC++ 14.2 _MSC_VER >= 1920 span_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019)
+
+#if defined(_MSC_VER ) && !defined(__clang__)
+# define span_COMPILER_MSVC_VER (_MSC_VER )
+# define span_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) )
+#else
+# define span_COMPILER_MSVC_VER 0
+# define span_COMPILER_MSVC_VERSION 0
+#endif
+
+#define span_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) )
+
+#if defined(__clang__)
+# define span_COMPILER_CLANG_VERSION span_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
+#else
+# define span_COMPILER_CLANG_VERSION 0
+#endif
+
+#if defined(__GNUC__) && !defined(__clang__)
+# define span_COMPILER_GNUC_VERSION span_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
+#else
+# define span_COMPILER_GNUC_VERSION 0
+#endif
+
+// half-open range [lo..hi):
+#define span_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) )
+
+// Compiler warning suppression:
+
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wundef"
+# pragma clang diagnostic ignored "-Wmismatched-tags"
+# define span_RESTORE_WARNINGS() _Pragma( "clang diagnostic pop" )
+
+#elif defined __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wundef"
+# define span_RESTORE_WARNINGS() _Pragma( "GCC diagnostic pop" )
+
+#elif span_COMPILER_MSVC_VER >= 1900
+# define span_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes))
+# define span_RESTORE_WARNINGS() __pragma(warning(pop ))
+
+// Suppress the following MSVC GSL warnings:
+// - C26439, gsl::f.6 : special function 'function' can be declared 'noexcept'
+// - C26440, gsl::f.6 : function 'function' can be declared 'noexcept'
+// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions;
+// use brace initialization, gsl::narrow_cast or gsl::narrow
+// - C26473: gsl::t.1 : don't cast between pointer types where the source type and the target type are the same
+// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead
+// - C26490: gsl::t.1 : don't use reinterpret_cast
+
+span_DISABLE_MSVC_WARNINGS( 26439 26440 26472 26473 26481 26490 )
+
+#else
+# define span_RESTORE_WARNINGS() /*empty*/
+#endif
+
+// Presence of language and library features:
+
+#ifdef _HAS_CPP0X
+# define span_HAS_CPP0X _HAS_CPP0X
+#else
+# define span_HAS_CPP0X 0
+#endif
+
+#define span_CPP11_80 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1400)
+#define span_CPP11_90 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1500)
+#define span_CPP11_100 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1600)
+#define span_CPP11_110 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1700)
+#define span_CPP11_120 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1800)
+#define span_CPP11_140 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1900)
+
+#define span_CPP14_000 (span_CPP14_OR_GREATER)
+#define span_CPP14_120 (span_CPP14_OR_GREATER || span_COMPILER_MSVC_VER >= 1800)
+#define span_CPP14_140 (span_CPP14_OR_GREATER || span_COMPILER_MSVC_VER >= 1900)
+
+#define span_CPP17_000 (span_CPP17_OR_GREATER)
+
+// Presence of C++11 language features:
+
+#define span_HAVE_ALIAS_TEMPLATE span_CPP11_140
+#define span_HAVE_AUTO span_CPP11_100
+#define span_HAVE_CONSTEXPR_11 span_CPP11_140
+#define span_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG span_CPP11_120
+#define span_HAVE_EXPLICIT_CONVERSION span_CPP11_140
+#define span_HAVE_INITIALIZER_LIST span_CPP11_120
+#define span_HAVE_IS_DEFAULT span_CPP11_140
+#define span_HAVE_IS_DELETE span_CPP11_140
+#define span_HAVE_NOEXCEPT span_CPP11_140
+#define span_HAVE_NULLPTR span_CPP11_100
+#define span_HAVE_STATIC_ASSERT span_CPP11_100
+
+// Presence of C++14 language features:
+
+#define span_HAVE_CONSTEXPR_14 span_CPP14_000
+
+// Presence of C++17 language features:
+
+#define span_HAVE_DEPRECATED span_CPP17_000
+#define span_HAVE_NODISCARD span_CPP17_000
+#define span_HAVE_NORETURN span_CPP17_000
+
+// MSVC: template parameter deduction guides since Visual Studio 2017 v15.7
+
+#if defined(__cpp_deduction_guides)
+# define span_HAVE_DEDUCTION_GUIDES 1
+#else
+# define span_HAVE_DEDUCTION_GUIDES (span_CPP17_OR_GREATER && ! span_BETWEEN( span_COMPILER_MSVC_VER, 1, 1913 ))
+#endif
+
+// Presence of C++ library features:
+
+#define span_HAVE_ADDRESSOF span_CPP17_000
+#define span_HAVE_ARRAY span_CPP11_110
+#define span_HAVE_BYTE span_CPP17_000
+#define span_HAVE_CONDITIONAL span_CPP11_120
+#define span_HAVE_CONTAINER_DATA_METHOD (span_CPP11_140 || ( span_COMPILER_MSVC_VER >= 1500 && span_HAS_CPP0X ))
+#define span_HAVE_DATA span_CPP17_000
+#define span_HAVE_LONGLONG span_CPP11_80
+#define span_HAVE_REMOVE_CONST span_CPP11_110
+#define span_HAVE_SNPRINTF span_CPP11_140
+#define span_HAVE_STRUCT_BINDING span_CPP11_120
+#define span_HAVE_TYPE_TRAITS span_CPP11_90
+
+// Presence of byte-lite:
+
+#ifdef NONSTD_BYTE_LITE_HPP
+# define span_HAVE_NONSTD_BYTE 1
+#else
+# define span_HAVE_NONSTD_BYTE 0
+#endif
+
+// C++ feature usage:
+
+#if span_HAVE_ADDRESSOF
+# define span_ADDRESSOF(x) std::addressof(x)
+#else
+# define span_ADDRESSOF(x) (&x)
+#endif
+
+#if span_HAVE_CONSTEXPR_11
+# define span_constexpr constexpr
+#else
+# define span_constexpr /*span_constexpr*/
+#endif
+
+#if span_HAVE_CONSTEXPR_14
+# define span_constexpr14 constexpr
+#else
+# define span_constexpr14 /*span_constexpr*/
+#endif
+
+#if span_HAVE_EXPLICIT_CONVERSION
+# define span_explicit explicit
+#else
+# define span_explicit /*explicit*/
+#endif
+
+#if span_HAVE_IS_DELETE
+# define span_is_delete = delete
+#else
+# define span_is_delete
+#endif
+
+#if span_HAVE_IS_DELETE
+# define span_is_delete_access public
+#else
+# define span_is_delete_access private
+#endif
+
+#if span_HAVE_NOEXCEPT && ! span_CONFIG_CONTRACT_VIOLATION_THROWS_V
+# define span_noexcept noexcept
+#else
+# define span_noexcept /*noexcept*/
+#endif
+
+#if span_HAVE_NULLPTR
+# define span_nullptr nullptr
+#else
+# define span_nullptr NULL
+#endif
+
+#if span_HAVE_DEPRECATED
+# define span_deprecated(msg) [[deprecated(msg)]]
+#else
+# define span_deprecated(msg) /*[[deprecated]]*/
+#endif
+
+#if span_HAVE_NODISCARD
+# define span_nodiscard [[nodiscard]]
+#else
+# define span_nodiscard /*[[nodiscard]]*/
+#endif
+
+#if span_HAVE_NORETURN
+# define span_noreturn [[noreturn]]
+#else
+# define span_noreturn /*[[noreturn]]*/
+#endif
+
+// Other features:
+
+#define span_HAVE_CONSTRAINED_SPAN_CONTAINER_CTOR span_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG
+#define span_HAVE_ITERATOR_CTOR span_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG
+
+// Additional includes:
+
+#if span_HAVE( ADDRESSOF )
+# include <memory>
+#endif
+
+#if span_HAVE( ARRAY )
+# include <array>
+#endif
+
+#if span_HAVE( BYTE )
+# include <cstddef>
+#endif
+
+#if span_HAVE( DATA )
+# include <iterator> // for std::data(), std::size()
+#endif
+
+#if span_HAVE( TYPE_TRAITS )
+# include <type_traits>
+#endif
+
+#if ! span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR )
+# include <vector>
+#endif
+
+#if span_FEATURE( MEMBER_AT ) > 1
+# include <cstdio>
+#endif
+
+#if ! span_CONFIG( NO_EXCEPTIONS )
+# include <stdexcept>
+#endif
+
+// Contract violation
+
+#define span_ELIDE_CONTRACT_EXPECTS ( 0 == ( span_CONFIG_CONTRACT_LEVEL_MASK & 0x01 ) )
+#define span_ELIDE_CONTRACT_ENSURES ( 0 == ( span_CONFIG_CONTRACT_LEVEL_MASK & 0x10 ) )
+
+#if span_ELIDE_CONTRACT_EXPECTS
+# define span_constexpr_exp span_constexpr
+# define span_EXPECTS( cond ) /* Expect elided */
+#else
+# define span_constexpr_exp span_constexpr14
+# define span_EXPECTS( cond ) span_CONTRACT_CHECK( "Precondition", cond )
+#endif
+
+#if span_ELIDE_CONTRACT_ENSURES
+# define span_constexpr_ens span_constexpr
+# define span_ENSURES( cond ) /* Ensures elided */
+#else
+# define span_constexpr_ens span_constexpr14
+# define span_ENSURES( cond ) span_CONTRACT_CHECK( "Postcondition", cond )
+#endif
+
+#define span_CONTRACT_CHECK( type, cond ) \
+ cond ? static_cast< void >( 0 ) \
+ : nonstd::span_lite::detail::report_contract_violation( span_LOCATION( __FILE__, __LINE__ ) ": " type " violation." )
+
+#ifdef __GNUG__
+# define span_LOCATION( file, line ) file ":" span_STRINGIFY( line )
+#else
+# define span_LOCATION( file, line ) file "(" span_STRINGIFY( line ) ")"
+#endif
+
+// Method enabling
+
+#if span_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG )
+
+#define span_REQUIRES_0(VA) \
+ template< bool B = (VA), typename std::enable_if<B, int>::type = 0 >
+
+# if span_BETWEEN( span_COMPILER_MSVC_VERSION, 1, 140 )
+// VS 2013 and earlier seem to have trouble with SFINAE for default non-type arguments
+# define span_REQUIRES_T(VA) \
+ , typename = typename std::enable_if< ( VA ), nonstd::span_lite::detail::enabler >::type
+# else
+# define span_REQUIRES_T(VA) \
+ , typename std::enable_if< (VA), int >::type = 0
+# endif
+
+#define span_REQUIRES_R(R, VA) \
+ typename std::enable_if< (VA), R>::type
+
+#define span_REQUIRES_A(VA) \
+ , typename std::enable_if< (VA), void*>::type = nullptr
+
+#else
+
+# define span_REQUIRES_0(VA) /*empty*/
+# define span_REQUIRES_T(VA) /*empty*/
+# define span_REQUIRES_R(R, VA) R
+# define span_REQUIRES_A(VA) /*empty*/
+
+#endif
+
+namespace nonstd {
+namespace span_lite {
+
+// [views.constants], constants
+
+typedef span_CONFIG_EXTENT_TYPE extent_t;
+typedef span_CONFIG_SIZE_TYPE size_t;
+
+span_constexpr const extent_t dynamic_extent = static_cast<extent_t>( -1 );
+
+template< class T, extent_t Extent = dynamic_extent >
+class span;
+
+// Tag to select span constructor taking a container (prevent ms-gsl warning C26426):
+
+struct with_container_t { span_constexpr with_container_t() span_noexcept {} };
+const span_constexpr with_container_t with_container;
+
+// C++11 emulation:
+
+namespace std11 {
+
+#if span_HAVE( REMOVE_CONST )
+
+using std::remove_cv;
+using std::remove_const;
+using std::remove_volatile;
+
+#else
+
+template< class T > struct remove_const { typedef T type; };
+template< class T > struct remove_const< T const > { typedef T type; };
+
+template< class T > struct remove_volatile { typedef T type; };
+template< class T > struct remove_volatile< T volatile > { typedef T type; };
+
+template< class T >
+struct remove_cv
+{
+ typedef typename std11::remove_volatile< typename std11::remove_const< T >::type >::type type;
+};
+
+#endif // span_HAVE( REMOVE_CONST )
+
+#if span_HAVE( TYPE_TRAITS )
+
+using std::is_same;
+using std::is_signed;
+using std::integral_constant;
+using std::true_type;
+using std::false_type;
+using std::remove_reference;
+
+#else
+
+template< class T, T v > struct integral_constant { enum { value = v }; };
+typedef integral_constant< bool, true > true_type;
+typedef integral_constant< bool, false > false_type;
+
+template< class T, class U > struct is_same : false_type{};
+template< class T > struct is_same<T, T> : true_type{};
+
+template< typename T > struct is_signed : false_type {};
+template<> struct is_signed<signed char> : true_type {};
+template<> struct is_signed<signed int > : true_type {};
+template<> struct is_signed<signed long> : true_type {};
+
+#endif
+
+} // namespace std11
+
+// C++17 emulation:
+
+namespace std17 {
+
+template< bool v > struct bool_constant : std11::integral_constant<bool, v>{};
+
+#if span_CPP11_120
+
+template< class...>
+using void_t = void;
+
+#endif
+
+#if span_HAVE( DATA )
+
+using std::data;
+using std::size;
+
+#elif span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR )
+
+template< typename T, std::size_t N >
+inline span_constexpr auto size( const T(&)[N] ) span_noexcept -> size_t
+{
+ return N;
+}
+
+template< typename C >
+inline span_constexpr auto size( C const & cont ) -> decltype( cont.size() )
+{
+ return cont.size();
+}
+
+template< typename T, std::size_t N >
+inline span_constexpr auto data( T(&arr)[N] ) span_noexcept -> T*
+{
+ return &arr[0];
+}
+
+template< typename C >
+inline span_constexpr auto data( C & cont ) -> decltype( cont.data() )
+{
+ return cont.data();
+}
+
+template< typename C >
+inline span_constexpr auto data( C const & cont ) -> decltype( cont.data() )
+{
+ return cont.data();
+}
+
+template< typename E >
+inline span_constexpr auto data( std::initializer_list<E> il ) span_noexcept -> E const *
+{
+ return il.begin();
+}
+
+#endif // span_HAVE( DATA )
+
+#if span_HAVE( BYTE )
+using std::byte;
+#elif span_HAVE( NONSTD_BYTE )
+using nonstd::byte;
+#endif
+
+} // namespace std17
+
+// C++20 emulation:
+
+namespace std20 {
+
+#if span_HAVE( DEDUCTION_GUIDES )
+template< class T >
+using iter_reference_t = decltype( *std::declval<T&>() );
+#endif
+
+} // namespace std20
+
+// Implementation details:
+
+namespace detail {
+
+/*enum*/ struct enabler{};
+
+template< typename T >
+span_constexpr bool is_positive( T x )
+{
+ return std11::is_signed<T>::value ? x >= 0 : true;
+}
+
+#if span_HAVE( TYPE_TRAITS )
+
+template< class Q >
+struct is_span_oracle : std::false_type{};
+
+template< class T, span_CONFIG_EXTENT_TYPE Extent >
+struct is_span_oracle< span<T, Extent> > : std::true_type{};
+
+template< class Q >
+struct is_span : is_span_oracle< typename std::remove_cv<Q>::type >{};
+
+template< class Q >
+struct is_std_array_oracle : std::false_type{};
+
+#if span_HAVE( ARRAY )
+
+template< class T, std::size_t Extent >
+struct is_std_array_oracle< std::array<T, Extent> > : std::true_type{};
+
+#endif
+
+template< class Q >
+struct is_std_array : is_std_array_oracle< typename std::remove_cv<Q>::type >{};
+
+template< class Q >
+struct is_array : std::false_type {};
+
+template< class T >
+struct is_array<T[]> : std::true_type {};
+
+template< class T, std::size_t N >
+struct is_array<T[N]> : std::true_type {};
+
+#if span_CPP11_140 && ! span_BETWEEN( span_COMPILER_GNUC_VERSION, 1, 500 )
+
+template< class, class = void >
+struct has_size_and_data : std::false_type{};
+
+template< class C >
+struct has_size_and_data
+<
+ C, std17::void_t<
+ decltype( std17::size(std::declval<C>()) ),
+ decltype( std17::data(std::declval<C>()) ) >
+> : std::true_type{};
+
+template< class, class, class = void >
+struct is_compatible_element : std::false_type {};
+
+template< class C, class E >
+struct is_compatible_element
+<
+ C, E, std17::void_t<
+ decltype( std17::data(std::declval<C>()) ) >
+> : std::is_convertible< typename std::remove_pointer<decltype( std17::data( std::declval<C&>() ) )>::type(*)[], E(*)[] >{};
+
+template< class C >
+struct is_container : std17::bool_constant
+<
+ ! is_span< C >::value
+ && ! is_array< C >::value
+ && ! is_std_array< C >::value
+ && has_size_and_data< C >::value
+>{};
+
+template< class C, class E >
+struct is_compatible_container : std17::bool_constant
+<
+ is_container<C>::value
+ && is_compatible_element<C,E>::value
+>{};
+
+#else // span_CPP11_140
+
+template<
+ class C, class E
+ span_REQUIRES_T((
+ ! is_span< C >::value
+ && ! is_array< C >::value
+ && ! is_std_array< C >::value
+ && ( std::is_convertible< typename std::remove_pointer<decltype( std17::data( std::declval<C&>() ) )>::type(*)[], E(*)[] >::value)
+ // && has_size_and_data< C >::value
+ ))
+ , class = decltype( std17::size(std::declval<C>()) )
+ , class = decltype( std17::data(std::declval<C>()) )
+>
+struct is_compatible_container : std::true_type{};
+
+#endif // span_CPP11_140
+
+#endif // span_HAVE( TYPE_TRAITS )
+
+#if ! span_CONFIG( NO_EXCEPTIONS )
+#if span_FEATURE( MEMBER_AT ) > 1
+
+// format index and size:
+
+#if defined(__clang__)
+# pragma clang diagnostic ignored "-Wlong-long"
+#elif defined __GNUC__
+# pragma GCC diagnostic ignored "-Wformat=ll"
+# pragma GCC diagnostic ignored "-Wlong-long"
+#endif
+
+span_noreturn inline void throw_out_of_range( size_t idx, size_t size )
+{
+ const char fmt[] = "span::at(): index '%lli' is out of range [0..%lli)";
+ char buffer[ 2 * 20 + sizeof fmt ];
+ sprintf( buffer, fmt, static_cast<long long>(idx), static_cast<long long>(size) );
+
+ throw std::out_of_range( buffer );
+}
+
+#else // MEMBER_AT
+
+span_noreturn inline void throw_out_of_range( size_t /*idx*/, size_t /*size*/ )
+{
+ throw std::out_of_range( "span::at(): index outside span" );
+}
+#endif // MEMBER_AT
+#endif // NO_EXCEPTIONS
+
+#if span_CONFIG( CONTRACT_VIOLATION_THROWS_V )
+
+struct contract_violation : std::logic_error
+{
+ explicit contract_violation( char const * const message )
+ : std::logic_error( message )
+ {}
+};
+
+inline void report_contract_violation( char const * msg )
+{
+ throw contract_violation( msg );
+}
+
+#else // span_CONFIG( CONTRACT_VIOLATION_THROWS_V )
+
+span_noreturn inline void report_contract_violation( char const * /*msg*/ ) span_noexcept
+{
+ std::terminate();
+}
+
+#endif // span_CONFIG( CONTRACT_VIOLATION_THROWS_V )
+
+} // namespace detail
+
+// Prevent signed-unsigned mismatch:
+
+#define span_sizeof(T) static_cast<extent_t>( sizeof(T) )
+
+template< class T >
+inline span_constexpr size_t to_size( T size )
+{
+ return static_cast<size_t>( size );
+}
+
+//
+// [views.span] - A view over a contiguous, single-dimension sequence of objects
+//
+template< class T, extent_t Extent /*= dynamic_extent*/ >
+class span
+{
+public:
+ // constants and types
+
+ typedef T element_type;
+ typedef typename std11::remove_cv< T >::type value_type;
+
+ typedef T & reference;
+ typedef T * pointer;
+ typedef T const * const_pointer;
+ typedef T const & const_reference;
+
+ typedef size_t size_type;
+ typedef extent_t extent_type;
+
+ typedef pointer iterator;
+ typedef const_pointer const_iterator;
+
+ typedef std::ptrdiff_t difference_type;
+
+ typedef std::reverse_iterator< iterator > reverse_iterator;
+ typedef std::reverse_iterator< const_iterator > const_reverse_iterator;
+
+// static constexpr extent_type extent = Extent;
+ enum { extent = Extent };
+
+ // 26.7.3.2 Constructors, copy, and assignment [span.cons]
+
+ span_REQUIRES_0(
+ ( Extent == 0 ) ||
+ ( Extent == dynamic_extent )
+ )
+ span_constexpr span() span_noexcept
+ : data_( span_nullptr )
+ , size_( 0 )
+ {
+ // span_EXPECTS( data() == span_nullptr );
+ // span_EXPECTS( size() == 0 );
+ }
+
+#if span_HAVE( ITERATOR_CTOR )
+ // Didn't yet succeed in combining the next two constructors:
+
+ span_constexpr_exp span( std::nullptr_t, size_type count )
+ : data_( span_nullptr )
+ , size_( count )
+ {
+ span_EXPECTS( data_ == span_nullptr && count == 0 );
+ }
+
+ template< typename It
+ span_REQUIRES_T((
+ std::is_convertible<decltype(*std::declval<It&>()), element_type &>::value
+ ))
+ >
+ span_constexpr_exp span( It first, size_type count )
+ : data_( to_address( first ) )
+ , size_( count )
+ {
+ span_EXPECTS(
+ ( data_ == span_nullptr && count == 0 ) ||
+ ( data_ != span_nullptr && detail::is_positive( count ) )
+ );
+ }
+#else
+ span_constexpr_exp span( pointer ptr, size_type count )
+ : data_( ptr )
+ , size_( count )
+ {
+ span_EXPECTS(
+ ( ptr == span_nullptr && count == 0 ) ||
+ ( ptr != span_nullptr && detail::is_positive( count ) )
+ );
+ }
+#endif
+
+#if span_HAVE( ITERATOR_CTOR )
+ template< typename It, typename End
+ span_REQUIRES_T((
+ std::is_convertible<decltype(&*std::declval<It&>()), element_type *>::value
+ && ! std::is_convertible<End, std::size_t>::value
+ ))
+ >
+ span_constexpr_exp span( It first, End last )
+ : data_( to_address( first ) )
+ , size_( to_size( last - first ) )
+ {
+ span_EXPECTS(
+ last - first >= 0
+ );
+ }
+#else
+ span_constexpr_exp span( pointer first, pointer last )
+ : data_( first )
+ , size_( to_size( last - first ) )
+ {
+ span_EXPECTS(
+ last - first >= 0
+ );
+ }
+#endif
+
+ template< std::size_t N
+ span_REQUIRES_T((
+ (Extent == dynamic_extent || Extent == static_cast<extent_t>(N))
+ && std::is_convertible< value_type(*)[], element_type(*)[] >::value
+ ))
+ >
+ span_constexpr span( element_type ( &arr )[ N ] ) span_noexcept
+ : data_( span_ADDRESSOF( arr[0] ) )
+ , size_( N )
+ {}
+
+#if span_HAVE( ARRAY )
+
+ template< std::size_t N
+ span_REQUIRES_T((
+ (Extent == dynamic_extent || Extent == static_cast<extent_t>(N))
+ && std::is_convertible< value_type(*)[], element_type(*)[] >::value
+ ))
+ >
+# if span_FEATURE( CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE )
+ span_constexpr span( std::array< element_type, N > & arr ) span_noexcept
+# else
+ span_constexpr span( std::array< value_type, N > & arr ) span_noexcept
+# endif
+ : data_( arr.data() )
+ , size_( to_size( arr.size() ) )
+ {}
+
+ template< std::size_t N
+# if span_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG )
+ span_REQUIRES_T((
+ (Extent == dynamic_extent || Extent == static_cast<extent_t>(N))
+ && std::is_convertible< value_type(*)[], element_type(*)[] >::value
+ ))
+# endif
+ >
+ span_constexpr span( std::array< value_type, N> const & arr ) span_noexcept
+ : data_( arr.data() )
+ , size_( to_size( arr.size() ) )
+ {}
+
+#endif // span_HAVE( ARRAY )
+
+#if span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR )
+ template< class Container
+ span_REQUIRES_T((
+ detail::is_compatible_container< Container, element_type >::value
+ ))
+ >
+ span_constexpr span( Container & cont )
+ : data_( std17::data( cont ) )
+ , size_( to_size( std17::size( cont ) ) )
+ {}
+
+ template< class Container
+ span_REQUIRES_T((
+ std::is_const< element_type >::value
+ && detail::is_compatible_container< Container, element_type >::value
+ ))
+ >
+ span_constexpr span( Container const & cont )
+ : data_( std17::data( cont ) )
+ , size_( to_size( std17::size( cont ) ) )
+ {}
+
+#endif // span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR )
+
+#if span_FEATURE( WITH_CONTAINER )
+
+ template< class Container >
+ span_constexpr span( with_container_t, Container & cont )
+ : data_( cont.size() == 0 ? span_nullptr : span_ADDRESSOF( cont[0] ) )
+ , size_( to_size( cont.size() ) )
+ {}
+
+ template< class Container >
+ span_constexpr span( with_container_t, Container const & cont )
+ : data_( cont.size() == 0 ? span_nullptr : const_cast<pointer>( span_ADDRESSOF( cont[0] ) ) )
+ , size_( to_size( cont.size() ) )
+ {}
+#endif
+
+#if span_HAVE( IS_DEFAULT )
+ span_constexpr span( span const & other ) span_noexcept = default;
+
+ ~span() span_noexcept = default;
+
+ span_constexpr14 span & operator=( span const & other ) span_noexcept = default;
+#else
+ span_constexpr span( span const & other ) span_noexcept
+ : data_( other.data_ )
+ , size_( other.size_ )
+ {}
+
+ ~span() span_noexcept
+ {}
+
+ span_constexpr14 span & operator=( span const & other ) span_noexcept
+ {
+ data_ = other.data_;
+ size_ = other.size_;
+
+ return *this;
+ }
+#endif
+
+ template< class OtherElementType, extent_type OtherExtent
+ span_REQUIRES_T((
+ (Extent == dynamic_extent || OtherExtent == dynamic_extent || Extent == OtherExtent)
+ && std::is_convertible<OtherElementType(*)[], element_type(*)[]>::value
+ ))
+ >
+ span_constexpr_exp span( span<OtherElementType, OtherExtent> const & other ) span_noexcept
+ : data_( other.data() )
+ , size_( other.size() )
+ {
+ span_EXPECTS( OtherExtent == dynamic_extent || other.size() == to_size(OtherExtent) );
+ }
+
+ // 26.7.3.3 Subviews [span.sub]
+
+ template< extent_type Count >
+ span_constexpr_exp span< element_type, Count >
+ first() const
+ {
+ span_EXPECTS( detail::is_positive( Count ) && Count <= size() );
+
+ return span< element_type, Count >( data(), Count );
+ }
+
+ template< extent_type Count >
+ span_constexpr_exp span< element_type, Count >
+ last() const
+ {
+ span_EXPECTS( detail::is_positive( Count ) && Count <= size() );
+
+ return span< element_type, Count >( data() + (size() - Count), Count );
+ }
+
+#if span_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG )
+ template< size_type Offset, extent_type Count = dynamic_extent >
+#else
+ template< size_type Offset, extent_type Count /*= dynamic_extent*/ >
+#endif
+ span_constexpr_exp span< element_type, Count >
+ subspan() const
+ {
+ span_EXPECTS(
+ ( detail::is_positive( Offset ) && Offset <= size() ) &&
+ ( Count == dynamic_extent || (detail::is_positive( Count ) && Count + Offset <= size()) )
+ );
+
+ return span< element_type, Count >(
+ data() + Offset, Count != dynamic_extent ? Count : (Extent != dynamic_extent ? Extent - Offset : size() - Offset) );
+ }
+
+ span_constexpr_exp span< element_type, dynamic_extent >
+ first( size_type count ) const
+ {
+ span_EXPECTS( detail::is_positive( count ) && count <= size() );
+
+ return span< element_type, dynamic_extent >( data(), count );
+ }
+
+ span_constexpr_exp span< element_type, dynamic_extent >
+ last( size_type count ) const
+ {
+ span_EXPECTS( detail::is_positive( count ) && count <= size() );
+
+ return span< element_type, dynamic_extent >( data() + ( size() - count ), count );
+ }
+
+ span_constexpr_exp span< element_type, dynamic_extent >
+ subspan( size_type offset, size_type count = static_cast<size_type>(dynamic_extent) ) const
+ {
+ span_EXPECTS(
+ ( ( detail::is_positive( offset ) && offset <= size() ) ) &&
+ ( count == static_cast<size_type>(dynamic_extent) || ( detail::is_positive( count ) && offset + count <= size() ) )
+ );
+
+ return span< element_type, dynamic_extent >(
+ data() + offset, count == static_cast<size_type>(dynamic_extent) ? size() - offset : count );
+ }
+
+ // 26.7.3.4 Observers [span.obs]
+
+ span_constexpr size_type size() const span_noexcept
+ {
+ return size_;
+ }
+
+ span_constexpr std::ptrdiff_t ssize() const span_noexcept
+ {
+ return static_cast<std::ptrdiff_t>( size_ );
+ }
+
+ span_constexpr size_type size_bytes() const span_noexcept
+ {
+ return size() * to_size( sizeof( element_type ) );
+ }
+
+ span_nodiscard span_constexpr bool empty() const span_noexcept
+ {
+ return size() == 0;
+ }
+
+ // 26.7.3.5 Element access [span.elem]
+
+ span_constexpr_exp reference operator[]( size_type idx ) const
+ {
+ span_EXPECTS( detail::is_positive( idx ) && idx < size() );
+
+ return *( data() + idx );
+ }
+
+#if span_FEATURE( MEMBER_CALL_OPERATOR )
+ span_deprecated("replace operator() with operator[]")
+
+ span_constexpr_exp reference operator()( size_type idx ) const
+ {
+ span_EXPECTS( detail::is_positive( idx ) && idx < size() );
+
+ return *( data() + idx );
+ }
+#endif
+
+#if span_FEATURE( MEMBER_AT )
+ span_constexpr14 reference at( size_type idx ) const
+ {
+#if span_CONFIG( NO_EXCEPTIONS )
+ return this->operator[]( idx );
+#else
+ if ( !detail::is_positive( idx ) || size() <= idx )
+ {
+ detail::throw_out_of_range( idx, size() );
+ }
+ return *( data() + idx );
+#endif
+ }
+#endif
+
+ span_constexpr pointer data() const span_noexcept
+ {
+ return data_;
+ }
+
+#if span_FEATURE( MEMBER_BACK_FRONT )
+
+ span_constexpr_exp reference front() const span_noexcept
+ {
+ span_EXPECTS( ! empty() );
+
+ return *data();
+ }
+
+ span_constexpr_exp reference back() const span_noexcept
+ {
+ span_EXPECTS( ! empty() );
+
+ return *( data() + size() - 1 );
+ }
+
+#endif
+
+ // xx.x.x.x Modifiers [span.modifiers]
+
+#if span_FEATURE( MEMBER_SWAP )
+
+ span_constexpr14 void swap( span & other ) span_noexcept
+ {
+ using std::swap;
+ swap( data_, other.data_ );
+ swap( size_, other.size_ );
+ }
+#endif
+
+ // 26.7.3.6 Iterator support [span.iterators]
+
+ span_constexpr iterator begin() const span_noexcept
+ {
+#if span_CPP11_OR_GREATER
+ return { data() };
+#else
+ return iterator( data() );
+#endif
+ }
+
+ span_constexpr iterator end() const span_noexcept
+ {
+#if span_CPP11_OR_GREATER
+ return { data() + size() };
+#else
+ return iterator( data() + size() );
+#endif
+ }
+
+ span_constexpr const_iterator cbegin() const span_noexcept
+ {
+#if span_CPP11_OR_GREATER
+ return { data() };
+#else
+ return const_iterator( data() );
+#endif
+ }
+
+ span_constexpr const_iterator cend() const span_noexcept
+ {
+#if span_CPP11_OR_GREATER
+ return { data() + size() };
+#else
+ return const_iterator( data() + size() );
+#endif
+ }
+
+ span_constexpr reverse_iterator rbegin() const span_noexcept
+ {
+ return reverse_iterator( end() );
+ }
+
+ span_constexpr reverse_iterator rend() const span_noexcept
+ {
+ return reverse_iterator( begin() );
+ }
+
+ span_constexpr const_reverse_iterator crbegin() const span_noexcept
+ {
+ return const_reverse_iterator ( cend() );
+ }
+
+ span_constexpr const_reverse_iterator crend() const span_noexcept
+ {
+ return const_reverse_iterator( cbegin() );
+ }
+
+private:
+
+ // Note: C++20 has std::pointer_traits<Ptr>::to_address( it );
+
+#if span_HAVE( ITERATOR_CTOR )
+ static inline span_constexpr pointer to_address( std::nullptr_t ) span_noexcept
+ {
+ return nullptr;
+ }
+
+ template< typename U >
+ static inline span_constexpr U * to_address( U * p ) span_noexcept
+ {
+ return p;
+ }
+
+ template< typename Ptr
+ span_REQUIRES_T(( ! std::is_pointer<Ptr>::value ))
+ >
+ static inline span_constexpr pointer to_address( Ptr const & it ) span_noexcept
+ {
+ return to_address( it.operator->() );
+ }
+#endif // span_HAVE( ITERATOR_CTOR )
+
+private:
+ pointer data_;
+ size_type size_;
+};
+
+// class template argument deduction guides:
+
+#if span_HAVE( DEDUCTION_GUIDES )
+
+template< class T, size_t N >
+span( T (&)[N] ) -> span<T, static_cast<extent_t>(N)>;
+
+template< class T, size_t N >
+span( std::array<T, N> & ) -> span<T, static_cast<extent_t>(N)>;
+
+template< class T, size_t N >
+span( std::array<T, N> const & ) -> span<const T, static_cast<extent_t>(N)>;
+
+#if span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR )
+
+template< class Container >
+span( Container& ) -> span<typename Container::value_type>;
+
+template< class Container >
+span( Container const & ) -> span<const typename Container::value_type>;
+
+#endif
+
+// iterator: constraints: It satisfies contiguous_iterator.
+
+template< class It, class EndOrSize >
+span( It, EndOrSize ) -> span< typename std11::remove_reference< typename std20::iter_reference_t<It> >::type >;
+
+#endif // span_HAVE( DEDUCTION_GUIDES )
+
+// 26.7.3.7 Comparison operators [span.comparison]
+
+#if span_FEATURE( COMPARISON )
+#if span_FEATURE( SAME )
+
+template< class T1, extent_t E1, class T2, extent_t E2 >
+inline span_constexpr bool same( span<T1,E1> const & l, span<T2,E2> const & r ) span_noexcept
+{
+ return std11::is_same<T1, T2>::value
+ && l.size() == r.size()
+ && static_cast<void const*>( l.data() ) == r.data();
+}
+
+#endif
+
+template< class T1, extent_t E1, class T2, extent_t E2 >
+inline span_constexpr bool operator==( span<T1,E1> const & l, span<T2,E2> const & r )
+{
+ return
+#if span_FEATURE( SAME )
+ same( l, r ) ||
+#endif
+ ( l.size() == r.size() && std::equal( l.begin(), l.end(), r.begin() ) );
+}
+
+template< class T1, extent_t E1, class T2, extent_t E2 >
+inline span_constexpr bool operator<( span<T1,E1> const & l, span<T2,E2> const & r )
+{
+ return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() );
+}
+
+template< class T1, extent_t E1, class T2, extent_t E2 >
+inline span_constexpr bool operator!=( span<T1,E1> const & l, span<T2,E2> const & r )
+{
+ return !( l == r );
+}
+
+template< class T1, extent_t E1, class T2, extent_t E2 >
+inline span_constexpr bool operator<=( span<T1,E1> const & l, span<T2,E2> const & r )
+{
+ return !( r < l );
+}
+
+template< class T1, extent_t E1, class T2, extent_t E2 >
+inline span_constexpr bool operator>( span<T1,E1> const & l, span<T2,E2> const & r )
+{
+ return ( r < l );
+}
+
+template< class T1, extent_t E1, class T2, extent_t E2 >
+inline span_constexpr bool operator>=( span<T1,E1> const & l, span<T2,E2> const & r )
+{
+ return !( l < r );
+}
+
+#endif // span_FEATURE( COMPARISON )
+
+// 26.7.2.6 views of object representation [span.objectrep]
+
+#if span_HAVE( BYTE ) || span_HAVE( NONSTD_BYTE )
+
+// Avoid MSVC 14.1 (1910), VS 2017: warning C4307: '*': integral constant overflow:
+
+template< typename T, extent_t Extent >
+struct BytesExtent
+{
+#if span_CPP11_OR_GREATER
+ enum ET : extent_t { value = span_sizeof(T) * Extent };
+#else
+ enum ET { value = span_sizeof(T) * Extent };
+#endif
+};
+
+template< typename T >
+struct BytesExtent< T, dynamic_extent >
+{
+#if span_CPP11_OR_GREATER
+ enum ET : extent_t { value = dynamic_extent };
+#else
+ enum ET { value = dynamic_extent };
+#endif
+};
+
+template< class T, extent_t Extent >
+inline span_constexpr span< const std17::byte, BytesExtent<T, Extent>::value >
+as_bytes( span<T,Extent> spn ) span_noexcept
+{
+#if 0
+ return { reinterpret_cast< std17::byte const * >( spn.data() ), spn.size_bytes() };
+#else
+ return span< const std17::byte, BytesExtent<T, Extent>::value >(
+ reinterpret_cast< std17::byte const * >( spn.data() ), spn.size_bytes() ); // NOLINT
+#endif
+}
+
+template< class T, extent_t Extent >
+inline span_constexpr span< std17::byte, BytesExtent<T, Extent>::value >
+as_writable_bytes( span<T,Extent> spn ) span_noexcept
+{
+#if 0
+ return { reinterpret_cast< std17::byte * >( spn.data() ), spn.size_bytes() };
+#else
+ return span< std17::byte, BytesExtent<T, Extent>::value >(
+ reinterpret_cast< std17::byte * >( spn.data() ), spn.size_bytes() ); // NOLINT
+#endif
+}
+
+#endif // span_HAVE( BYTE ) || span_HAVE( NONSTD_BYTE )
+
+// 27.8 Container and view access [iterator.container]
+
+template< class T, extent_t Extent /*= dynamic_extent*/ >
+span_constexpr std::size_t size( span<T,Extent> const & spn )
+{
+ return static_cast<std::size_t>( spn.size() );
+}
+
+template< class T, extent_t Extent /*= dynamic_extent*/ >
+span_constexpr std::ptrdiff_t ssize( span<T,Extent> const & spn )
+{
+ return static_cast<std::ptrdiff_t>( spn.size() );
+}
+
+} // namespace span_lite
+} // namespace nonstd
+
+// make available in nonstd:
+
+namespace nonstd {
+
+using span_lite::dynamic_extent;
+
+using span_lite::span;
+
+using span_lite::with_container;
+
+#if span_FEATURE( COMPARISON )
+#if span_FEATURE( SAME )
+using span_lite::same;
+#endif
+
+using span_lite::operator==;
+using span_lite::operator!=;
+using span_lite::operator<;
+using span_lite::operator<=;
+using span_lite::operator>;
+using span_lite::operator>=;
+#endif
+
+#if span_HAVE( BYTE )
+using span_lite::as_bytes;
+using span_lite::as_writable_bytes;
+#endif
+
+using span_lite::size;
+using span_lite::ssize;
+
+} // namespace nonstd
+
+#endif // span_USES_STD_SPAN
+
+// make_span() [span-lite extension]:
+
+#if span_FEATURE( MAKE_SPAN ) || span_FEATURE( NON_MEMBER_FIRST_LAST_SUB_SPAN ) || span_FEATURE( NON_MEMBER_FIRST_LAST_SUB_CONTAINER )
+
+#if span_USES_STD_SPAN
+# define span_constexpr constexpr
+# define span_noexcept noexcept
+# define span_nullptr nullptr
+# ifndef span_CONFIG_EXTENT_TYPE
+# define span_CONFIG_EXTENT_TYPE std::size_t
+# endif
+using extent_t = span_CONFIG_EXTENT_TYPE;
+#endif // span_USES_STD_SPAN
+
+namespace nonstd {
+namespace span_lite {
+
+template< class T >
+inline span_constexpr span<T>
+make_span( T * ptr, size_t count ) span_noexcept
+{
+ return span<T>( ptr, count );
+}
+
+template< class T >
+inline span_constexpr span<T>
+make_span( T * first, T * last ) span_noexcept
+{
+ return span<T>( first, last );
+}
+
+template< class T, std::size_t N >
+inline span_constexpr span<T, static_cast<extent_t>(N)>
+make_span( T ( &arr )[ N ] ) span_noexcept
+{
+ return span<T, static_cast<extent_t>(N)>( &arr[ 0 ], N );
+}
+
+#if span_USES_STD_SPAN || span_HAVE( ARRAY )
+
+template< class T, std::size_t N >
+inline span_constexpr span<T, static_cast<extent_t>(N)>
+make_span( std::array< T, N > & arr ) span_noexcept
+{
+ return span<T, static_cast<extent_t>(N)>( arr );
+}
+
+template< class T, std::size_t N >
+inline span_constexpr span< const T, static_cast<extent_t>(N) >
+make_span( std::array< T, N > const & arr ) span_noexcept
+{
+ return span<const T, static_cast<extent_t>(N)>( arr );
+}
+
+#endif // span_HAVE( ARRAY )
+
+#if span_USES_STD_SPAN
+
+template< class Container, class EP = decltype( std::data(std::declval<Container&>())) >
+inline span_constexpr auto
+make_span( Container & cont ) span_noexcept -> span< typename std::remove_pointer<EP>::type >
+{
+ return span< typename std::remove_pointer<EP>::type >( cont );
+}
+
+template< class Container, class EP = decltype( std::data(std::declval<Container&>())) >
+inline span_constexpr auto
+make_span( Container const & cont ) span_noexcept -> span< const typename std::remove_pointer<EP>::type >
+{
+ return span< const typename std::remove_pointer<EP>::type >( cont );
+}
+
+#elif span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) && span_HAVE( AUTO )
+
+template< class Container, class EP = decltype( std17::data(std::declval<Container&>())) >
+inline span_constexpr auto
+make_span( Container & cont ) span_noexcept -> span< typename std::remove_pointer<EP>::type >
+{
+ return span< typename std::remove_pointer<EP>::type >( cont );
+}
+
+template< class Container, class EP = decltype( std17::data(std::declval<Container&>())) >
+inline span_constexpr auto
+make_span( Container const & cont ) span_noexcept -> span< const typename std::remove_pointer<EP>::type >
+{
+ return span< const typename std::remove_pointer<EP>::type >( cont );
+}
+
+#else
+
+template< class T >
+inline span_constexpr span<T>
+make_span( span<T> spn ) span_noexcept
+{
+ return spn;
+}
+
+template< class T, class Allocator >
+inline span_constexpr span<T>
+make_span( std::vector<T, Allocator> & cont ) span_noexcept
+{
+ return span<T>( with_container, cont );
+}
+
+template< class T, class Allocator >
+inline span_constexpr span<const T>
+make_span( std::vector<T, Allocator> const & cont ) span_noexcept
+{
+ return span<const T>( with_container, cont );
+}
+
+#endif // span_USES_STD_SPAN || ( ... )
+
+#if ! span_USES_STD_SPAN && span_FEATURE( WITH_CONTAINER )
+
+template< class Container >
+inline span_constexpr span<typename Container::value_type>
+make_span( with_container_t, Container & cont ) span_noexcept
+{
+ return span< typename Container::value_type >( with_container, cont );
+}
+
+template< class Container >
+inline span_constexpr span<const typename Container::value_type>
+make_span( with_container_t, Container const & cont ) span_noexcept
+{
+ return span< const typename Container::value_type >( with_container, cont );
+}
+
+#endif // ! span_USES_STD_SPAN && span_FEATURE( WITH_CONTAINER )
+
+// extensions: non-member views:
+// this feature implies the presence of make_span()
+
+#if span_FEATURE( NON_MEMBER_FIRST_LAST_SUB_SPAN )
+
+template< extent_t Count, class T, extent_t Extent >
+span_constexpr span<T, Count>
+first( span<T, Extent> spn )
+{
+ return spn.template first<Count>();
+}
+
+template< class T, extent_t Extent >
+span_constexpr span<T>
+first( span<T, Extent> spn, size_t count )
+{
+ return spn.first( count );
+}
+
+template< extent_t Count, class T, extent_t Extent >
+span_constexpr span<T, Count>
+last( span<T, Extent> spn )
+{
+ return spn.template last<Count>();
+}
+
+template< class T, extent_t Extent >
+span_constexpr span<T>
+last( span<T, Extent> spn, size_t count )
+{
+ return spn.last( count );
+}
+
+template< size_t Offset, extent_t Count, class T, extent_t Extent >
+span_constexpr span<T, Count>
+subspan( span<T, Extent> spn )
+{
+ return spn.template subspan<Offset, Count>();
+}
+
+template< class T, extent_t Extent >
+span_constexpr span<T>
+subspan( span<T, Extent> spn, size_t offset, extent_t count = dynamic_extent )
+{
+ return spn.subspan( offset, count );
+}
+
+#endif // span_FEATURE( NON_MEMBER_FIRST_LAST_SUB_SPAN )
+
+#if span_FEATURE( NON_MEMBER_FIRST_LAST_SUB_CONTAINER ) && span_CPP11_120
+
+template< extent_t Count, class T >
+span_constexpr auto
+first( T & t ) -> decltype( make_span(t).template first<Count>() )
+{
+ return make_span( t ).template first<Count>();
+}
+
+template< class T >
+span_constexpr auto
+first( T & t, size_t count ) -> decltype( make_span(t).first(count) )
+{
+ return make_span( t ).first( count );
+}
+
+template< extent_t Count, class T >
+span_constexpr auto
+last( T & t ) -> decltype( make_span(t).template last<Count>() )
+{
+ return make_span(t).template last<Count>();
+}
+
+template< class T >
+span_constexpr auto
+last( T & t, extent_t count ) -> decltype( make_span(t).last(count) )
+{
+ return make_span( t ).last( count );
+}
+
+template< size_t Offset, extent_t Count = dynamic_extent, class T >
+span_constexpr auto
+subspan( T & t ) -> decltype( make_span(t).template subspan<Offset, Count>() )
+{
+ return make_span( t ).template subspan<Offset, Count>();
+}
+
+template< class T >
+span_constexpr auto
+subspan( T & t, size_t offset, extent_t count = dynamic_extent ) -> decltype( make_span(t).subspan(offset, count) )
+{
+ return make_span( t ).subspan( offset, count );
+}
+
+#endif // span_FEATURE( NON_MEMBER_FIRST_LAST_SUB_CONTAINER )
+
+} // namespace span_lite
+} // namespace nonstd
+
+// make available in nonstd:
+
+namespace nonstd {
+using span_lite::make_span;
+
+#if span_FEATURE( NON_MEMBER_FIRST_LAST_SUB_SPAN ) || ( span_FEATURE( NON_MEMBER_FIRST_LAST_SUB_CONTAINER ) && span_CPP11_120 )
+
+using span_lite::first;
+using span_lite::last;
+using span_lite::subspan;
+
+#endif // span_FEATURE( NON_MEMBER_FIRST_LAST_SUB_[SPAN|CONTAINER] )
+
+} // namespace nonstd
+
+#endif // #if span_FEATURE_TO_STD( MAKE_SPAN )
+
+#if span_CPP11_OR_GREATER && span_FEATURE( BYTE_SPAN ) && ( span_HAVE( BYTE ) || span_HAVE( NONSTD_BYTE ) )
+
+namespace nonstd {
+namespace span_lite {
+
+template< class T >
+inline span_constexpr auto
+byte_span( T & t ) span_noexcept -> span< std17::byte, span_sizeof(T) >
+{
+ return span< std17::byte, span_sizeof(t) >( reinterpret_cast< std17::byte * >( &t ), span_sizeof(T) );
+}
+
+template< class T >
+inline span_constexpr auto
+byte_span( T const & t ) span_noexcept -> span< const std17::byte, span_sizeof(T) >
+{
+ return span< const std17::byte, span_sizeof(t) >( reinterpret_cast< std17::byte const * >( &t ), span_sizeof(T) );
+}
+
+} // namespace span_lite
+} // namespace nonstd
+
+// make available in nonstd:
+
+namespace nonstd {
+using span_lite::byte_span;
+} // namespace nonstd
+
+#endif // span_FEATURE( BYTE_SPAN )
+
+#if span_HAVE( STRUCT_BINDING )
+
+#if span_CPP14_OR_GREATER
+# include <tuple>
+#elif span_CPP11_OR_GREATER
+# include <tuple>
+namespace std {
+ template< std::size_t I, typename T >
+ using tuple_element_t = typename tuple_element<I, T>::type;
+}
+#else
+namespace std {
+ template< typename T >
+ class tuple_size; /*undefined*/
+
+ template< std::size_t I, typename T >
+ class tuple_element; /* undefined */
+}
+#endif // span_CPP14_OR_GREATER
+
+namespace std {
+
+// 26.7.X Tuple interface
+
+// std::tuple_size<>:
+
+template< typename ElementType, nonstd::span_lite::extent_t Extent >
+class tuple_size< nonstd::span<ElementType, Extent> > : public integral_constant<size_t, static_cast<size_t>(Extent)> {};
+
+// std::tuple_size<>: Leave undefined for dynamic extent:
+
+template< typename ElementType >
+class tuple_size< nonstd::span<ElementType, nonstd::dynamic_extent> >;
+
+// std::tuple_element<>:
+
+template< size_t I, typename ElementType, nonstd::span_lite::extent_t Extent >
+class tuple_element< I, nonstd::span<ElementType, Extent> >
+{
+public:
+#if span_HAVE( STATIC_ASSERT )
+ static_assert( Extent != nonstd::dynamic_extent && I < Extent, "tuple_element<I,span>: dynamic extent or index out of range" );
+#endif
+ using type = ElementType;
+};
+
+// std::get<>(), 2 variants:
+
+template< size_t I, typename ElementType, nonstd::span_lite::extent_t Extent >
+span_constexpr ElementType & get( nonstd::span<ElementType, Extent> & spn ) span_noexcept
+{
+#if span_HAVE( STATIC_ASSERT )
+ static_assert( Extent != nonstd::dynamic_extent && I < Extent, "get<>(span): dynamic extent or index out of range" );
+#endif
+ return spn[I];
+}
+
+template< size_t I, typename ElementType, nonstd::span_lite::extent_t Extent >
+span_constexpr ElementType const & get( nonstd::span<ElementType, Extent> const & spn ) span_noexcept
+{
+#if span_HAVE( STATIC_ASSERT )
+ static_assert( Extent != nonstd::dynamic_extent && I < Extent, "get<>(span): dynamic extent or index out of range" );
+#endif
+ return spn[I];
+}
+
+} // end namespace std
+
+#endif // span_HAVE( STRUCT_BINDING )
+
+#if ! span_USES_STD_SPAN
+span_RESTORE_WARNINGS()
+#endif // span_USES_STD_SPAN
+
+#endif // NONSTD_SPAN_HPP_INCLUDED
+++ /dev/null
-// Copyright 2017-2020 by Martin Moene
-//
-// string-view lite, a C++17-like string_view for C++98 and later.
-// For more information see https://github.com/martinmoene/string-view-lite
-//
-// Distributed under the Boost Software License, Version 1.0.
-// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-
-#pragma once
-
-#ifndef NONSTD_SV_LITE_H_INCLUDED
-#define NONSTD_SV_LITE_H_INCLUDED
-
-#define string_view_lite_MAJOR 1
-#define string_view_lite_MINOR 6
-#define string_view_lite_PATCH 0
-
-#define string_view_lite_VERSION nssv_STRINGIFY(string_view_lite_MAJOR) "." nssv_STRINGIFY(string_view_lite_MINOR) "." nssv_STRINGIFY(string_view_lite_PATCH)
-
-#define nssv_STRINGIFY( x ) nssv_STRINGIFY_( x )
-#define nssv_STRINGIFY_( x ) #x
-
-// string-view lite configuration:
-
-#define nssv_STRING_VIEW_DEFAULT 0
-#define nssv_STRING_VIEW_NONSTD 1
-#define nssv_STRING_VIEW_STD 2
-
-// tweak header support:
-
-#ifdef __has_include
-# if __has_include(<nonstd/string_view.tweak.hpp>)
-# include <nonstd/string_view.tweak.hpp>
-# endif
-#define nssv_HAVE_TWEAK_HEADER 1
-#else
-#define nssv_HAVE_TWEAK_HEADER 0
-//# pragma message("string_view.hpp: Note: Tweak header not supported.")
-#endif
-
-// string_view selection and configuration:
-
-#if !defined( nssv_CONFIG_SELECT_STRING_VIEW )
-# define nssv_CONFIG_SELECT_STRING_VIEW ( nssv_HAVE_STD_STRING_VIEW ? nssv_STRING_VIEW_STD : nssv_STRING_VIEW_NONSTD )
-#endif
-
-#if defined( nssv_CONFIG_SELECT_STD_STRING_VIEW ) || defined( nssv_CONFIG_SELECT_NONSTD_STRING_VIEW )
-# error nssv_CONFIG_SELECT_STD_STRING_VIEW and nssv_CONFIG_SELECT_NONSTD_STRING_VIEW are deprecated and removed, please use nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_...
-#endif
-
-#ifndef nssv_CONFIG_STD_SV_OPERATOR
-# define nssv_CONFIG_STD_SV_OPERATOR 0
-#endif
-
-#ifndef nssv_CONFIG_USR_SV_OPERATOR
-# define nssv_CONFIG_USR_SV_OPERATOR 1
-#endif
-
-#ifdef nssv_CONFIG_CONVERSION_STD_STRING
-# define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS nssv_CONFIG_CONVERSION_STD_STRING
-# define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS nssv_CONFIG_CONVERSION_STD_STRING
-#endif
-
-#ifndef nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS
-# define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS 1
-#endif
-
-#ifndef nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
-# define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS 1
-#endif
-
-// Control presence of exception handling (try and auto discover):
-
-#ifndef nssv_CONFIG_NO_EXCEPTIONS
-# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
-# define nssv_CONFIG_NO_EXCEPTIONS 0
-# else
-# define nssv_CONFIG_NO_EXCEPTIONS 1
-# endif
-#endif
-
-// C++ language version detection (C++20 is speculative):
-// Note: VC14.0/1900 (VS2015) lacks too much from C++14.
-
-#ifndef nssv_CPLUSPLUS
-# if defined(_MSVC_LANG ) && !defined(__clang__)
-# define nssv_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG )
-# else
-# define nssv_CPLUSPLUS __cplusplus
-# endif
-#endif
-
-#define nssv_CPP98_OR_GREATER ( nssv_CPLUSPLUS >= 199711L )
-#define nssv_CPP11_OR_GREATER ( nssv_CPLUSPLUS >= 201103L )
-#define nssv_CPP11_OR_GREATER_ ( nssv_CPLUSPLUS >= 201103L )
-#define nssv_CPP14_OR_GREATER ( nssv_CPLUSPLUS >= 201402L )
-#define nssv_CPP17_OR_GREATER ( nssv_CPLUSPLUS >= 201703L )
-#define nssv_CPP20_OR_GREATER ( nssv_CPLUSPLUS >= 202000L )
-
-// use C++17 std::string_view if available and requested:
-
-#if nssv_CPP17_OR_GREATER && defined(__has_include )
-# if __has_include( <string_view> )
-# define nssv_HAVE_STD_STRING_VIEW 1
-# else
-# define nssv_HAVE_STD_STRING_VIEW 0
-# endif
-#else
-# define nssv_HAVE_STD_STRING_VIEW 0
-#endif
-
-#define nssv_USES_STD_STRING_VIEW ( (nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_STD) || ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_DEFAULT) && nssv_HAVE_STD_STRING_VIEW) )
-
-#define nssv_HAVE_STARTS_WITH ( nssv_CPP20_OR_GREATER || !nssv_USES_STD_STRING_VIEW )
-#define nssv_HAVE_ENDS_WITH nssv_HAVE_STARTS_WITH
-
-//
-// Use C++17 std::string_view:
-//
-
-#if nssv_USES_STD_STRING_VIEW
-
-#include <string_view>
-
-// Extensions for std::string:
-
-#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
-
-namespace nonstd {
-
-template< class CharT, class Traits, class Allocator = std::allocator<CharT> >
-std::basic_string<CharT, Traits, Allocator>
-to_string( std::basic_string_view<CharT, Traits> v, Allocator const & a = Allocator() )
-{
- return std::basic_string<CharT,Traits, Allocator>( v.begin(), v.end(), a );
-}
-
-template< class CharT, class Traits, class Allocator >
-std::basic_string_view<CharT, Traits>
-to_string_view( std::basic_string<CharT, Traits, Allocator> const & s )
-{
- return std::basic_string_view<CharT, Traits>( s.data(), s.size() );
-}
-
-// Literal operators sv and _sv:
-
-#if nssv_CONFIG_STD_SV_OPERATOR
-
-using namespace std::literals::string_view_literals;
-
-#endif
-
-#if nssv_CONFIG_USR_SV_OPERATOR
-
-inline namespace literals {
-inline namespace string_view_literals {
-
-
-constexpr std::string_view operator "" _sv( const char* str, size_t len ) noexcept // (1)
-{
- return std::string_view{ str, len };
-}
-
-constexpr std::u16string_view operator "" _sv( const char16_t* str, size_t len ) noexcept // (2)
-{
- return std::u16string_view{ str, len };
-}
-
-constexpr std::u32string_view operator "" _sv( const char32_t* str, size_t len ) noexcept // (3)
-{
- return std::u32string_view{ str, len };
-}
-
-constexpr std::wstring_view operator "" _sv( const wchar_t* str, size_t len ) noexcept // (4)
-{
- return std::wstring_view{ str, len };
-}
-
-}} // namespace literals::string_view_literals
-
-#endif // nssv_CONFIG_USR_SV_OPERATOR
-
-} // namespace nonstd
-
-#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
-
-namespace nonstd {
-
-using std::string_view;
-using std::wstring_view;
-using std::u16string_view;
-using std::u32string_view;
-using std::basic_string_view;
-
-// literal "sv" and "_sv", see above
-
-using std::operator==;
-using std::operator!=;
-using std::operator<;
-using std::operator<=;
-using std::operator>;
-using std::operator>=;
-
-using std::operator<<;
-
-} // namespace nonstd
-
-#else // nssv_HAVE_STD_STRING_VIEW
-
-//
-// Before C++17: use string_view lite:
-//
-
-// Compiler versions:
-//
-// MSVC++ 6.0 _MSC_VER == 1200 nssv_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0)
-// MSVC++ 7.0 _MSC_VER == 1300 nssv_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002)
-// MSVC++ 7.1 _MSC_VER == 1310 nssv_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003)
-// MSVC++ 8.0 _MSC_VER == 1400 nssv_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005)
-// MSVC++ 9.0 _MSC_VER == 1500 nssv_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008)
-// MSVC++ 10.0 _MSC_VER == 1600 nssv_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010)
-// MSVC++ 11.0 _MSC_VER == 1700 nssv_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012)
-// MSVC++ 12.0 _MSC_VER == 1800 nssv_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013)
-// MSVC++ 14.0 _MSC_VER == 1900 nssv_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015)
-// MSVC++ 14.1 _MSC_VER >= 1910 nssv_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017)
-// MSVC++ 14.2 _MSC_VER >= 1920 nssv_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019)
-
-#if defined(_MSC_VER ) && !defined(__clang__)
-# define nssv_COMPILER_MSVC_VER (_MSC_VER )
-# define nssv_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) )
-#else
-# define nssv_COMPILER_MSVC_VER 0
-# define nssv_COMPILER_MSVC_VERSION 0
-#endif
-
-#define nssv_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) )
-
-#if defined( __apple_build_version__ )
-# define nssv_COMPILER_APPLECLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
-# define nssv_COMPILER_CLANG_VERSION 0
-#elif defined( __clang__ )
-# define nssv_COMPILER_APPLECLANG_VERSION 0
-# define nssv_COMPILER_CLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
-#else
-# define nssv_COMPILER_APPLECLANG_VERSION 0
-# define nssv_COMPILER_CLANG_VERSION 0
-#endif
-
-#if defined(__GNUC__) && !defined(__clang__)
-# define nssv_COMPILER_GNUC_VERSION nssv_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
-#else
-# define nssv_COMPILER_GNUC_VERSION 0
-#endif
-
-// half-open range [lo..hi):
-#define nssv_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) )
-
-// Presence of language and library features:
-
-#ifdef _HAS_CPP0X
-# define nssv_HAS_CPP0X _HAS_CPP0X
-#else
-# define nssv_HAS_CPP0X 0
-#endif
-
-// Unless defined otherwise below, consider VC14 as C++11 for variant-lite:
-
-#if nssv_COMPILER_MSVC_VER >= 1900
-# undef nssv_CPP11_OR_GREATER
-# define nssv_CPP11_OR_GREATER 1
-#endif
-
-#define nssv_CPP11_90 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1500)
-#define nssv_CPP11_100 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1600)
-#define nssv_CPP11_110 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1700)
-#define nssv_CPP11_120 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1800)
-#define nssv_CPP11_140 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1900)
-#define nssv_CPP11_141 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1910)
-
-#define nssv_CPP14_000 (nssv_CPP14_OR_GREATER)
-#define nssv_CPP17_000 (nssv_CPP17_OR_GREATER)
-
-// Presence of C++11 language features:
-
-#define nssv_HAVE_CONSTEXPR_11 nssv_CPP11_140
-#define nssv_HAVE_EXPLICIT_CONVERSION nssv_CPP11_140
-#define nssv_HAVE_INLINE_NAMESPACE nssv_CPP11_140
-#define nssv_HAVE_NOEXCEPT nssv_CPP11_140
-#define nssv_HAVE_NULLPTR nssv_CPP11_100
-#define nssv_HAVE_REF_QUALIFIER nssv_CPP11_140
-#define nssv_HAVE_UNICODE_LITERALS nssv_CPP11_140
-#define nssv_HAVE_USER_DEFINED_LITERALS nssv_CPP11_140
-#define nssv_HAVE_WCHAR16_T nssv_CPP11_100
-#define nssv_HAVE_WCHAR32_T nssv_CPP11_100
-
-#if ! ( ( nssv_CPP11_OR_GREATER && nssv_COMPILER_CLANG_VERSION ) || nssv_BETWEEN( nssv_COMPILER_CLANG_VERSION, 300, 400 ) )
-# define nssv_HAVE_STD_DEFINED_LITERALS nssv_CPP11_140
-#else
-# define nssv_HAVE_STD_DEFINED_LITERALS 0
-#endif
-
-// Presence of C++14 language features:
-
-#define nssv_HAVE_CONSTEXPR_14 nssv_CPP14_000
-
-// Presence of C++17 language features:
-
-#define nssv_HAVE_NODISCARD nssv_CPP17_000
-
-// Presence of C++ library features:
-
-#define nssv_HAVE_STD_HASH nssv_CPP11_120
-
-// Presence of compiler intrinsics:
-
-// Providing char-type specializations for compare() and length() that
-// use compiler intrinsics can improve compile- and run-time performance.
-//
-// The challenge is in using the right combinations of builtin availablity
-// and its constexpr-ness.
-//
-// | compiler | __builtin_memcmp (constexpr) | memcmp (constexpr) |
-// |----------|------------------------------|---------------------|
-// | clang | 4.0 (>= 4.0 ) | any (? ) |
-// | clang-a | 9.0 (>= 9.0 ) | any (? ) |
-// | gcc | any (constexpr) | any (? ) |
-// | msvc | >= 14.2 C++17 (>= 14.2 ) | any (? ) |
-
-#define nssv_HAVE_BUILTIN_VER ( (nssv_CPP17_000 && nssv_COMPILER_MSVC_VERSION >= 142) || nssv_COMPILER_GNUC_VERSION > 0 || nssv_COMPILER_CLANG_VERSION >= 400 || nssv_COMPILER_APPLECLANG_VERSION >= 900 )
-#define nssv_HAVE_BUILTIN_CE ( nssv_HAVE_BUILTIN_VER )
-
-#define nssv_HAVE_BUILTIN_MEMCMP ( (nssv_HAVE_CONSTEXPR_14 && nssv_HAVE_BUILTIN_CE) || !nssv_HAVE_CONSTEXPR_14 )
-#define nssv_HAVE_BUILTIN_STRLEN ( (nssv_HAVE_CONSTEXPR_11 && nssv_HAVE_BUILTIN_CE) || !nssv_HAVE_CONSTEXPR_11 )
-
-#ifdef __has_builtin
-# define nssv_HAVE_BUILTIN( x ) __has_builtin( x )
-#else
-# define nssv_HAVE_BUILTIN( x ) 0
-#endif
-
-#if nssv_HAVE_BUILTIN(__builtin_memcmp) || nssv_HAVE_BUILTIN_VER
-# define nssv_BUILTIN_MEMCMP __builtin_memcmp
-#else
-# define nssv_BUILTIN_MEMCMP memcmp
-#endif
-
-#if nssv_HAVE_BUILTIN(__builtin_strlen) || nssv_HAVE_BUILTIN_VER
-# define nssv_BUILTIN_STRLEN __builtin_strlen
-#else
-# define nssv_BUILTIN_STRLEN strlen
-#endif
-
-// C++ feature usage:
-
-#if nssv_HAVE_CONSTEXPR_11
-# define nssv_constexpr constexpr
-#else
-# define nssv_constexpr /*constexpr*/
-#endif
-
-#if nssv_HAVE_CONSTEXPR_14
-# define nssv_constexpr14 constexpr
-#else
-# define nssv_constexpr14 /*constexpr*/
-#endif
-
-#if nssv_HAVE_EXPLICIT_CONVERSION
-# define nssv_explicit explicit
-#else
-# define nssv_explicit /*explicit*/
-#endif
-
-#if nssv_HAVE_INLINE_NAMESPACE
-# define nssv_inline_ns inline
-#else
-# define nssv_inline_ns /*inline*/
-#endif
-
-#if nssv_HAVE_NOEXCEPT
-# define nssv_noexcept noexcept
-#else
-# define nssv_noexcept /*noexcept*/
-#endif
-
-//#if nssv_HAVE_REF_QUALIFIER
-//# define nssv_ref_qual &
-//# define nssv_refref_qual &&
-//#else
-//# define nssv_ref_qual /*&*/
-//# define nssv_refref_qual /*&&*/
-//#endif
-
-#if nssv_HAVE_NULLPTR
-# define nssv_nullptr nullptr
-#else
-# define nssv_nullptr NULL
-#endif
-
-#if nssv_HAVE_NODISCARD
-# define nssv_nodiscard [[nodiscard]]
-#else
-# define nssv_nodiscard /*[[nodiscard]]*/
-#endif
-
-// Additional includes:
-
-#include <algorithm>
-#include <cassert>
-#include <iterator>
-#include <limits>
-#include <ostream>
-#include <string> // std::char_traits<>
-
-#if ! nssv_CONFIG_NO_EXCEPTIONS
-# include <stdexcept>
-#endif
-
-#if nssv_CPP11_OR_GREATER
-# include <type_traits>
-#endif
-
-// Clang, GNUC, MSVC warning suppression macros:
-
-#if defined(__clang__)
-# pragma clang diagnostic ignored "-Wreserved-user-defined-literal"
-# pragma clang diagnostic push
-# pragma clang diagnostic ignored "-Wuser-defined-literals"
-#elif defined(__GNUC__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wliteral-suffix"
-#endif // __clang__
-
-#if nssv_COMPILER_MSVC_VERSION >= 140
-# define nssv_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]]
-# define nssv_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress: code) )
-# define nssv_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes))
-#else
-# define nssv_SUPPRESS_MSGSL_WARNING(expr)
-# define nssv_SUPPRESS_MSVC_WARNING(code, descr)
-# define nssv_DISABLE_MSVC_WARNINGS(codes)
-#endif
-
-#if defined(__clang__)
-# define nssv_RESTORE_WARNINGS() _Pragma("clang diagnostic pop")
-#elif defined(__GNUC__)
-# define nssv_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop")
-#elif nssv_COMPILER_MSVC_VERSION >= 140
-# define nssv_RESTORE_WARNINGS() __pragma(warning(pop ))
-#else
-# define nssv_RESTORE_WARNINGS()
-#endif
-
-// Suppress the following MSVC (GSL) warnings:
-// - C4455, non-gsl : 'operator ""sv': literal suffix identifiers that do not
-// start with an underscore are reserved
-// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions;
-// use brace initialization, gsl::narrow_cast or gsl::narow
-// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead
-
-nssv_DISABLE_MSVC_WARNINGS( 4455 26481 26472 )
-//nssv_DISABLE_CLANG_WARNINGS( "-Wuser-defined-literals" )
-//nssv_DISABLE_GNUC_WARNINGS( -Wliteral-suffix )
-
-namespace nonstd { namespace sv_lite {
-
-namespace detail {
-
-// support constexpr comparison in C++14;
-// for C++17 and later, use provided traits:
-
-template< typename CharT >
-inline nssv_constexpr14 int compare( CharT const * s1, CharT const * s2, std::size_t count )
-{
- while ( count-- != 0 )
- {
- if ( *s1 < *s2 ) return -1;
- if ( *s1 > *s2 ) return +1;
- ++s1; ++s2;
- }
- return 0;
-}
-
-#if nssv_HAVE_BUILTIN_MEMCMP
-
-// specialization of compare() for char, see also generic compare() above:
-
-inline nssv_constexpr14 int compare( char const * s1, char const * s2, std::size_t count )
-{
- return nssv_BUILTIN_MEMCMP( s1, s2, count );
-}
-
-#endif
-
-#if nssv_HAVE_BUILTIN_STRLEN
-
-// specialization of length() for char, see also generic length() further below:
-
-inline nssv_constexpr std::size_t length( char const * s )
-{
- return nssv_BUILTIN_STRLEN( s );
-}
-
-#endif
-
-#if defined(__OPTIMIZE__)
-
-// gcc, clang provide __OPTIMIZE__
-// Expect tail call optimization to make length() non-recursive:
-
-template< typename CharT >
-inline nssv_constexpr std::size_t length( CharT * s, std::size_t result = 0 )
-{
- return *s == '\0' ? result : length( s + 1, result + 1 );
-}
-
-#else // OPTIMIZE
-
-// non-recursive:
-
-template< typename CharT >
-inline nssv_constexpr14 std::size_t length( CharT * s )
-{
- std::size_t result = 0;
- while ( *s++ != '\0' )
- {
- ++result;
- }
- return result;
-}
-
-#endif // OPTIMIZE
-
-} // namespace detail
-
-template
-<
- class CharT,
- class Traits = std::char_traits<CharT>
->
-class basic_string_view;
-
-//
-// basic_string_view:
-//
-
-template
-<
- class CharT,
- class Traits /* = std::char_traits<CharT> */
->
-class basic_string_view
-{
-public:
- // Member types:
-
- typedef Traits traits_type;
- typedef CharT value_type;
-
- typedef CharT * pointer;
- typedef CharT const * const_pointer;
- typedef CharT & reference;
- typedef CharT const & const_reference;
-
- typedef const_pointer iterator;
- typedef const_pointer const_iterator;
- typedef std::reverse_iterator< const_iterator > reverse_iterator;
- typedef std::reverse_iterator< const_iterator > const_reverse_iterator;
-
- typedef std::size_t size_type;
- typedef std::ptrdiff_t difference_type;
-
- // 24.4.2.1 Construction and assignment:
-
- nssv_constexpr basic_string_view() nssv_noexcept
- : data_( nssv_nullptr )
- , size_( 0 )
- {}
-
-#if nssv_CPP11_OR_GREATER
- nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept = default;
-#else
- nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept
- : data_( other.data_)
- , size_( other.size_)
- {}
-#endif
-
- nssv_constexpr basic_string_view( CharT const * s, size_type count ) nssv_noexcept // non-standard noexcept
- : data_( s )
- , size_( count )
- {}
-
- nssv_constexpr basic_string_view( CharT const * s) nssv_noexcept // non-standard noexcept
- : data_( s )
-#if nssv_CPP17_OR_GREATER
- , size_( Traits::length(s) )
-#elif nssv_CPP11_OR_GREATER
- , size_( detail::length(s) )
-#else
- , size_( Traits::length(s) )
-#endif
- {}
-
- // Assignment:
-
-#if nssv_CPP11_OR_GREATER
- nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept = default;
-#else
- nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept
- {
- data_ = other.data_;
- size_ = other.size_;
- return *this;
- }
-#endif
-
- // 24.4.2.2 Iterator support:
-
- nssv_constexpr const_iterator begin() const nssv_noexcept { return data_; }
- nssv_constexpr const_iterator end() const nssv_noexcept { return data_ + size_; }
-
- nssv_constexpr const_iterator cbegin() const nssv_noexcept { return begin(); }
- nssv_constexpr const_iterator cend() const nssv_noexcept { return end(); }
-
- nssv_constexpr const_reverse_iterator rbegin() const nssv_noexcept { return const_reverse_iterator( end() ); }
- nssv_constexpr const_reverse_iterator rend() const nssv_noexcept { return const_reverse_iterator( begin() ); }
-
- nssv_constexpr const_reverse_iterator crbegin() const nssv_noexcept { return rbegin(); }
- nssv_constexpr const_reverse_iterator crend() const nssv_noexcept { return rend(); }
-
- // 24.4.2.3 Capacity:
-
- nssv_constexpr size_type size() const nssv_noexcept { return size_; }
- nssv_constexpr size_type length() const nssv_noexcept { return size_; }
- nssv_constexpr size_type max_size() const nssv_noexcept { return (std::numeric_limits< size_type >::max)(); }
-
- // since C++20
- nssv_nodiscard nssv_constexpr bool empty() const nssv_noexcept
- {
- return 0 == size_;
- }
-
- // 24.4.2.4 Element access:
-
- nssv_constexpr const_reference operator[]( size_type pos ) const
- {
- return data_at( pos );
- }
-
- nssv_constexpr14 const_reference at( size_type pos ) const
- {
-#if nssv_CONFIG_NO_EXCEPTIONS
- assert( pos < size() );
-#else
- if ( pos >= size() )
- {
- throw std::out_of_range("nonstd::string_view::at()");
- }
-#endif
- return data_at( pos );
- }
-
- nssv_constexpr const_reference front() const { return data_at( 0 ); }
- nssv_constexpr const_reference back() const { return data_at( size() - 1 ); }
-
- nssv_constexpr const_pointer data() const nssv_noexcept { return data_; }
-
- // 24.4.2.5 Modifiers:
-
- nssv_constexpr14 void remove_prefix( size_type n )
- {
- assert( n <= size() );
- data_ += n;
- size_ -= n;
- }
-
- nssv_constexpr14 void remove_suffix( size_type n )
- {
- assert( n <= size() );
- size_ -= n;
- }
-
- nssv_constexpr14 void swap( basic_string_view & other ) nssv_noexcept
- {
- using std::swap;
- swap( data_, other.data_ );
- swap( size_, other.size_ );
- }
-
- // 24.4.2.6 String operations:
-
- size_type copy( CharT * dest, size_type n, size_type pos = 0 ) const
- {
-#if nssv_CONFIG_NO_EXCEPTIONS
- assert( pos <= size() );
-#else
- if ( pos > size() )
- {
- throw std::out_of_range("nonstd::string_view::copy()");
- }
-#endif
- const size_type rlen = (std::min)( n, size() - pos );
-
- (void) Traits::copy( dest, data() + pos, rlen );
-
- return rlen;
- }
-
- nssv_constexpr14 basic_string_view substr( size_type pos = 0, size_type n = npos ) const
- {
-#if nssv_CONFIG_NO_EXCEPTIONS
- assert( pos <= size() );
-#else
- if ( pos > size() )
- {
- throw std::out_of_range("nonstd::string_view::substr()");
- }
-#endif
- return basic_string_view( data() + pos, (std::min)( n, size() - pos ) );
- }
-
- // compare(), 6x:
-
- nssv_constexpr14 int compare( basic_string_view other ) const nssv_noexcept // (1)
- {
-#if nssv_CPP17_OR_GREATER
- if ( const int result = Traits::compare( data(), other.data(), (std::min)( size(), other.size() ) ) )
-#else
- if ( const int result = detail::compare( data(), other.data(), (std::min)( size(), other.size() ) ) )
-#endif
- {
- return result;
- }
-
- return size() == other.size() ? 0 : size() < other.size() ? -1 : 1;
- }
-
- nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other ) const // (2)
- {
- return substr( pos1, n1 ).compare( other );
- }
-
- nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other, size_type pos2, size_type n2 ) const // (3)
- {
- return substr( pos1, n1 ).compare( other.substr( pos2, n2 ) );
- }
-
- nssv_constexpr int compare( CharT const * s ) const // (4)
- {
- return compare( basic_string_view( s ) );
- }
-
- nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s ) const // (5)
- {
- return substr( pos1, n1 ).compare( basic_string_view( s ) );
- }
-
- nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s, size_type n2 ) const // (6)
- {
- return substr( pos1, n1 ).compare( basic_string_view( s, n2 ) );
- }
-
- // 24.4.2.7 Searching:
-
- // starts_with(), 3x, since C++20:
-
- nssv_constexpr bool starts_with( basic_string_view v ) const nssv_noexcept // (1)
- {
- return size() >= v.size() && compare( 0, v.size(), v ) == 0;
- }
-
- nssv_constexpr bool starts_with( CharT c ) const nssv_noexcept // (2)
- {
- return starts_with( basic_string_view( &c, 1 ) );
- }
-
- nssv_constexpr bool starts_with( CharT const * s ) const // (3)
- {
- return starts_with( basic_string_view( s ) );
- }
-
- // ends_with(), 3x, since C++20:
-
- nssv_constexpr bool ends_with( basic_string_view v ) const nssv_noexcept // (1)
- {
- return size() >= v.size() && compare( size() - v.size(), npos, v ) == 0;
- }
-
- nssv_constexpr bool ends_with( CharT c ) const nssv_noexcept // (2)
- {
- return ends_with( basic_string_view( &c, 1 ) );
- }
-
- nssv_constexpr bool ends_with( CharT const * s ) const // (3)
- {
- return ends_with( basic_string_view( s ) );
- }
-
- // find(), 4x:
-
- nssv_constexpr14 size_type find( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1)
- {
- return assert( v.size() == 0 || v.data() != nssv_nullptr )
- , pos >= size()
- ? npos
- : to_pos( std::search( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) );
- }
-
- nssv_constexpr14 size_type find( CharT c, size_type pos = 0 ) const nssv_noexcept // (2)
- {
- return find( basic_string_view( &c, 1 ), pos );
- }
-
- nssv_constexpr14 size_type find( CharT const * s, size_type pos, size_type n ) const // (3)
- {
- return find( basic_string_view( s, n ), pos );
- }
-
- nssv_constexpr14 size_type find( CharT const * s, size_type pos = 0 ) const // (4)
- {
- return find( basic_string_view( s ), pos );
- }
-
- // rfind(), 4x:
-
- nssv_constexpr14 size_type rfind( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1)
- {
- if ( size() < v.size() )
- {
- return npos;
- }
-
- if ( v.empty() )
- {
- return (std::min)( size(), pos );
- }
-
- const_iterator last = cbegin() + (std::min)( size() - v.size(), pos ) + v.size();
- const_iterator result = std::find_end( cbegin(), last, v.cbegin(), v.cend(), Traits::eq );
-
- return result != last ? size_type( result - cbegin() ) : npos;
- }
-
- nssv_constexpr14 size_type rfind( CharT c, size_type pos = npos ) const nssv_noexcept // (2)
- {
- return rfind( basic_string_view( &c, 1 ), pos );
- }
-
- nssv_constexpr14 size_type rfind( CharT const * s, size_type pos, size_type n ) const // (3)
- {
- return rfind( basic_string_view( s, n ), pos );
- }
-
- nssv_constexpr14 size_type rfind( CharT const * s, size_type pos = npos ) const // (4)
- {
- return rfind( basic_string_view( s ), pos );
- }
-
- // find_first_of(), 4x:
-
- nssv_constexpr size_type find_first_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1)
- {
- return pos >= size()
- ? npos
- : to_pos( std::find_first_of( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) );
- }
-
- nssv_constexpr size_type find_first_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2)
- {
- return find_first_of( basic_string_view( &c, 1 ), pos );
- }
-
- nssv_constexpr size_type find_first_of( CharT const * s, size_type pos, size_type n ) const // (3)
- {
- return find_first_of( basic_string_view( s, n ), pos );
- }
-
- nssv_constexpr size_type find_first_of( CharT const * s, size_type pos = 0 ) const // (4)
- {
- return find_first_of( basic_string_view( s ), pos );
- }
-
- // find_last_of(), 4x:
-
- nssv_constexpr size_type find_last_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1)
- {
- return empty()
- ? npos
- : pos >= size()
- ? find_last_of( v, size() - 1 )
- : to_pos( std::find_first_of( const_reverse_iterator( cbegin() + pos + 1 ), crend(), v.cbegin(), v.cend(), Traits::eq ) );
- }
-
- nssv_constexpr size_type find_last_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2)
- {
- return find_last_of( basic_string_view( &c, 1 ), pos );
- }
-
- nssv_constexpr size_type find_last_of( CharT const * s, size_type pos, size_type count ) const // (3)
- {
- return find_last_of( basic_string_view( s, count ), pos );
- }
-
- nssv_constexpr size_type find_last_of( CharT const * s, size_type pos = npos ) const // (4)
- {
- return find_last_of( basic_string_view( s ), pos );
- }
-
- // find_first_not_of(), 4x:
-
- nssv_constexpr size_type find_first_not_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1)
- {
- return pos >= size()
- ? npos
- : to_pos( std::find_if( cbegin() + pos, cend(), not_in_view( v ) ) );
- }
-
- nssv_constexpr size_type find_first_not_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2)
- {
- return find_first_not_of( basic_string_view( &c, 1 ), pos );
- }
-
- nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos, size_type count ) const // (3)
- {
- return find_first_not_of( basic_string_view( s, count ), pos );
- }
-
- nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos = 0 ) const // (4)
- {
- return find_first_not_of( basic_string_view( s ), pos );
- }
-
- // find_last_not_of(), 4x:
-
- nssv_constexpr size_type find_last_not_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1)
- {
- return empty()
- ? npos
- : pos >= size()
- ? find_last_not_of( v, size() - 1 )
- : to_pos( std::find_if( const_reverse_iterator( cbegin() + pos + 1 ), crend(), not_in_view( v ) ) );
- }
-
- nssv_constexpr size_type find_last_not_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2)
- {
- return find_last_not_of( basic_string_view( &c, 1 ), pos );
- }
-
- nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos, size_type count ) const // (3)
- {
- return find_last_not_of( basic_string_view( s, count ), pos );
- }
-
- nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos = npos ) const // (4)
- {
- return find_last_not_of( basic_string_view( s ), pos );
- }
-
- // Constants:
-
-#if nssv_CPP17_OR_GREATER
- static nssv_constexpr size_type npos = size_type(-1);
-#elif nssv_CPP11_OR_GREATER
- enum : size_type { npos = size_type(-1) };
-#else
- enum { npos = size_type(-1) };
-#endif
-
-private:
- struct not_in_view
- {
- const basic_string_view v;
-
- nssv_constexpr explicit not_in_view( basic_string_view v_ ) : v( v_ ) {}
-
- nssv_constexpr bool operator()( CharT c ) const
- {
- return npos == v.find_first_of( c );
- }
- };
-
- nssv_constexpr size_type to_pos( const_iterator it ) const
- {
- return it == cend() ? npos : size_type( it - cbegin() );
- }
-
- nssv_constexpr size_type to_pos( const_reverse_iterator it ) const
- {
- return it == crend() ? npos : size_type( crend() - it - 1 );
- }
-
- nssv_constexpr const_reference data_at( size_type pos ) const
- {
-#if nssv_BETWEEN( nssv_COMPILER_GNUC_VERSION, 1, 500 )
- return data_[pos];
-#else
- return assert( pos < size() ), data_[pos];
-#endif
- }
-
-private:
- const_pointer data_;
- size_type size_;
-
-public:
-#if nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS
-
- template< class Allocator >
- basic_string_view( std::basic_string<CharT, Traits, Allocator> const & s ) nssv_noexcept
- : data_( s.data() )
- , size_( s.size() )
- {}
-
-#if nssv_HAVE_EXPLICIT_CONVERSION
-
- template< class Allocator >
- explicit operator std::basic_string<CharT, Traits, Allocator>() const
- {
- return to_string( Allocator() );
- }
-
-#endif // nssv_HAVE_EXPLICIT_CONVERSION
-
-#if nssv_CPP11_OR_GREATER
-
- template< class Allocator = std::allocator<CharT> >
- std::basic_string<CharT, Traits, Allocator>
- to_string( Allocator const & a = Allocator() ) const
- {
- return std::basic_string<CharT, Traits, Allocator>( begin(), end(), a );
- }
-
-#else
-
- std::basic_string<CharT, Traits>
- to_string() const
- {
- return std::basic_string<CharT, Traits>( begin(), end() );
- }
-
- template< class Allocator >
- std::basic_string<CharT, Traits, Allocator>
- to_string( Allocator const & a ) const
- {
- return std::basic_string<CharT, Traits, Allocator>( begin(), end(), a );
- }
-
-#endif // nssv_CPP11_OR_GREATER
-
-#endif // nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS
-};
-
-//
-// Non-member functions:
-//
-
-// 24.4.3 Non-member comparison functions:
-// lexicographically compare two string views (function template):
-
-template< class CharT, class Traits >
-nssv_constexpr bool operator== (
- basic_string_view <CharT, Traits> lhs,
- basic_string_view <CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; }
-
-template< class CharT, class Traits >
-nssv_constexpr bool operator!= (
- basic_string_view <CharT, Traits> lhs,
- basic_string_view <CharT, Traits> rhs ) nssv_noexcept
-{ return !( lhs == rhs ); }
-
-template< class CharT, class Traits >
-nssv_constexpr bool operator< (
- basic_string_view <CharT, Traits> lhs,
- basic_string_view <CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) < 0; }
-
-template< class CharT, class Traits >
-nssv_constexpr bool operator<= (
- basic_string_view <CharT, Traits> lhs,
- basic_string_view <CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) <= 0; }
-
-template< class CharT, class Traits >
-nssv_constexpr bool operator> (
- basic_string_view <CharT, Traits> lhs,
- basic_string_view <CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) > 0; }
-
-template< class CharT, class Traits >
-nssv_constexpr bool operator>= (
- basic_string_view <CharT, Traits> lhs,
- basic_string_view <CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) >= 0; }
-
-// Let S be basic_string_view<CharT, Traits>, and sv be an instance of S.
-// Implementations shall provide sufficient additional overloads marked
-// constexpr and noexcept so that an object t with an implicit conversion
-// to S can be compared according to Table 67.
-
-#if ! nssv_CPP11_OR_GREATER || nssv_BETWEEN( nssv_COMPILER_MSVC_VERSION, 100, 141 )
-
-// accomodate for older compilers:
-
-// ==
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator==(
- basic_string_view<CharT, Traits> lhs,
- CharT const * rhs ) nssv_noexcept
-{ return lhs.size() == detail::length( rhs ) && lhs.compare( rhs ) == 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator==(
- CharT const * lhs,
- basic_string_view<CharT, Traits> rhs ) nssv_noexcept
-{ return detail::length( lhs ) == rhs.size() && rhs.compare( lhs ) == 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator==(
- basic_string_view<CharT, Traits> lhs,
- std::basic_string<CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator==(
- std::basic_string<CharT, Traits> rhs,
- basic_string_view<CharT, Traits> lhs ) nssv_noexcept
-{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; }
-
-// !=
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator!=(
- basic_string_view<CharT, Traits> lhs,
- char const * rhs ) nssv_noexcept
-{ return !( lhs == rhs ); }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator!=(
- char const * lhs,
- basic_string_view<CharT, Traits> rhs ) nssv_noexcept
-{ return !( lhs == rhs ); }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator!=(
- basic_string_view<CharT, Traits> lhs,
- std::basic_string<CharT, Traits> rhs ) nssv_noexcept
-{ return !( lhs == rhs ); }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator!=(
- std::basic_string<CharT, Traits> rhs,
- basic_string_view<CharT, Traits> lhs ) nssv_noexcept
-{ return !( lhs == rhs ); }
-
-// <
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator<(
- basic_string_view<CharT, Traits> lhs,
- char const * rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) < 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator<(
- char const * lhs,
- basic_string_view<CharT, Traits> rhs ) nssv_noexcept
-{ return rhs.compare( lhs ) > 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator<(
- basic_string_view<CharT, Traits> lhs,
- std::basic_string<CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) < 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator<(
- std::basic_string<CharT, Traits> rhs,
- basic_string_view<CharT, Traits> lhs ) nssv_noexcept
-{ return rhs.compare( lhs ) > 0; }
-
-// <=
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator<=(
- basic_string_view<CharT, Traits> lhs,
- char const * rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) <= 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator<=(
- char const * lhs,
- basic_string_view<CharT, Traits> rhs ) nssv_noexcept
-{ return rhs.compare( lhs ) >= 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator<=(
- basic_string_view<CharT, Traits> lhs,
- std::basic_string<CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) <= 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator<=(
- std::basic_string<CharT, Traits> rhs,
- basic_string_view<CharT, Traits> lhs ) nssv_noexcept
-{ return rhs.compare( lhs ) >= 0; }
-
-// >
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator>(
- basic_string_view<CharT, Traits> lhs,
- char const * rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) > 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator>(
- char const * lhs,
- basic_string_view<CharT, Traits> rhs ) nssv_noexcept
-{ return rhs.compare( lhs ) < 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator>(
- basic_string_view<CharT, Traits> lhs,
- std::basic_string<CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) > 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator>(
- std::basic_string<CharT, Traits> rhs,
- basic_string_view<CharT, Traits> lhs ) nssv_noexcept
-{ return rhs.compare( lhs ) < 0; }
-
-// >=
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator>=(
- basic_string_view<CharT, Traits> lhs,
- char const * rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) >= 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator>=(
- char const * lhs,
- basic_string_view<CharT, Traits> rhs ) nssv_noexcept
-{ return rhs.compare( lhs ) <= 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator>=(
- basic_string_view<CharT, Traits> lhs,
- std::basic_string<CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) >= 0; }
-
-template< class CharT, class Traits>
-nssv_constexpr bool operator>=(
- std::basic_string<CharT, Traits> rhs,
- basic_string_view<CharT, Traits> lhs ) nssv_noexcept
-{ return rhs.compare( lhs ) <= 0; }
-
-#else // newer compilers:
-
-#define nssv_BASIC_STRING_VIEW_I(T,U) typename std::decay< basic_string_view<T,U> >::type
-
-#if nssv_BETWEEN( nssv_COMPILER_MSVC_VERSION, 140, 150 )
-# define nssv_MSVC_ORDER(x) , int=x
-#else
-# define nssv_MSVC_ORDER(x) /*, int=x*/
-#endif
-
-// ==
-
-template< class CharT, class Traits nssv_MSVC_ORDER(1) >
-nssv_constexpr bool operator==(
- basic_string_view <CharT, Traits> lhs,
- nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs ) nssv_noexcept
-{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; }
-
-template< class CharT, class Traits nssv_MSVC_ORDER(2) >
-nssv_constexpr bool operator==(
- nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs,
- basic_string_view <CharT, Traits> rhs ) nssv_noexcept
-{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; }
-
-// !=
-
-template< class CharT, class Traits nssv_MSVC_ORDER(1) >
-nssv_constexpr bool operator!= (
- basic_string_view < CharT, Traits > lhs,
- nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept
-{ return !( lhs == rhs ); }
-
-template< class CharT, class Traits nssv_MSVC_ORDER(2) >
-nssv_constexpr bool operator!= (
- nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs,
- basic_string_view < CharT, Traits > rhs ) nssv_noexcept
-{ return !( lhs == rhs ); }
-
-// <
-
-template< class CharT, class Traits nssv_MSVC_ORDER(1) >
-nssv_constexpr bool operator< (
- basic_string_view < CharT, Traits > lhs,
- nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) < 0; }
-
-template< class CharT, class Traits nssv_MSVC_ORDER(2) >
-nssv_constexpr bool operator< (
- nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs,
- basic_string_view < CharT, Traits > rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) < 0; }
-
-// <=
-
-template< class CharT, class Traits nssv_MSVC_ORDER(1) >
-nssv_constexpr bool operator<= (
- basic_string_view < CharT, Traits > lhs,
- nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) <= 0; }
-
-template< class CharT, class Traits nssv_MSVC_ORDER(2) >
-nssv_constexpr bool operator<= (
- nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs,
- basic_string_view < CharT, Traits > rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) <= 0; }
-
-// >
-
-template< class CharT, class Traits nssv_MSVC_ORDER(1) >
-nssv_constexpr bool operator> (
- basic_string_view < CharT, Traits > lhs,
- nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) > 0; }
-
-template< class CharT, class Traits nssv_MSVC_ORDER(2) >
-nssv_constexpr bool operator> (
- nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs,
- basic_string_view < CharT, Traits > rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) > 0; }
-
-// >=
-
-template< class CharT, class Traits nssv_MSVC_ORDER(1) >
-nssv_constexpr bool operator>= (
- basic_string_view < CharT, Traits > lhs,
- nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) >= 0; }
-
-template< class CharT, class Traits nssv_MSVC_ORDER(2) >
-nssv_constexpr bool operator>= (
- nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs,
- basic_string_view < CharT, Traits > rhs ) nssv_noexcept
-{ return lhs.compare( rhs ) >= 0; }
-
-#undef nssv_MSVC_ORDER
-#undef nssv_BASIC_STRING_VIEW_I
-
-#endif // compiler-dependent approach to comparisons
-
-// 24.4.4 Inserters and extractors:
-
-namespace detail {
-
-template< class Stream >
-void write_padding( Stream & os, std::streamsize n )
-{
- for ( std::streamsize i = 0; i < n; ++i )
- os.rdbuf()->sputc( os.fill() );
-}
-
-template< class Stream, class View >
-Stream & write_to_stream( Stream & os, View const & sv )
-{
- typename Stream::sentry sentry( os );
-
- if ( !os )
- return os;
-
- const std::streamsize length = static_cast<std::streamsize>( sv.length() );
-
- // Whether, and how, to pad:
- const bool pad = ( length < os.width() );
- const bool left_pad = pad && ( os.flags() & std::ios_base::adjustfield ) == std::ios_base::right;
-
- if ( left_pad )
- write_padding( os, os.width() - length );
-
- // Write span characters:
- os.rdbuf()->sputn( sv.begin(), length );
-
- if ( pad && !left_pad )
- write_padding( os, os.width() - length );
-
- // Reset output stream width:
- os.width( 0 );
-
- return os;
-}
-
-} // namespace detail
-
-template< class CharT, class Traits >
-std::basic_ostream<CharT, Traits> &
-operator<<(
- std::basic_ostream<CharT, Traits>& os,
- basic_string_view <CharT, Traits> sv )
-{
- return detail::write_to_stream( os, sv );
-}
-
-// Several typedefs for common character types are provided:
-
-typedef basic_string_view<char> string_view;
-typedef basic_string_view<wchar_t> wstring_view;
-#if nssv_HAVE_WCHAR16_T
-typedef basic_string_view<char16_t> u16string_view;
-typedef basic_string_view<char32_t> u32string_view;
-#endif
-
-}} // namespace nonstd::sv_lite
-
-//
-// 24.4.6 Suffix for basic_string_view literals:
-//
-
-#if nssv_HAVE_USER_DEFINED_LITERALS
-
-namespace nonstd {
-nssv_inline_ns namespace literals {
-nssv_inline_ns namespace string_view_literals {
-
-#if nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS
-
-nssv_constexpr nonstd::sv_lite::string_view operator "" sv( const char* str, size_t len ) nssv_noexcept // (1)
-{
- return nonstd::sv_lite::string_view{ str, len };
-}
-
-nssv_constexpr nonstd::sv_lite::u16string_view operator "" sv( const char16_t* str, size_t len ) nssv_noexcept // (2)
-{
- return nonstd::sv_lite::u16string_view{ str, len };
-}
-
-nssv_constexpr nonstd::sv_lite::u32string_view operator "" sv( const char32_t* str, size_t len ) nssv_noexcept // (3)
-{
- return nonstd::sv_lite::u32string_view{ str, len };
-}
-
-nssv_constexpr nonstd::sv_lite::wstring_view operator "" sv( const wchar_t* str, size_t len ) nssv_noexcept // (4)
-{
- return nonstd::sv_lite::wstring_view{ str, len };
-}
-
-#endif // nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS
-
-#if nssv_CONFIG_USR_SV_OPERATOR
-
-nssv_constexpr nonstd::sv_lite::string_view operator "" _sv( const char* str, size_t len ) nssv_noexcept // (1)
-{
- return nonstd::sv_lite::string_view{ str, len };
-}
-
-nssv_constexpr nonstd::sv_lite::u16string_view operator "" _sv( const char16_t* str, size_t len ) nssv_noexcept // (2)
-{
- return nonstd::sv_lite::u16string_view{ str, len };
-}
-
-nssv_constexpr nonstd::sv_lite::u32string_view operator "" _sv( const char32_t* str, size_t len ) nssv_noexcept // (3)
-{
- return nonstd::sv_lite::u32string_view{ str, len };
-}
-
-nssv_constexpr nonstd::sv_lite::wstring_view operator "" _sv( const wchar_t* str, size_t len ) nssv_noexcept // (4)
-{
- return nonstd::sv_lite::wstring_view{ str, len };
-}
-
-#endif // nssv_CONFIG_USR_SV_OPERATOR
-
-}}} // namespace nonstd::literals::string_view_literals
-
-#endif
-
-//
-// Extensions for std::string:
-//
-
-#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
-
-namespace nonstd {
-namespace sv_lite {
-
-// Exclude MSVC 14 (19.00): it yields ambiguous to_string():
-
-#if nssv_CPP11_OR_GREATER && nssv_COMPILER_MSVC_VERSION != 140
-
-template< class CharT, class Traits, class Allocator = std::allocator<CharT> >
-std::basic_string<CharT, Traits, Allocator>
-to_string( basic_string_view<CharT, Traits> v, Allocator const & a = Allocator() )
-{
- return std::basic_string<CharT,Traits, Allocator>( v.begin(), v.end(), a );
-}
-
-#else
-
-template< class CharT, class Traits >
-std::basic_string<CharT, Traits>
-to_string( basic_string_view<CharT, Traits> v )
-{
- return std::basic_string<CharT, Traits>( v.begin(), v.end() );
-}
-
-template< class CharT, class Traits, class Allocator >
-std::basic_string<CharT, Traits, Allocator>
-to_string( basic_string_view<CharT, Traits> v, Allocator const & a )
-{
- return std::basic_string<CharT, Traits, Allocator>( v.begin(), v.end(), a );
-}
-
-#endif // nssv_CPP11_OR_GREATER
-
-template< class CharT, class Traits, class Allocator >
-basic_string_view<CharT, Traits>
-to_string_view( std::basic_string<CharT, Traits, Allocator> const & s )
-{
- return basic_string_view<CharT, Traits>( s.data(), s.size() );
-}
-
-}} // namespace nonstd::sv_lite
-
-#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
-
-//
-// make types and algorithms available in namespace nonstd:
-//
-
-namespace nonstd {
-
-using sv_lite::basic_string_view;
-using sv_lite::string_view;
-using sv_lite::wstring_view;
-
-#if nssv_HAVE_WCHAR16_T
-using sv_lite::u16string_view;
-#endif
-#if nssv_HAVE_WCHAR32_T
-using sv_lite::u32string_view;
-#endif
-
-// literal "sv"
-
-using sv_lite::operator==;
-using sv_lite::operator!=;
-using sv_lite::operator<;
-using sv_lite::operator<=;
-using sv_lite::operator>;
-using sv_lite::operator>=;
-
-using sv_lite::operator<<;
-
-#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
-using sv_lite::to_string;
-using sv_lite::to_string_view;
-#endif
-
-} // namespace nonstd
-
-// 24.4.5 Hash support (C++11):
-
-// Note: The hash value of a string view object is equal to the hash value of
-// the corresponding string object.
-
-#if nssv_HAVE_STD_HASH
-
-#include <functional>
-
-namespace std {
-
-template<>
-struct hash< nonstd::string_view >
-{
-public:
- std::size_t operator()( nonstd::string_view v ) const nssv_noexcept
- {
- return std::hash<std::string>()( std::string( v.data(), v.size() ) );
- }
-};
-
-template<>
-struct hash< nonstd::wstring_view >
-{
-public:
- std::size_t operator()( nonstd::wstring_view v ) const nssv_noexcept
- {
- return std::hash<std::wstring>()( std::wstring( v.data(), v.size() ) );
- }
-};
-
-template<>
-struct hash< nonstd::u16string_view >
-{
-public:
- std::size_t operator()( nonstd::u16string_view v ) const nssv_noexcept
- {
- return std::hash<std::u16string>()( std::u16string( v.data(), v.size() ) );
- }
-};
-
-template<>
-struct hash< nonstd::u32string_view >
-{
-public:
- std::size_t operator()( nonstd::u32string_view v ) const nssv_noexcept
- {
- return std::hash<std::u32string>()( std::u32string( v.data(), v.size() ) );
- }
-};
-
-} // namespace std
-
-#endif // nssv_HAVE_STD_HASH
-
-nssv_RESTORE_WARNINGS()
-
-#endif // nssv_HAVE_STD_STRING_VIEW
-#endif // NONSTD_SV_LITE_H_INCLUDED
--- /dev/null
+#include "url.hpp"
+//#include <boost/regex.hpp>
+#include <algorithm>
+#include <cstdlib>
+#include <iterator>
+#include <sstream>
+#include <cstdint>
+#include <iostream>
+
+
+
+
+namespace {
+
+static const uint8_t tbl[256] = {
+ 0,0,0,0, 0,0,0,0, // NUL SOH STX ETX EOT ENQ ACK BEL
+ 0,0,0,0, 0,0,0,0, // BS HT LF VT FF CR SO SI
+ 0,0,0,0, 0,0,0,0, // DLE DC1 DC2 DC3 DC4 NAK SYN ETB
+ 0,0,0,0, 0,0,0,0, // CAN EM SUB ESC FS GS RS US
+ 0x00,0x01,0x00,0x00, 0x01,0x20,0x01,0x01, // SP ! " # $ % & '
+ 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x08, // ( ) * + , - . /
+ 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // 0 1 2 3 4 5 6 7
+ 0x01,0x01,0x04,0x01, 0x00,0x01,0x00,0x10, // 8 9 : ; < = > ?
+ 0x02,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // @ A B C D E F G
+ 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // H I J K L M N O
+ 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // P Q R S T U V W
+ 0x01,0x01,0x01,0x00, 0x00,0x00,0x00,0x01, // X Y Z [ \ ] ^ _
+ 0x00,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // ` a b c d e f g
+ 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // h i j k l m n o
+ 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // p q r s t u v w
+ 0x01,0x01,0x01,0x00, 0x00,0x00,0x01,0x00, // x y z { | } ~ DEL
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0
+};
+
+
+inline bool is_char(char c, std::uint8_t mask) {
+ return (tbl[static_cast<unsigned char>(c)]&mask) != 0;
+}
+
+
+inline bool is_chars(const char* s, const char* e, std::uint8_t mask) {
+ while(s!=e)
+ if (!is_char(*s++,mask))
+ return false;
+ return true;
+}
+
+
+inline bool is_alpha(char c) {
+ return (c>='A'&&c<='Z')||(c>='a'&&c<='z');
+}
+
+
+inline bool is_num(char c) {
+ return c>='0'&&c<='9';
+}
+
+
+inline bool is_alnum(char c) {
+ return is_alpha(c)||is_num(c);
+}
+
+
+inline bool is_hexdigit(char c) {
+ return is_num(c)||(c>='A'&&c<='F')||(c>='a'&&c<='f');
+}
+
+
+inline bool is_uint(const char *&s, const char *e, uint32_t max) {
+ if (s==e || !is_num(*s))
+ return false;
+ const char *t=s;
+ uint32_t val = *t++-'0';
+ if (val)
+ while(t!=e && is_num(*t))
+ val=val*10+(*t++-'0');
+ if (val>max)
+ return false;
+ s=t;
+ return true;
+}
+
+
+inline char get_hex_digit(char c) {
+ if (c>='0'&&c<='9')
+ return c-'0';
+ if (c>='A'&&c<='F')
+ return c-'A'+10;
+ if (c>='a'&&c<='f')
+ return c-'a'+10;
+ return -1;
+}
+
+
+inline void to_lower(std::string& s) {
+ for(auto& c : s)
+ if (c>='A' && c<='Z')
+ c |= 0x20;
+}
+
+
+inline const char* find_first_of(const char *s, const char *e, const char *q) {
+ for(; s!=e; ++s)
+ for(const char *t=q; *t; ++t)
+ if (*s==*t)
+ return s;
+ return e;
+}
+
+
+inline const char* find_char(const char *s, const char *e, const char c) {
+ while (s!=e && *s!=c)
+ ++s;
+ return s;
+}
+
+
+inline bool is_scheme(const char *s, const char *e)
+{
+ if (!s||!e||s==e||!is_alpha(*s))
+ return false;
+ char c;
+ while(++s!=e)
+ if (!is_alnum(c=*s)&&c!='+'&&c!='-'&&c!='.')
+ return false;
+ return true;
+}
+
+
+inline bool is_scheme(const std::string &s) {
+ return is_scheme(s.data(),s.data()+s.length());
+}
+
+
+std::string normalize_scheme(const char *b, const char *e) {
+ std::string o(b,e-b);
+ to_lower(o);
+ return o;
+}
+
+
+inline bool is_ipv4(const char *s, const char *e) {
+ size_t l=e-s;
+ if (l<7 || l>254)
+ return false;
+ for (const char *p=s; p!=e; ++p)
+ if (*p!='.'&&!is_num(*p))
+ return false;
+ return true;
+}
+
+
+inline bool is_ipv4(const std::string &s) {
+ return is_ipv4(s.data(),s.data()+s.length());
+}
+
+
+inline bool is_valid_ipv4(const char *s, const char *e) {
+ return is_uint(s,e,255) && s!=e && *s++=='.' &&
+ is_uint(s,e,255) && s!=e && *s++=='.' &&
+ is_uint(s,e,255) && s!=e && *s++=='.' &&
+ is_uint(s,e,255) && s==e;
+}
+
+
+inline bool is_valid_ipv4(const std::string &s) {
+ return is_valid_ipv4(s.data(),s.data()+s.length());
+}
+
+
+inline bool is_reg_name(const char *s, const char *e) {
+ return is_chars(s, e, 0x01);
+}
+
+
+inline bool is_reg_name(const std::string &s) {
+ return is_reg_name(s.data(),s.data()+s.length());
+}
+
+
+std::string normalize_reg_name(const std::string& s) {
+ std::string o(s);
+ to_lower(o); // see rfc 4343
+ return o;
+}
+
+
+bool is_ipv6(const char *s, const char *e) {
+ size_t l=e-s;
+ if (l<2 || l>254)
+ return false;
+ for (const char *p=s; p!=e; ++p)
+ if (*p!=':'&&*p!='.'&&!is_hexdigit(*p))
+ return false;
+ return true;
+}
+
+
+inline bool is_ipv6(const std::string &s) {
+ return is_ipv6(s.data(),s.data()+s.length());
+}
+
+
+bool is_valid_ipv6(const char *s, const char *e) {
+ if ((e-s)>39||(e-s)<2)
+ return false;
+ bool null_field=false;
+ const char *b=s, *p=s;
+ int nfields=0, ndigits=0;
+ if (p[0]==':') {
+ if (p[1]!=':')
+ return false;
+ null_field=true;
+ b=(p+=2);
+ if (p==e)
+ return true;
+ }
+ while(p!=e) {
+ if (*p=='.') {
+ return ((!null_field&&nfields==6)||(null_field&&nfields<7))&&is_valid_ipv4(b, e);
+ } else if (*p==':') {
+ if (ndigits==0) {
+ if (null_field)
+ return false;
+ null_field=true;
+ } else {
+ ++nfields;
+ ndigits=0;
+ }
+ b=++p;
+ } else {
+ if ((++ndigits>4) || !is_hexdigit(*p++))
+ return false;
+ }
+ }
+ if (ndigits>0)
+ ++nfields;
+ else {
+ if (e[-1]==':') {
+ if (e[-2]==':' && nfields<8)
+ return true;
+ return false;
+ }
+ }
+ return (!null_field&&nfields==8)||(null_field&&nfields<8);
+}
+
+
+inline bool is_valid_ipv6(const std::string &s) {
+ return is_valid_ipv6(s.data(),s.data()+s.length());
+}
+
+
+std::string normalize_IPv6(const char *s, const char *e) {
+ if (!is_ipv6(s, e))
+ throw Url::parse_error("IPv6 ["+std::string(s,e-s)+"] is invalid");
+ if ((e-s)==2 && s[0]==':' && s[1]==':')
+ return std::string(s,e-s);
+
+ // Split IPv6 at colons
+ const size_t token_size = 10;
+ const char *p=s, *tokens[token_size];
+ if (*p==':')
+ ++p;
+ if (e[-1]==':')
+ --e;
+ const char *b=p;
+ size_t i=0;
+ while (p!=e) {
+ if (*p++==':') {
+ if (i+1 >= token_size) {
+ throw Url::parse_error("IPv6 ["+std::string(s,e-s)+"] is invalid");
+ }
+ tokens[i++]=b;
+ b=p;
+ }
+ }
+ if (i<8)
+ tokens[i++]=b;
+ tokens[i]=p;
+ size_t ntokens=i;
+
+ // Get IPv4 address which is normalized by default
+ const char *ipv4_b=nullptr, *ipv4_e=nullptr;
+ if ((tokens[ntokens]-tokens[ntokens-1])>5) {
+ ipv4_b=tokens[ntokens-1];
+ ipv4_e=tokens[ntokens];
+ --ntokens;
+ }
+
+ // Decode the fields
+ const size_t fields_size = 8;
+ std::uint16_t fields[fields_size];
+ size_t null_pos=8, null_len=0, nfields=0;
+ for(size_t i=0; i<ntokens; ++i) {
+ const char *p=tokens[i];
+ if (p==tokens[i+1] || *p==':')
+ null_pos=i;
+ else {
+ if (nfields >= fields_size) {
+ throw Url::parse_error("IPv6 ["+std::string(s,e-s)+"] is invalid");
+ }
+ std::uint16_t field=get_hex_digit(*p++);
+ while (p!=tokens[i+1] && *p!=':')
+ field=(field<<4)|get_hex_digit(*p++);
+ fields[nfields++]=field;
+ }
+ }
+ i = nfields;
+ nfields=(ipv4_b)?6:8;
+ if (i<nfields) {
+ if (i<null_pos) {
+ throw Url::parse_error("IPv6 ["+std::string(s,e-s)+"] is invalid");
+ }
+ size_t last=nfields;
+ if (i!=null_pos)
+ do fields[--last]=fields[--i]; while (i!=null_pos);
+ do fields[--last]=0; while (last!=null_pos);
+ }
+
+ // locate first longer sequence of zero
+ i=null_len=0;
+ null_pos=nfields;
+ size_t first=0;
+ for(;;) {
+ while (i<nfields && fields[i]!=0)
+ ++i;
+ if (i==nfields)
+ break;
+ first=i;
+ while (i<nfields && fields[i]==0)
+ ++i;
+ if ((i-first)>null_len) {
+ null_pos=first;
+ null_len=i-first;
+ }
+ if (i==nfields)
+ break;
+ }
+ if (null_len==1) {
+ null_pos=nfields;
+ null_len=1;
+ }
+
+ // Encode normalized IPv6
+ std::stringstream str;
+ if (null_pos==0) {
+ str << std::hex << ':';
+ i=null_len;
+ } else {
+ str << std::hex << fields[0];
+ for (i=1; i<null_pos; ++i)
+ str << ':' << fields[i];
+ if (i<nfields)
+ str << ':';
+ i+=null_len;
+ if (i==8 && null_len!=0)
+ str << ':';
+ }
+ for (; i<nfields; ++i)
+ str << ':' << fields[i];
+ if (ipv4_b)
+ str << ':' << std::string(ipv4_b, ipv4_e-ipv4_b);
+
+ return str.str();
+}
+
+
+inline std::string normalize_IPv6(const std::string &s) {
+ return normalize_IPv6(s.data(),s.data()+s.length());
+}
+
+
+inline bool is_port(const char *s, const char *e) {
+ return is_uint(s,e,65535) && s==e;
+}
+
+
+inline bool is_port(const std::string &s) {
+ return is_port(s.data(),s.data()+s.length());
+}
+
+
+std::string normalize_path(const std::string& s) {
+ if (s.empty())
+ return s;
+ std::string elem;
+ std::vector<std::string> elems;
+ std::stringstream si(s);
+
+ while(!std::getline(si, elem, '/').eof()){
+ if (elem=="" || elem==".")
+ continue;
+ if (elem=="..") {
+ if (!elems.empty())
+ elems.pop_back();
+ continue;
+ }
+ elems.push_back(elem);
+ }
+ if (elem==".")
+ elems.push_back("");
+ else if (elem=="..") {
+ if (!elems.empty())
+ elems.pop_back();
+ }
+ else
+ elems.push_back(elem);
+
+ std::stringstream so;
+ if (s[0]=='/')
+ so << '/';
+ if (!elems.empty()) {
+ auto it=elems.begin(), end=elems.end();
+ so << *it;
+ while(++it!=end)
+ so << '/' << *it;
+ }
+ return so.str();
+}
+
+
+std::string decode(const char *s, const char *e) {
+ std::string o;
+ o.reserve(e-s);
+ while(s!=e) {
+ char c=*s++, a, b;
+ if (c=='%') {
+ if (s==e || (a=get_hex_digit(*s++))<0 || s==e || (b=get_hex_digit(*s++))<0)
+ throw Url::parse_error("Invalid percent encoding");
+ c=(a<<4)|b;
+ }
+ o.push_back(c);
+ }
+ return o;
+}
+
+
+std::string decode_plus(const char *s, const char *e) {
+ std::string o;
+ o.reserve(e-s);
+ while(s!=e) {
+ char c=*s++, a, b;
+ if (c=='+')
+ c=' ';
+ else if (c=='%') {
+ if (s==e || (a=get_hex_digit(*s++))<0 || s==e || (b=get_hex_digit(*s++))<0)
+ throw Url::parse_error("Invalid percent encoding");
+ c=(a<<4)|b;
+ }
+ o.push_back(c);
+ }
+ return o;
+}
+
+
+class encode {
+ public:
+ encode(const std::string& s, std::uint8_t mask) : m_s(s), m_mask(mask) {}
+ private:
+ const std::string& m_s;
+ std::uint8_t m_mask;
+ friend std::ostream& operator<< (std::ostream& o, const encode& e) {
+ for (const char c:e.m_s)
+ if (is_char(c,e.m_mask))
+ o<<c;
+ else
+ o<<'%'<<"0123456789ABCDEF"[((uint8_t)c)>>4]<<"0123456789ABCDEF"[((uint8_t)c)&0xF];
+ return o;
+ }
+};
+
+
+class encode_query_key {
+ public:
+ encode_query_key(const std::string& s, std::uint8_t mask) : m_s(s), m_mask(mask) {}
+ private:
+ const std::string& m_s;
+ std::uint8_t m_mask;
+ friend std::ostream& operator<< (std::ostream& o, const encode_query_key& e) {
+ for (const char c:e.m_s)
+ if (c==' ')
+ o<<'+';
+ else if (c=='+')
+ o<<"%2B";
+ else if (c=='=')
+ o<<"%3D";
+ else if (c=='&')
+ o<<"%26";
+ else if (c==';')
+ o<<"%3B";
+ else if (is_char(c,e.m_mask))
+ o<<c;
+ else
+ o<<'%'<<"0123456789ABCDEF"[((uint8_t)c)>>4]<<"0123456789ABCDEF"[((uint8_t)c)&0xF];
+ return o;
+ }
+};
+
+
+class encode_query_val {
+ public:
+ encode_query_val(const std::string& s, std::uint8_t mask) : m_s(s), m_mask(mask) {}
+ private:
+ const std::string& m_s;
+ std::uint8_t m_mask;
+ friend std::ostream& operator<< (std::ostream& o, const encode_query_val& e) {
+ for (const char c:e.m_s)
+ if (c==' ')
+ o<<'+';
+ else if (c=='+')
+ o<<"%2B";
+ else if (c=='&')
+ o<<"%26";
+ else if (c==';')
+ o<<"%3B";
+ else if (is_char(c,e.m_mask))
+ o<<c;
+ else
+ o<<'%'<<"0123456789ABCDEF"[((uint8_t)c)>>4]<<"0123456789ABCDEF"[((uint8_t)c)&0xF];
+ return o;
+ }
+};
+
+
+
+} // end of anonymous namnespace
+// ---------------------------------------------------------------------
+
+
+// Copy assignment
+void Url::assign(const Url &url) {
+ m_parse=url.m_parse;
+ m_built=url.m_built;
+ if (m_parse) {
+ m_scheme=url.m_scheme;
+ m_user=url.m_user;
+ m_host=url.m_host;
+ m_ip_v=url.m_ip_v;
+ m_port=url.m_port;
+ m_path=url.m_path;
+ m_query=url.m_query;
+ m_fragment=url.m_fragment;
+ }
+ if (!m_parse || m_built)
+ m_url=url.m_url;
+}
+
+
+// Move assignment
+void Url::assign(Url&& url) {
+ m_parse=url.m_parse;
+ m_built=url.m_built;
+ if (m_parse) {
+ m_scheme=std::move(url.m_scheme);
+ m_user=std::move(url.m_user);
+ m_host=std::move(url.m_host);
+ m_ip_v=std::move(url.m_ip_v);
+ m_port=std::move(url.m_port);
+ m_path=std::move(url.m_path);
+ m_query=std::move(url.m_query);
+ m_fragment=std::move(url.m_fragment);
+ }
+ if (!m_parse || m_built)
+ m_url=std::move(url.m_url);
+}
+
+
+Url &Url::scheme(const std::string& s) {
+ if (!is_scheme(s))
+ throw Url::parse_error("Invalid scheme '"+s+"'");
+ lazy_parse();
+ std::string o(s);
+ to_lower(o);
+ if (o!=m_scheme) {
+ m_scheme=o;
+ m_built=false;
+ if ((m_scheme=="http" && m_port=="80") || (m_scheme=="https" && m_port=="443"))
+ m_port="";
+ }
+ return *this;
+}
+
+
+Url &Url::user_info(const std::string& s) {
+ if (s.length()>256)
+ throw Url::parse_error("User info is longer than 256 characters '"+s+"'");
+ lazy_parse();
+ if (m_user!=s) {
+ m_user=s;
+ m_built=false;
+ }
+ return *this;
+}
+
+
+Url &Url::host(const std::string& h, std::uint8_t ip_v) {
+ if (h.length()>253)
+ throw Url::parse_error("Host is longer than 253 characters '"+h+"'");
+ lazy_parse();
+ std::string o;
+ if (h.empty())
+ ip_v=-1;
+ else if (is_ipv4(h)) {
+ if (!is_valid_ipv4(h))
+ throw Url::parse_error("Invalid IPv4 address '"+h+"'");
+ ip_v=4;
+ o=h;
+ } else if(ip_v!=0&&ip_v!=4&&ip_v!=6) {
+ if (!is_ipv6(h)) {
+ throw Url::parse_error("Invalid IPvFuture address '"+h+"'");
+ }
+ o=h;
+ } else if (is_ipv6(h)) {
+ if (!is_valid_ipv6(h))
+ throw Url::parse_error("Invalid IPv6 address '"+h+"'");
+ ip_v=6;
+ o=normalize_IPv6(h);
+ } else if (is_reg_name(h)) {
+ ip_v=0;
+ o=normalize_reg_name(h);
+ } else
+ throw Url::parse_error("Invalid host '"+h+"'");
+ if (m_host!=o||m_ip_v!=ip_v) {
+ m_host=o;
+ m_ip_v=ip_v;
+ m_built=false;
+ }
+ return *this;
+}
+
+
+Url &Url::port(const std::string& p) {
+ if (!is_port(p))
+ throw Url::parse_error("Invalid port '"+p+"'");
+ lazy_parse();
+ std::string o(p);
+ if ((m_scheme=="http" && o=="80") || (m_scheme=="https" && o=="443"))
+ o="";
+ if (m_port!=o) {
+ m_port=o;
+ m_built=false;
+ }
+ return *this;
+}
+
+
+Url &Url::path(const std::string& p) {
+ if (p.length()>8000)
+ throw Url::parse_error("Path is longer than 8000 characters '"+p+"'");
+ lazy_parse();
+ std::string o(normalize_path(p));
+ if (m_path!=o) {
+ m_path=o;
+ m_built=false;
+ }
+ return *this;
+}
+
+
+Url &Url::fragment(const std::string& f) {
+ if (f.length()>256)
+ throw Url::parse_error("Fragment is longer than 256 characters '"+f+"'");
+ lazy_parse();
+ if (m_fragment!=f) {
+ m_fragment=f;
+ m_built=false;
+ }
+ return *this;
+}
+
+
+Url &Url::clear() {
+ m_url.clear();
+ m_scheme.clear();
+ m_user.clear();
+ m_host.clear();
+ m_port.clear();
+ m_path.clear();
+ m_query.clear();
+ m_fragment.clear();
+ m_ip_v=-1;
+ m_built=true;
+ m_parse=true;
+ return *this;
+}
+
+
+void Url::parse_url() const {
+ if (m_url.empty()) {
+ const_cast<Url*>(this)->clear();
+ m_parse=m_built=true;
+ return;
+ }
+ if (m_url.length()>8000)
+ throw Url::parse_error("URI is longer than 8000 characters");
+
+ const char *s=m_url.data(), *e=s+m_url.length();
+ std::int8_t ip_v=-1;
+ const char *scheme_b, *scheme_e, *user_b, *user_e, *host_b, *host_e,
+ *port_b, *port_e, *path_b, *path_e, *query_b, *query_e,
+ *fragment_b, *fragment_e;
+ scheme_b=scheme_e=user_b=user_e=host_b=host_e=port_b=port_e=path_b=
+ path_e=query_b=query_e=fragment_b=fragment_e=nullptr;
+
+ const char *b=s, *p=find_first_of(b, e, ":/?#");
+ if (p==e) {
+ if (!is_chars(b, p, 0x2F))
+ throw Url::parse_error("Path '"+std::string(b,p)+"' in '"+std::string(s,e-s)+"' is invalid");
+ path_b=b;
+ path_e=e;
+ } else {
+ // get schema if any
+ if (*p==':') {
+ if (!is_scheme(b, p))
+ throw Url::parse_error("Scheme in '"+std::string(s,e-s)+"' is invalid");
+ scheme_b=b;
+ scheme_e=p;
+ p=find_first_of(b=p+1, e, "/?#");
+ }
+ // get authority if any
+ if (p!=e && *p=='/' && (e-b)>1 && b[0]=='/' && b[1]=='/') {
+ const char *ea=find_first_of(b+=2, e, "/?#"); // locate end of authority
+ p=find_char(b, ea, '@');
+ // get user info if any
+ if (p!=ea) {
+ if (!is_chars(b, p, 0x25))
+ throw Url::parse_error("User info in '"+std::string(s,e-s)+"' is invalid");
+ user_b=b;
+ user_e=p;
+ b=p+1;
+ }
+ // Get IP literal if any
+ if (*b=='[') {
+ // locate end of IP literal
+ p=find_char(++b, ea, ']');
+ if (*p!=']')
+ throw Url::parse_error("Missing ] in '"+std::string(s,e-s)+"'");
+ // decode IPvFuture protocol version
+ if (*b=='v') {
+ if (is_hexdigit(*++b)) {
+ ip_v=get_hex_digit(*b);
+ if (is_hexdigit(*++b)) {
+ ip_v=(ip_v<<8)|get_hex_digit(*b);
+ }
+ }
+ if (ip_v==-1||*b++!='.'||!is_chars(b,p,0x05))
+ throw Url::parse_error("Host address in '"+std::string(s,e-s)+"' is invalid");
+ } else if (is_ipv6(b,p)) {
+ ip_v=6;
+ } else
+ throw Url::parse_error("Host address in '"+std::string(s,e-s)+"' is invalid");
+ host_b=b;
+ host_e=p;
+ b=p+1;
+ } else {
+ p=find_char(b, ea, ':');
+ if (is_ipv4(b, p))
+ ip_v=4;
+ else if (is_reg_name(b, p))
+ ip_v=0;
+ else
+ throw Url::parse_error("Host address in '"+std::string(s,e-s)+"' is invalid");
+ host_b=b;
+ host_e=p;
+ b=p;
+ }
+ //get port if any
+ if (b!=ea&&*b==':') {
+ if (!is_port(++b, ea))
+ throw Url::parse_error("Port '"+std::string(b,ea-b)+"' in '"+std::string(s,e-s)+"' is invalid");
+ port_b=b;
+ port_e=ea;
+ }
+ b=ea;
+ }
+ p=find_first_of(b,e,"?#");
+ if (!is_chars(b, p, 0x2F))
+ throw Url::parse_error("Path '"+std::string(b,p)+"' in '"+std::string(s,e-s)+"' is invalid");
+ path_b=b;
+ path_e=p;
+ if (p!=e && *p=='?') {
+ p=find_char(b=p+1,e,'#');
+ query_b=b;
+ query_e=p;
+ }
+ if (p!=e && *p=='#') {
+ if (!is_chars(p+1, e, 0x3F))
+ throw Url::parse_error("Fragment '"+std::string(p+1,e)+"' in '"+std::string(s,e-s)+"' is invalid");
+ fragment_b=p+1;
+ fragment_e=e;
+ }
+ }
+ std::string _scheme, _user, _host, _port, _path, _query, _fragment;
+ Query query_v;
+
+ if (scheme_b)
+ _scheme=normalize_scheme(scheme_b, scheme_e);
+ if (user_b)
+ _user=decode(user_b, user_e);
+ if (host_b) {
+ _host=decode(host_b, host_e);
+ if (ip_v==0)
+ _host=normalize_reg_name(_host);
+ else if (ip_v==6)
+ _host=normalize_IPv6(_host);
+ }
+ if (port_b)
+ _port=std::string(port_b,port_e-port_b);
+ if (path_b)
+ _path=normalize_path(decode(path_b, path_e));
+ if (query_b) {
+ _query=std::string(query_b, query_e);
+ p=b=query_b;
+ while (p!=query_e) {
+ p=find_first_of(b, query_e, "=;&");
+ if (!is_chars(b, p, 0x3F))
+ throw Url::parse_error("Query key '"+std::string(b,p)+"' in '"+std::string(s,e-s)+"' is invalid");
+ std::string key(decode_plus(b,p)), val;
+ if (p!=query_e) {
+ if (*p=='=') {
+ p=find_first_of(b=p+1, query_e, ";&");
+ if (!is_chars(b, p, 0x3F))
+ throw Url::parse_error("Query value '"+std::string(b,p)+"' in '"+std::string(s,e-s)+"' is invalid");
+ val=decode_plus(b,p);
+ }
+ b=p+1;
+ }
+ query_v.emplace_back(key, val);
+ }
+ }
+ if (fragment_b)
+ _fragment=decode(fragment_b, fragment_e);
+
+ m_scheme=_scheme;
+ m_user=_user;
+ m_host=_host;
+ m_ip_v=ip_v;
+ m_port=_port;
+ m_path=_path;
+ m_query=query_v;
+ m_fragment=_fragment;
+ m_parse=true;
+ m_built=false;
+}
+
+
+void Url::build_url() const {
+ lazy_parse();
+ std::stringstream url;
+ if (!m_scheme.empty())
+ url<<m_scheme<<":";
+ if (!m_host.empty()) {
+ url<<"//";
+ if (!m_user.empty())
+ url<<encode(m_user, 0x05)<<'@';
+ if (m_ip_v==0||m_ip_v==4)
+ url<<m_host;
+ else if (m_ip_v==6)
+ url<<"["<<m_host<<"]";
+ else
+ url<<"[v"<<std::hex<<(int)m_ip_v<<std::dec<<'.'<<m_host<<"]";
+ if (!m_port.empty())
+ if (!((m_scheme=="http"&&m_port=="80")||(m_scheme=="https"&&m_port=="443")))
+ url<<":"<<m_port;
+ } else {
+ if (!m_user.empty())
+ throw Url::build_error("User info defined, but host is empty");
+ if (!m_port.empty())
+ throw Url::build_error("Port defined, but host is empty");
+ if (!m_path.empty()) {
+ const char *b=m_path.data(), *e=b+m_path.length(), *p=find_first_of(b,e,":/");
+ if (p!=e && *p==':')
+ throw Url::build_error("The first segment of the relative path can't contain ':'");
+ }
+ }
+ if (!m_path.empty()) {
+ if (m_path[0]!='/'&&!m_host.empty())
+ throw Url::build_error("Path must start with '/' when host is not empty");
+ url<<encode(m_path, 0x0F);
+ }
+ if (!m_query.empty()) {
+ url<<"?";
+ auto it = m_query.begin(), end = m_query.end();
+ if (it->key().empty())
+ throw Url::build_error("First query entry has no key");
+ url<<encode_query_key(it->key(), 0x1F);
+ if (!it->val().empty())
+ url<<"="<<encode_query_val(it->val(), 0x1F);
+ while(++it!=end) {
+ if (it->key().empty())
+ throw Url::build_error("A query entry has no key");
+ url<<"&"<<encode_query_key(it->key(), 0x1F);
+ if (!it->val().empty())
+ url<<"="<<encode_query_val(it->val(), 0x1F);
+ }
+ }
+ if (!m_fragment.empty())
+ url<<"#"<<encode(m_fragment, 0x1F);
+ m_built=false;
+ m_url=url.str();
+}
+
+
+// Output
+std::ostream& Url::output(std::ostream &o) const {
+ lazy_parse();
+ if(!m_built) build_url();
+ o<<"Url:{url("<<m_url<<")";
+ if (!m_scheme.empty()) o << " scheme("<<m_scheme<<")";
+ if (!m_user.empty()) o << " user_info("<<m_user<<")";
+ if (m_ip_v!=-1) o << " host("<<m_host<<") IPv("<<(int)m_ip_v<<")";
+ if (!m_port.empty()) o << " port("<<m_port<<")";
+ if (!m_path.empty()) o << " path("<<m_path<<")";
+ if (!m_query.empty()) {
+ std::stringstream str;
+ str<<" query(";
+ for (const auto& q:m_query)
+ str<<q;
+ std::string s(str.str());
+ o<<s.substr(0,s.length()-1)<<")";
+ }
+ if (!m_fragment.empty()) o << "fragment("<<m_fragment<<") ";
+ o<<"}";
+ return o;
+}
+
--- /dev/null
+#ifndef URL_HPP
+#define URL_HPP
+
+#include <vector>
+#include <utility>
+#include <stdexcept>
+#include <cstdint>
+#include <ostream>
+#include <utility>
+#include <string>
+
+class Url {
+public:
+ // Exception thut may be thrown when decoding an URL or an assigning value
+ class parse_error: public std::invalid_argument {
+ public:
+ parse_error(const std::string &reason) : std::invalid_argument(reason) {}
+ };
+
+ // Exception that may be thrown when building an URL
+ class build_error: public std::runtime_error {
+ public:
+ build_error(const std::string &reason) : std::runtime_error(reason) {}
+ };
+
+ // Default constructor
+ Url() : m_parse(true),m_built(true),m_ip_v(-1) {}
+
+ // Copy initializer constructor
+ Url(const Url &url) : m_ip_v(-1) {assign(url);}
+
+ // Move constructor
+ Url(Url&& url) : m_ip_v(-1) {assign(std::move(url));}
+
+ // Construct Url with the given string
+ Url(const std::string &url_str) : m_url(url_str),m_parse(false),m_built(false),m_ip_v(-1) {}
+
+ // Assign the given URL string
+ Url &operator=(const std::string &url_str) {return str(url_str);}
+
+ // Assign the given Url object
+ Url &operator=(const Url &url) {assign(url); return *this;}
+
+ // Move the given Url object
+ Url &operator=(Url&& url) {assign(std::move(url)); return *this;}
+
+ // Clear the Url object
+ Url &clear();
+
+ // Build Url if needed and return it as string
+ std::string str() const {if(!m_built) build_url(); return m_url;}
+
+ // Set the Url to the given string. All fields are overwritten
+ Url& str(const std::string &url_str) {m_url=url_str; m_built=m_parse=false; return *this;}
+
+ // Get scheme
+ const std::string& scheme() const {lazy_parse(); return m_scheme;}
+
+ // Set scheme
+ Url &scheme(const std::string& s);
+
+ // Get user info
+ const std::string& user_info() const {lazy_parse(); return m_user;}
+
+ // Set user info
+ Url &user_info(const std::string& s);
+
+ // Get host
+ const std::string& host() const {lazy_parse(); return m_host;}
+
+ // Set host
+ Url &host(const std::string& h, uint8_t ip_v=0);
+
+ // Get host IP version: 0=name, 4=IPv4, 6=IPv6, -1=undefined
+ std::int8_t ip_version() const {lazy_parse(); return m_ip_v;}
+
+ // Get port
+ const std::string& port() const {lazy_parse(); return m_port;}
+
+ // Set Port given as string
+ Url &port(const std::string& str);
+
+ // Set port given as a 16bit unsigned integer
+ Url &port(std::uint16_t num) {return port(std::to_string(num));}
+
+ // Get path
+ const std::string& path() const {lazy_parse(); return m_path;}
+
+ // Set path
+ Url &path(const std::string& str);
+
+ class KeyVal {
+ public:
+ // Default constructor
+ KeyVal() {}
+
+ // Construct with provided Key and Value strings
+ KeyVal(const std::string &key, const std::string &val) : m_key(key),m_val(val) {}
+
+ // Construct with provided Key string, val will be empty
+ KeyVal(const std::string &key) : m_key(key) {}
+
+ // Equality test operator
+ bool operator==(const KeyVal &q) const {return m_key==q.m_key&&m_val==q.m_val;}
+
+ // Swap this with q
+ void swap(KeyVal& q) {std::swap(m_key,q.m_key); std::swap(m_val,q.m_val);}
+
+ // Get key
+ const std::string& key() const {return m_key;}
+
+ // Set key
+ void key(const std::string &k) {m_key=k;}
+
+ // Get value
+ const std::string& val() const {return m_val;}
+
+ // Set value
+ void val(const std::string &v) {m_val=v;}
+
+ // Output key value pair
+ friend std::ostream& operator<<(std::ostream &o, const KeyVal &kv)
+ {o<<"<key("<<kv.m_key<<") val("<<kv.m_val<<")> "; return o;}
+
+ private:
+ std::string m_key;
+ std::string m_val;
+ };
+
+ // Define Query as vector of Key Value pairs
+ typedef std::vector<KeyVal> Query;
+
+ // Get a reference to the query vector for read only access
+ const Query& query() const {lazy_parse(); return m_query;}
+
+ // Get a reference to a specific Key Value pair in the query vector for read only access
+ const KeyVal& query(size_t i) const {
+ lazy_parse();
+ if (i>=m_query.size())
+ throw std::out_of_range("Invalid Url query index ("+std::to_string(i)+")");
+ return m_query[i];
+ }
+
+ // Get a reference to the query vector for a writable access
+ Query& set_query() {lazy_parse(); m_built=false; return m_query;}
+
+ // Get a reference to specific Key Value pair in the query vector for a writable access
+ KeyVal& set_query(size_t i) {
+ lazy_parse();
+ if (i>=m_query.size())
+ throw std::out_of_range("Invalid Url query index ("+std::to_string(i)+")");
+ m_built=false;
+ return m_query[i];
+ }
+
+ // Set the query vector to the Query vector q
+ Url &set_query(const Query &q)
+ {lazy_parse(); if (q != m_query) {m_query=q; m_built=false;} return *this;}
+
+ // Append KeyVal kv to the query
+ Url &add_query(const KeyVal &kv)
+ {lazy_parse(); m_built=false; m_query.push_back(kv); return *this;}
+
+ // Append key val pair to the query
+ Url &add_query(const std::string &key, const std::string &val)
+ {lazy_parse(); m_built=false; m_query.emplace_back(key,val); return *this;}
+
+ // Append key with empty val to the query
+ Url &add_query(const std::string &key)
+ {lazy_parse(); m_built=false; m_query.emplace_back(key); return *this;}
+
+ // Get the fragment
+ const std::string& fragment() const {lazy_parse(); return m_fragment;}
+
+ // Set the fragment
+ Url &fragment(const std::string& f);
+
+ // Output
+ std::ostream& output(std::ostream &o) const;
+
+ // Output strean operator
+ friend std::ostream& operator<<(std::ostream &o, const Url &u) {return u.output(o);}
+
+private:
+ void assign(const Url &url);
+ void assign(Url&& url);
+ void build_url() const;
+ void lazy_parse() const {if (!m_parse) parse_url();}
+ void parse_url() const;
+
+ mutable std::string m_scheme;
+ mutable std::string m_user;
+ mutable std::string m_host;
+ mutable std::string m_port;
+ mutable std::string m_path;
+ mutable Query m_query;
+ mutable std::string m_fragment;
+ mutable std::string m_url;
+ mutable bool m_parse;
+ mutable bool m_built;
+ mutable std::int8_t m_ip_v;
+};
+
+
+
+
+#endif // URL_HPP
+
#include <direct.h>
#include <io.h>
-#define WIN32_LEAN_AND_MEAN
#define NOMINMAX 1
#define WIN32_NO_STATUS
#include <windows.h>
-#ifndef CCACHE_THIRD_PARTY_WIN32_MKTEMP_H_
-#define CCACHE_THIRD_PARTY_WIN32_MKTEMP_H_
+#pragma once
#include <stddef.h>
extern "C" {
#endif
-int bsd_mkstemp(char *);
+int bsd_mkstemps(char* path, int slen);
// Exposed for testing.
-void bsd_mkstemp_set_random_source(void (*)(void *buf, size_t n));
+void bsd_mkstemp_set_random_source(void (*)(void* buf, size_t n));
#ifdef __cplusplus
}
#endif
-#endif
*/
+/*!
+ * @file xxh_x86dispatch.c
+ *
+ * Automatic dispatcher code for the @ref xxh3_family on x86-based targets.
+ *
+ * Optional add-on.
+ *
+ * **Compile this file with the default flags for your target.** Do not compile
+ * with flags like `-mavx*`, `-march=native`, or `/arch:AVX*`, there will be
+ * an error. See @ref XXH_X86DISPATCH_ALLOW_AVX for details.
+ *
+ * @defgroup dispatch x86 Dispatcher
+ * @{
+ */
+
#if defined (__cplusplus)
extern "C" {
#endif
-/*
- * Dispatcher code for XXH3 on x86-based targets.
- */
#if !(defined(__x86_64__) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64))
# error "Dispatching is currently only supported on x86 and x86_64."
#endif
-#ifndef __GNUC__
-# error "Dispatching requires __attribute__((__target__)) capability"
+/*!
+ * @def XXH_X86DISPATCH_ALLOW_AVX
+ * @brief Disables the AVX sanity check.
+ *
+ * Don't compile xxh_x86dispatch.c with options like `-mavx*`, `-march=native`,
+ * or `/arch:AVX*`. It is intended to be compiled for the minimum target, and
+ * it selectively enables SSE2, AVX2, and AVX512 when it is needed.
+ *
+ * Using this option _globally_ allows this feature, and therefore makes it
+ * undefined behavior to execute on any CPU without said feature.
+ *
+ * Even if the source code isn't directly using AVX intrinsics in a function,
+ * the compiler can still generate AVX code from autovectorization and by
+ * "upgrading" SSE2 intrinsics to use the VEX prefixes (a.k.a. AVX128).
+ *
+ * Use the same flags that you use to compile the rest of the program; this
+ * file will safely generate SSE2, AVX2, and AVX512 without these flags.
+ *
+ * Define XXH_X86DISPATCH_ALLOW_AVX to ignore this check, and feel free to open
+ * an issue if there is a target in the future where AVX is a default feature.
+ */
+#ifdef XXH_DOXYGEN
+# define XXH_X86DISPATCH_ALLOW_AVX
+#endif
+
+#if defined(__AVX__) && !defined(XXH_X86DISPATCH_ALLOW_AVX)
+# error "Do not compile xxh_x86dispatch.c with AVX enabled! See the comment above."
+#endif
+
+#ifdef __has_include
+# define XXH_HAS_INCLUDE(header) __has_include(header)
+#else
+# define XXH_HAS_INCLUDE(header) 0
#endif
-#define XXH_DISPATCH_AVX2 /* enable dispatch towards AVX2 */
-#define XXH_DISPATCH_AVX512 /* enable dispatch towards AVX512 */
+/*!
+ * @def XXH_DISPATCH_SCALAR
+ * @brief Enables/dispatching the scalar code path.
+ *
+ * If this is defined to 0, SSE2 support is assumed. This reduces code size
+ * when the scalar path is not needed.
+ *
+ * This is automatically defined to 0 when...
+ * - SSE2 support is enabled in the compiler
+ * - Targeting x86_64
+ * - Targeting Android x86
+ * - Targeting macOS
+ */
+#ifndef XXH_DISPATCH_SCALAR
+# if defined(__SSE2__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) /* SSE2 on by default */ \
+ || defined(__x86_64__) || defined(_M_X64) /* x86_64 */ \
+ || defined(__ANDROID__) || defined(__APPLEv__) /* Android or macOS */
+# define XXH_DISPATCH_SCALAR 0 /* disable */
+# else
+# define XXH_DISPATCH_SCALAR 1
+# endif
+#endif
+/*!
+ * @def XXH_DISPATCH_AVX2
+ * @brief Enables/disables dispatching for AVX2.
+ *
+ * This is automatically detected if it is not defined.
+ * - GCC 4.7 and later are known to support AVX2, but >4.9 is required for
+ * to get the AVX2 intrinsics and typedefs without -mavx -mavx2.
+ * - Visual Studio 2013 Update 2 and later are known to support AVX2.
+ * - The GCC/Clang internal header `<avx2intrin.h>` is detected. While this is
+ * not allowed to be included directly, it still appears in the builtin
+ * include path and is detectable with `__has_include`.
+ *
+ * @see XXH_AVX2
+ */
+#ifndef XXH_DISPATCH_AVX2
+# if (defined(__GNUC__) && (__GNUC__ > 4)) /* GCC 5.0+ */ \
+ || (defined(_MSC_VER) && _MSC_VER >= 1900) /* VS 2015+ */ \
+ || (defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 180030501) /* VS 2013 Update 2 */ \
+ || XXH_HAS_INCLUDE(<avx2intrin.h>) /* GCC/Clang internal header */
+# define XXH_DISPATCH_AVX2 1 /* enable dispatch towards AVX2 */
+# else
+# define XXH_DISPATCH_AVX2 0
+# endif
+#endif /* XXH_DISPATCH_AVX2 */
+
+/*!
+ * @def XXH_DISPATCH_AVX512
+ * @brief Enables/disables dispatching for AVX512.
+ *
+ * Automatically detected if one of the following conditions is met:
+ * - GCC 4.9 and later are known to support AVX512.
+ * - Visual Studio 2017 and later are known to support AVX2.
+ * - The GCC/Clang internal header `<avx512fintrin.h>` is detected. While this
+ * is not allowed to be included directly, it still appears in the builtin
+ * include path and is detectable with `__has_include`.
+ *
+ * @see XXH_AVX512
+ */
+#ifndef XXH_DISPATCH_AVX512
+# if (defined(__GNUC__) \
+ && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))) /* GCC 4.9+ */ \
+ || (defined(_MSC_VER) && _MSC_VER >= 1910) /* VS 2017+ */ \
+ || XXH_HAS_INCLUDE(<avx512fintrin.h>) /* GCC/Clang internal header */
+# define XXH_DISPATCH_AVX512 1 /* enable dispatch towards AVX512 */
+# else
+# define XXH_DISPATCH_AVX512 0
+# endif
+#endif /* XXH_DISPATCH_AVX512 */
+
+/*!
+ * @def XXH_TARGET_SSE2
+ * @brief Allows a function to be compiled with SSE2 intrinsics.
+ *
+ * Uses `__attribute__((__target__("sse2")))` on GCC to allow SSE2 to be used
+ * even with `-mno-sse2`.
+ *
+ * @def XXH_TARGET_AVX2
+ * @brief Like @ref XXH_TARGET_SSE2, but for AVX2.
+ *
+ * @def XXH_TARGET_AVX512
+ * @brief Like @ref XXH_TARGET_SSE2, but for AVX512.
+ */
+#if defined(__GNUC__)
+# include <emmintrin.h> /* SSE2 */
+# if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
+# include <immintrin.h> /* AVX2, AVX512F */
+# endif
+# define XXH_TARGET_SSE2 __attribute__((__target__("sse2")))
+# define XXH_TARGET_AVX2 __attribute__((__target__("avx2")))
+# define XXH_TARGET_AVX512 __attribute__((__target__("avx512f")))
+#elif defined(_MSC_VER)
+# include <intrin.h>
+# define XXH_TARGET_SSE2
+# define XXH_TARGET_AVX2
+# define XXH_TARGET_AVX512
+#else
+# error "Dispatching is currently not supported for your compiler."
+#endif
#ifdef XXH_DISPATCH_DEBUG
/* debug logging */
#endif
#include <assert.h>
-#if defined(__GNUC__)
-# include <immintrin.h> /* sse2 */
-# include <emmintrin.h> /* avx2 */
-#elif defined(_MSC_VER)
-# include <intrin.h>
-#endif
-
#define XXH_INLINE_ALL
#define XXH_X86DISPATCH
-#define XXH_TARGET_AVX512 __attribute__((__target__("avx512f")))
-#define XXH_TARGET_AVX2 __attribute__((__target__("avx2")))
-#define XXH_TARGET_SSE2 __attribute__((__target__("sse2")))
#include "xxhash.h"
-/*
- * Modified version of Intel's guide
- * https://software.intel.com/en-us/articles/how-to-detect-new-instruction-support-in-the-4th-generation-intel-core-processor-family
- */
-#if defined(_MSC_VER)
-# include <intrin.h>
-#endif
-
/*
* Support both AT&T and Intel dialects
*
* Note: Comments are written in the inline assembly itself.
*/
#ifdef __clang__
-# define I_ATT(intel, att) att "\n\t"
+# define XXH_I_ATT(intel, att) att "\n\t"
#else
-# define I_ATT(intel, att) "{" att "|" intel "}\n\t"
+# define XXH_I_ATT(intel, att) "{" att "|" intel "}\n\t"
#endif
-
+/*!
+ * @internal
+ * @brief Runs CPUID.
+ *
+ * @param eax , ecx The parameters to pass to CPUID, %eax and %ecx respectively.
+ * @param abcd The array to store the result in, `{ eax, ebx, ecx, edx }`
+ */
static void XXH_cpuid(xxh_u32 eax, xxh_u32 ecx, xxh_u32* abcd)
{
#if defined(_MSC_VER)
"#\n\t"
"# On 32-bit x86 with PIC enabled, we are not allowed to overwrite\n\t"
"# EBX, so we use EDI instead.\n\t"
- I_ATT("mov edi, ebx", "movl %%ebx, %%edi")
- I_ATT("cpuid", "cpuid" )
- I_ATT("xchg edi, ebx", "xchgl %%ebx, %%edi")
+ XXH_I_ATT("mov edi, ebx", "movl %%ebx, %%edi")
+ XXH_I_ATT("cpuid", "cpuid" )
+ XXH_I_ATT("xchg edi, ebx", "xchgl %%ebx, %%edi")
: "=D" (ebx),
# else
__asm__(
"# Call CPUID\n\t"
- I_ATT("cpuid", "cpuid")
+ XXH_I_ATT("cpuid", "cpuid")
: "=b" (ebx),
# endif
"+a" (eax), "+c" (ecx), "=d" (edx));
#endif
}
-#if defined(XXH_DISPATCH_AVX2) || defined(XXH_DISPATCH_AVX512)
/*
+ * Modified version of Intel's guide
+ * https://software.intel.com/en-us/articles/how-to-detect-new-instruction-support-in-the-4th-generation-intel-core-processor-family
+ */
+
+#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
+/*!
+ * @internal
+ * @brief Runs `XGETBV`.
+ *
* While the CPU may support AVX2, the operating system might not properly save
* the full YMM/ZMM registers.
*
}
#endif
-#define SSE2_CPUID_MASK (1 << 26)
-#define OSXSAVE_CPUID_MASK ((1 << 26) | (1 << 27))
-#define AVX2_CPUID_MASK (1 << 5)
-#define AVX2_XGETBV_MASK ((1 << 2) | (1 << 1))
-#define AVX512F_CPUID_MASK (1 << 16)
-#define AVX512F_XGETBV_MASK ((7 << 5) | (1 << 2) | (1 << 1))
+#define XXH_SSE2_CPUID_MASK (1 << 26)
+#define XXH_OSXSAVE_CPUID_MASK ((1 << 26) | (1 << 27))
+#define XXH_AVX2_CPUID_MASK (1 << 5)
+#define XXH_AVX2_XGETBV_MASK ((1 << 2) | (1 << 1))
+#define XXH_AVX512F_CPUID_MASK (1 << 16)
+#define XXH_AVX512F_XGETBV_MASK ((7 << 5) | (1 << 2) | (1 << 1))
-/* Returns the best XXH3 implementation */
+/*!
+ * @internal
+ * @brief Returns the best XXH3 implementation.
+ *
+ * Runs various CPUID/XGETBV tests to try and determine the best implementation.
+ *
+ * @ret The best @ref XXH_VECTOR implementation.
+ * @see XXH_VECTOR_TYPES
+ */
static int XXH_featureTest(void)
{
xxh_u32 abcd[4];
xxh_u32 max_leaves;
int best = XXH_SCALAR;
-#if defined(XXH_DISPATCH_AVX2) || defined(XXH_DISPATCH_AVX512)
+#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
xxh_u64 xgetbv_val;
#endif
#if defined(__GNUC__) && defined(__i386__)
"# Routine is from <https://wiki.osdev.org/CPUID>.\n\t"
"# Save EFLAGS\n\t"
- I_ATT("pushfd", "pushfl" )
+ XXH_I_ATT("pushfd", "pushfl" )
"# Store EFLAGS\n\t"
- I_ATT("pushfd", "pushfl" )
+ XXH_I_ATT("pushfd", "pushfl" )
"# Invert the ID bit in stored EFLAGS\n\t"
- I_ATT("xor dword ptr[esp], 0x200000", "xorl $0x200000, (%%esp)")
+ XXH_I_ATT("xor dword ptr[esp], 0x200000", "xorl $0x200000, (%%esp)")
"# Load stored EFLAGS (with ID bit inverted)\n\t"
- I_ATT("popfd", "popfl" )
+ XXH_I_ATT("popfd", "popfl" )
"# Store EFLAGS again (ID bit may or not be inverted)\n\t"
- I_ATT("pushfd", "pushfl" )
+ XXH_I_ATT("pushfd", "pushfl" )
"# eax = modified EFLAGS (ID bit may or may not be inverted)\n\t"
- I_ATT("pop eax", "popl %%eax" )
+ XXH_I_ATT("pop eax", "popl %%eax" )
"# eax = whichever bits were changed\n\t"
- I_ATT("xor eax, dword ptr[esp]", "xorl (%%esp), %%eax" )
+ XXH_I_ATT("xor eax, dword ptr[esp]", "xorl (%%esp), %%eax" )
"# Restore original EFLAGS\n\t"
- I_ATT("popfd", "popfl" )
+ XXH_I_ATT("popfd", "popfl" )
"# eax = zero if ID bit can't be changed, else non-zero\n\t"
- I_ATT("and eax, 0x200000", "andl $0x200000, %%eax" )
+ XXH_I_ATT("and eax, 0x200000", "andl $0x200000, %%eax" )
: "=a" (cpuid_supported) :: "cc");
if (XXH_unlikely(!cpuid_supported)) {
/*
* Test for SSE2. The check is redundant on x86_64, but it doesn't hurt.
*/
- if (XXH_unlikely((abcd[3] & SSE2_CPUID_MASK) != SSE2_CPUID_MASK))
+ if (XXH_unlikely((abcd[3] & XXH_SSE2_CPUID_MASK) != XXH_SSE2_CPUID_MASK))
return best;
XXH_debugPrint("SSE2 support detected.");
best = XXH_SSE2;
-#if defined(XXH_DISPATCH_AVX2) || defined(XXH_DISPATCH_AVX512)
+#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
/* Make sure we have enough leaves */
if (XXH_unlikely(max_leaves < 7))
return best;
/* Test for OSXSAVE and XGETBV */
- if ((abcd[2] & OSXSAVE_CPUID_MASK) != OSXSAVE_CPUID_MASK)
+ if ((abcd[2] & XXH_OSXSAVE_CPUID_MASK) != XXH_OSXSAVE_CPUID_MASK)
return best;
/* CPUID check for AVX features */
XXH_cpuid(7, 0, abcd);
xgetbv_val = XXH_xgetbv();
-#if defined(XXH_DISPATCH_AVX2)
+#if XXH_DISPATCH_AVX2
/* Validate that AVX2 is supported by the CPU */
- if ((abcd[1] & AVX2_CPUID_MASK) != AVX2_CPUID_MASK)
+ if ((abcd[1] & XXH_AVX2_CPUID_MASK) != XXH_AVX2_CPUID_MASK)
return best;
/* Validate that the OS supports YMM registers */
- if ((xgetbv_val & AVX2_XGETBV_MASK) != AVX2_XGETBV_MASK) {
+ if ((xgetbv_val & XXH_AVX2_XGETBV_MASK) != XXH_AVX2_XGETBV_MASK) {
XXH_debugPrint("AVX2 supported by the CPU, but not the OS.");
return best;
}
XXH_debugPrint("AVX2 support detected.");
best = XXH_AVX2;
#endif
-#if defined(XXH_DISPATCH_AVX512)
+#if XXH_DISPATCH_AVX512
/* Check if AVX512F is supported by the CPU */
- if ((abcd[1] & AVX512F_CPUID_MASK) != AVX512F_CPUID_MASK) {
+ if ((abcd[1] & XXH_AVX512F_CPUID_MASK) != XXH_AVX512F_CPUID_MASK) {
XXH_debugPrint("AVX512F not supported by CPU");
return best;
}
/* Validate that the OS supports ZMM registers */
- if ((xgetbv_val & AVX512F_XGETBV_MASK) != AVX512F_XGETBV_MASK) {
+ if ((xgetbv_val & XXH_AVX512F_XGETBV_MASK) != XXH_AVX512F_XGETBV_MASK) {
XXH_debugPrint("AVX512F supported by the CPU, but not the OS.");
return best;
}
/* === Vector implementations === */
-/* === XXH3, default variants === */
-
-XXH_NO_INLINE XXH64_hash_t
-XXHL64_default_scalar(const void* XXH_RESTRICT input, size_t len)
-{
- return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512_scalar, XXH3_scrambleAcc_scalar);
-}
-
-XXH_NO_INLINE XXH_TARGET_SSE2 XXH64_hash_t
-XXHL64_default_sse2(const void* XXH_RESTRICT input, size_t len)
-{
- return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512_sse2, XXH3_scrambleAcc_sse2);
-}
-
-#ifdef XXH_DISPATCH_AVX2
-XXH_NO_INLINE XXH_TARGET_AVX2 XXH64_hash_t
-XXHL64_default_avx2(const void* XXH_RESTRICT input, size_t len)
-{
- return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512_avx2, XXH3_scrambleAcc_avx2);
-}
-#endif
-
-#ifdef XXH_DISPATCH_AVX512
-XXH_NO_INLINE XXH_TARGET_AVX512 XXH64_hash_t
-XXHL64_default_avx512(const void* XXH_RESTRICT input, size_t len)
-{
- return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512_avx512, XXH3_scrambleAcc_avx512);
-}
-#endif
-
-/* === XXH3, Seeded variants === */
-
-XXH_NO_INLINE XXH64_hash_t
-XXHL64_seed_scalar(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed)
-{
- return XXH3_hashLong_64b_withSeed_internal(input, len, seed,
- XXH3_accumulate_512_scalar, XXH3_scrambleAcc_scalar, XXH3_initCustomSecret_scalar);
-}
-
-XXH_NO_INLINE XXH_TARGET_SSE2 XXH64_hash_t
-XXHL64_seed_sse2(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed)
-{
- return XXH3_hashLong_64b_withSeed_internal(input, len, seed,
- XXH3_accumulate_512_sse2, XXH3_scrambleAcc_sse2, XXH3_initCustomSecret_sse2);
-}
-
-#ifdef XXH_DISPATCH_AVX2
-XXH_NO_INLINE XXH_TARGET_AVX2 XXH64_hash_t
-XXHL64_seed_avx2(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed)
-{
- return XXH3_hashLong_64b_withSeed_internal(input, len, seed,
- XXH3_accumulate_512_avx2, XXH3_scrambleAcc_avx2, XXH3_initCustomSecret_avx2);
-}
-#endif
-
-#ifdef XXH_DISPATCH_AVX512
-XXH_NO_INLINE XXH_TARGET_AVX512 XXH64_hash_t
-XXHL64_seed_avx512(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed)
-{
- return XXH3_hashLong_64b_withSeed_internal(input, len, seed,
- XXH3_accumulate_512_avx512, XXH3_scrambleAcc_avx512, XXH3_initCustomSecret_avx512);
-}
-#endif
-
-/* === XXH3, Secret variants === */
-
-XXH_NO_INLINE XXH64_hash_t
-XXHL64_secret_scalar(const void* XXH_RESTRICT input, size_t len, const void* secret, size_t secretLen)
-{
- return XXH3_hashLong_64b_internal(input, len, secret, secretLen,
- XXH3_accumulate_512_scalar, XXH3_scrambleAcc_scalar);
-}
-
-XXH_NO_INLINE XXH_TARGET_SSE2 XXH64_hash_t
-XXHL64_secret_sse2(const void* XXH_RESTRICT input, size_t len, const void* secret, size_t secretLen)
-{
- return XXH3_hashLong_64b_internal(input, len, secret, secretLen,
- XXH3_accumulate_512_sse2, XXH3_scrambleAcc_sse2);
-}
-
-#ifdef XXH_DISPATCH_AVX2
-XXH_NO_INLINE XXH_TARGET_AVX2 XXH64_hash_t
-XXHL64_secret_avx2(const void* XXH_RESTRICT input, size_t len, const void* secret, size_t secretLen)
-{
- return XXH3_hashLong_64b_internal(input, len, secret, secretLen,
- XXH3_accumulate_512_avx2, XXH3_scrambleAcc_avx2);
-}
-#endif
-
-#ifdef XXH_DISPATCH_AVX512
-XXH_NO_INLINE XXH_TARGET_AVX512 XXH64_hash_t
-XXHL64_secret_avx512(const void* XXH_RESTRICT input, size_t len, const void* secret, size_t secretLen)
-{
- return XXH3_hashLong_64b_internal(input, len, secret, secretLen,
- XXH3_accumulate_512_avx512, XXH3_scrambleAcc_avx512);
-}
-#endif
-
-/* === XXH3 update variants === */
-
-XXH_NO_INLINE XXH_errorcode
-XXH3_64bits_update_scalar(XXH3_state_t* state, const void* input, size_t len)
-{
- return XXH3_update(state, (const xxh_u8*)input, len,
- XXH3_accumulate_512_scalar, XXH3_scrambleAcc_scalar);
-}
-
-XXH_NO_INLINE XXH_TARGET_SSE2 XXH_errorcode
-XXH3_64bits_update_sse2(XXH3_state_t* state, const void* input, size_t len)
-{
- return XXH3_update(state, (const xxh_u8*)input, len,
- XXH3_accumulate_512_sse2, XXH3_scrambleAcc_sse2);
-}
-
-#ifdef XXH_DISPATCH_AVX2
-XXH_NO_INLINE XXH_TARGET_AVX2 XXH_errorcode
-XXH3_64bits_update_avx2(XXH3_state_t* state, const void* input, size_t len)
-{
- return XXH3_update(state, (const xxh_u8*)input, len,
- XXH3_accumulate_512_avx2, XXH3_scrambleAcc_avx2);
-}
-#endif
-
-#ifdef XXH_DISPATCH_AVX512
-XXH_NO_INLINE XXH_TARGET_AVX512 XXH_errorcode
-XXH3_64bits_update_avx512(XXH3_state_t* state, const void* input, size_t len)
-{
- return XXH3_update(state, (const xxh_u8*)input, len,
- XXH3_accumulate_512_avx512, XXH3_scrambleAcc_avx512);
-}
-#endif
-
-/* === XXH128 default variants === */
-
-XXH_NO_INLINE XXH128_hash_t
-XXHL128_default_scalar(const void* XXH_RESTRICT input, size_t len)
-{
- return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512_scalar, XXH3_scrambleAcc_scalar);
-}
-
-XXH_NO_INLINE XXH_TARGET_SSE2 XXH128_hash_t
-XXHL128_default_sse2(const void* XXH_RESTRICT input, size_t len)
-{
- return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512_sse2, XXH3_scrambleAcc_sse2);
-}
-
-#ifdef XXH_DISPATCH_AVX2
-XXH_NO_INLINE XXH_TARGET_AVX2 XXH128_hash_t
-XXHL128_default_avx2(const void* XXH_RESTRICT input, size_t len)
-{
- return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512_avx2, XXH3_scrambleAcc_avx2);
-}
-#endif
-
-#ifdef XXH_DISPATCH_AVX512
-XXH_NO_INLINE XXH_TARGET_AVX512 XXH128_hash_t
-XXHL128_default_avx512(const void* XXH_RESTRICT input, size_t len)
-{
- return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512_avx512, XXH3_scrambleAcc_avx512);
-}
-#endif
-
-/* === XXH128 Secret variants === */
-
-XXH_NO_INLINE XXH128_hash_t
-XXHL128_secret_scalar(const void* XXH_RESTRICT input, size_t len, const void* XXH_RESTRICT secret, size_t secretLen)
-{
- return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, secretLen,
- XXH3_accumulate_512_scalar, XXH3_scrambleAcc_scalar);
-}
-
-XXH_NO_INLINE XXH_TARGET_SSE2 XXH128_hash_t
-XXHL128_secret_sse2(const void* XXH_RESTRICT input, size_t len, const void* XXH_RESTRICT secret, size_t secretLen)
-{
- return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, secretLen,
- XXH3_accumulate_512_sse2, XXH3_scrambleAcc_sse2);
-}
-
-#ifdef XXH_DISPATCH_AVX2
-XXH_NO_INLINE XXH_TARGET_AVX2 XXH128_hash_t
-XXHL128_secret_avx2(const void* XXH_RESTRICT input, size_t len, const void* XXH_RESTRICT secret, size_t secretLen)
-{
- return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, secretLen,
- XXH3_accumulate_512_avx2, XXH3_scrambleAcc_avx2);
-}
-#endif
-
-#ifdef XXH_DISPATCH_AVX512
-XXH_NO_INLINE XXH_TARGET_AVX512 XXH128_hash_t
-XXHL128_secret_avx512(const void* XXH_RESTRICT input, size_t len, const void* XXH_RESTRICT secret, size_t secretLen)
-{
- return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, secretLen,
- XXH3_accumulate_512_avx512, XXH3_scrambleAcc_avx512);
-}
-#endif
-
-/* === XXH128 Seeded variants === */
-
-XXH_NO_INLINE XXH128_hash_t
-XXHL128_seed_scalar(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed)
-{
- return XXH3_hashLong_128b_withSeed_internal(input, len, seed,
- XXH3_accumulate_512_scalar, XXH3_scrambleAcc_scalar, XXH3_initCustomSecret_scalar);
-}
-
-XXH_NO_INLINE XXH_TARGET_SSE2 XXH128_hash_t
-XXHL128_seed_sse2(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed)
-{
- return XXH3_hashLong_128b_withSeed_internal(input, len, seed,
- XXH3_accumulate_512_sse2, XXH3_scrambleAcc_sse2, XXH3_initCustomSecret_sse2);
-}
-
-#ifdef XXH_DISPATCH_AVX2
-XXH_NO_INLINE XXH_TARGET_AVX2 XXH128_hash_t
-XXHL128_seed_avx2(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed)
-{
- return XXH3_hashLong_128b_withSeed_internal(input, len, seed,
- XXH3_accumulate_512_avx2, XXH3_scrambleAcc_avx2, XXH3_initCustomSecret_avx2);
-}
-#endif
-
-#ifdef XXH_DISPATCH_AVX512
-XXH_NO_INLINE XXH_TARGET_AVX512 XXH128_hash_t
-XXHL128_seed_avx512(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed)
-{
- return XXH3_hashLong_128b_withSeed_internal(input, len, seed,
- XXH3_accumulate_512_avx512, XXH3_scrambleAcc_avx512, XXH3_initCustomSecret_avx512);
-}
-#endif
-
-/* === XXH128 update variants === */
-
-XXH_NO_INLINE XXH_errorcode
-XXH3_128bits_update_scalar(XXH3_state_t* state, const void* input, size_t len)
-{
- return XXH3_update(state, (const xxh_u8*)input, len,
- XXH3_accumulate_512_scalar, XXH3_scrambleAcc_scalar);
-}
-
-XXH_NO_INLINE XXH_TARGET_SSE2 XXH_errorcode
-XXH3_128bits_update_sse2(XXH3_state_t* state, const void* input, size_t len)
-{
- return XXH3_update(state, (const xxh_u8*)input, len,
- XXH3_accumulate_512_sse2, XXH3_scrambleAcc_sse2);
-}
-
-#ifdef XXH_DISPATCH_AVX2
-XXH_NO_INLINE XXH_TARGET_AVX2 XXH_errorcode
-XXH3_128bits_update_avx2(XXH3_state_t* state, const void* input, size_t len)
-{
- return XXH3_update(state, (const xxh_u8*)input, len,
- XXH3_accumulate_512_avx2, XXH3_scrambleAcc_avx2);
-}
-#endif
-
-#ifdef XXH_DISPATCH_AVX512
-XXH_NO_INLINE XXH_TARGET_AVX512 XXH_errorcode
-XXH3_128bits_update_avx512(XXH3_state_t* state, const void* input, size_t len)
-{
- return XXH3_update(state, (const xxh_u8*)input, len,
- XXH3_accumulate_512_avx512, XXH3_scrambleAcc_avx512);
-}
-#endif
+/*!
+ * @internal
+ * @brief Defines the various dispatch functions.
+ *
+ * TODO: Consolidate?
+ *
+ * @param suffix The suffix for the functions, e.g. sse2 or scalar
+ * @param target XXH_TARGET_* or empty.
+ */
+#define XXH_DEFINE_DISPATCH_FUNCS(suffix, target) \
+ \
+/* === XXH3, default variants === */ \
+ \
+XXH_NO_INLINE target XXH64_hash_t \
+XXHL64_default_##suffix(const void* XXH_RESTRICT input, size_t len) \
+{ \
+ return XXH3_hashLong_64b_internal( \
+ input, len, XXH3_kSecret, sizeof(XXH3_kSecret), \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix \
+ ); \
+} \
+ \
+/* === XXH3, Seeded variants === */ \
+ \
+XXH_NO_INLINE target XXH64_hash_t \
+XXHL64_seed_##suffix(const void* XXH_RESTRICT input, size_t len, \
+ XXH64_hash_t seed) \
+{ \
+ return XXH3_hashLong_64b_withSeed_internal( \
+ input, len, seed, XXH3_accumulate_512_##suffix, \
+ XXH3_scrambleAcc_##suffix, XXH3_initCustomSecret_##suffix \
+ ); \
+} \
+ \
+/* === XXH3, Secret variants === */ \
+ \
+XXH_NO_INLINE target XXH64_hash_t \
+XXHL64_secret_##suffix(const void* XXH_RESTRICT input, size_t len, \
+ const void* secret, size_t secretLen) \
+{ \
+ return XXH3_hashLong_64b_internal( \
+ input, len, secret, secretLen, \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix \
+ ); \
+} \
+ \
+/* === XXH3 update variants === */ \
+ \
+XXH_NO_INLINE target XXH_errorcode \
+XXH3_update_##suffix(XXH3_state_t* state, const void* input, size_t len) \
+{ \
+ return XXH3_update(state, (const xxh_u8*)input, len, \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix); \
+} \
+ \
+/* === XXH128 default variants === */ \
+ \
+XXH_NO_INLINE target XXH128_hash_t \
+XXHL128_default_##suffix(const void* XXH_RESTRICT input, size_t len) \
+{ \
+ return XXH3_hashLong_128b_internal( \
+ input, len, XXH3_kSecret, sizeof(XXH3_kSecret), \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix \
+ ); \
+} \
+ \
+/* === XXH128 Secret variants === */ \
+ \
+XXH_NO_INLINE target XXH128_hash_t \
+XXHL128_secret_##suffix(const void* XXH_RESTRICT input, size_t len, \
+ const void* XXH_RESTRICT secret, size_t secretLen) \
+{ \
+ return XXH3_hashLong_128b_internal( \
+ input, len, (const xxh_u8*)secret, secretLen, \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix); \
+} \
+ \
+/* === XXH128 Seeded variants === */ \
+ \
+XXH_NO_INLINE target XXH128_hash_t \
+XXHL128_seed_##suffix(const void* XXH_RESTRICT input, size_t len, \
+ XXH64_hash_t seed) \
+{ \
+ return XXH3_hashLong_128b_withSeed_internal(input, len, seed, \
+ XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix, \
+ XXH3_initCustomSecret_##suffix); \
+}
+
+/* End XXH_DEFINE_DISPATCH_FUNCS */
+
+#if XXH_DISPATCH_SCALAR
+XXH_DEFINE_DISPATCH_FUNCS(scalar, /* nothing */)
+#endif
+XXH_DEFINE_DISPATCH_FUNCS(sse2, XXH_TARGET_SSE2)
+#if XXH_DISPATCH_AVX2
+XXH_DEFINE_DISPATCH_FUNCS(avx2, XXH_TARGET_AVX2)
+#endif
+#if XXH_DISPATCH_AVX512
+XXH_DEFINE_DISPATCH_FUNCS(avx512, XXH_TARGET_AVX512)
+#endif
+#undef XXH_DEFINE_DISPATCH_FUNCS
/* ==== Dispatchers ==== */
XXH3_dispatchx86_hashLong64_withSeed hashLong64_seed;
XXH3_dispatchx86_hashLong64_withSecret hashLong64_secret;
XXH3_dispatchx86_update update;
-} dispatchFunctions_s;
+} XXH_dispatchFunctions_s;
-static dispatchFunctions_s g_dispatch = { NULL, NULL, NULL, NULL};
+#define XXH_NB_DISPATCHES 4
-#define NB_DISPATCHES 4
-static const dispatchFunctions_s k_dispatch[NB_DISPATCHES] = {
- /* scalar */ { XXHL64_default_scalar, XXHL64_seed_scalar, XXHL64_secret_scalar, XXH3_64bits_update_scalar },
- /* sse2 */ { XXHL64_default_sse2, XXHL64_seed_sse2, XXHL64_secret_sse2, XXH3_64bits_update_sse2 },
-#ifdef XXH_DISPATCH_AVX2
- /* avx2 */ { XXHL64_default_avx2, XXHL64_seed_avx2, XXHL64_secret_avx2, XXH3_64bits_update_avx2 },
+/*!
+ * @internal
+ * @brief Table of dispatchers for @ref XXH3_64bits().
+ *
+ * @pre The indices must match @ref XXH_VECTOR_TYPE.
+ */
+static const XXH_dispatchFunctions_s XXH_kDispatch[XXH_NB_DISPATCHES] = {
+#if XXH_DISPATCH_SCALAR
+ /* Scalar */ { XXHL64_default_scalar, XXHL64_seed_scalar, XXHL64_secret_scalar, XXH3_update_scalar },
+#else
+ /* Scalar */ { NULL, NULL, NULL, NULL },
+#endif
+ /* SSE2 */ { XXHL64_default_sse2, XXHL64_seed_sse2, XXHL64_secret_sse2, XXH3_update_sse2 },
+#if XXH_DISPATCH_AVX2
+ /* AVX2 */ { XXHL64_default_avx2, XXHL64_seed_avx2, XXHL64_secret_avx2, XXH3_update_avx2 },
#else
- /* avx2 */ { NULL, NULL, NULL, NULL },
+ /* AVX2 */ { NULL, NULL, NULL, NULL },
#endif
-#ifdef XXH_DISPATCH_AVX512
- /* avx512 */ { XXHL64_default_avx512, XXHL64_seed_avx512, XXHL64_secret_avx512, XXH3_64bits_update_avx512 }
+#if XXH_DISPATCH_AVX512
+ /* AVX512 */ { XXHL64_default_avx512, XXHL64_seed_avx512, XXHL64_secret_avx512, XXH3_update_avx512 }
#else
- /* avx512 */ { NULL, NULL, NULL, NULL }
+ /* AVX512 */ { NULL, NULL, NULL, NULL }
#endif
};
+/*!
+ * @internal
+ * @brief The selected dispatch table for @ref XXH3_64bits().
+ */
+static XXH_dispatchFunctions_s XXH_g_dispatch = { NULL, NULL, NULL, NULL };
+
typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_default)(const void* XXH_RESTRICT, size_t);
XXH3_dispatchx86_hashLong128_withSeed hashLong128_seed;
XXH3_dispatchx86_hashLong128_withSecret hashLong128_secret;
XXH3_dispatchx86_update update;
-} dispatch128Functions_s;
+} XXH_dispatch128Functions_s;
-static dispatch128Functions_s g_dispatch128 = { NULL, NULL, NULL, NULL };
-static const dispatch128Functions_s k_dispatch128[NB_DISPATCHES] = {
- /* scalar */ { XXHL128_default_scalar, XXHL128_seed_scalar, XXHL128_secret_scalar, XXH3_128bits_update_scalar },
- /* sse2 */ { XXHL128_default_sse2, XXHL128_seed_sse2, XXHL128_secret_sse2, XXH3_128bits_update_sse2 },
-#ifdef XXH_DISPATCH_AVX2
- /* avx2 */ { XXHL128_default_avx2, XXHL128_seed_avx2, XXHL128_secret_avx2, XXH3_128bits_update_avx2 },
+/*!
+ * @internal
+ * @brief Table of dispatchers for @ref XXH3_128bits().
+ *
+ * @pre The indices must match @ref XXH_VECTOR_TYPE.
+ */
+static const XXH_dispatch128Functions_s XXH_kDispatch128[XXH_NB_DISPATCHES] = {
+#if XXH_DISPATCH_SCALAR
+ /* Scalar */ { XXHL128_default_scalar, XXHL128_seed_scalar, XXHL128_secret_scalar, XXH3_update_scalar },
+#else
+ /* Scalar */ { NULL, NULL, NULL, NULL },
+#endif
+ /* SSE2 */ { XXHL128_default_sse2, XXHL128_seed_sse2, XXHL128_secret_sse2, XXH3_update_sse2 },
+#if XXH_DISPATCH_AVX2
+ /* AVX2 */ { XXHL128_default_avx2, XXHL128_seed_avx2, XXHL128_secret_avx2, XXH3_update_avx2 },
#else
- /* avx2 */ { NULL, NULL, NULL, NULL },
+ /* AVX2 */ { NULL, NULL, NULL, NULL },
#endif
-#ifdef XXH_DISPATCH_AVX512
- /* avx512 */ { XXHL128_default_avx512, XXHL128_seed_avx512, XXHL128_secret_avx512, XXH3_128bits_update_avx512 }
+#if XXH_DISPATCH_AVX512
+ /* AVX512 */ { XXHL128_default_avx512, XXHL128_seed_avx512, XXHL128_secret_avx512, XXH3_update_avx512 }
#else
- /* avx512 */ { NULL, NULL, NULL, NULL }
+ /* AVX512 */ { NULL, NULL, NULL, NULL }
#endif
};
-static void setDispatch(void)
+/*!
+ * @internal
+ * @brief The selected dispatch table for @ref XXH3_64bits().
+ */
+static XXH_dispatch128Functions_s XXH_g_dispatch128 = { NULL, NULL, NULL, NULL };
+
+/*!
+ * @internal
+ * @brief Runs a CPUID check and sets the correct dispatch tables.
+ */
+static void XXH_setDispatch(void)
{
int vecID = XXH_featureTest();
- XXH_STATIC_ASSERT(XXH_AVX512 == NB_DISPATCHES-1);
+ XXH_STATIC_ASSERT(XXH_AVX512 == XXH_NB_DISPATCHES-1);
assert(XXH_SCALAR <= vecID && vecID <= XXH_AVX512);
-#ifndef XXH_DISPATCH_AVX512
+#if !XXH_DISPATCH_SCALAR
+ assert(vecID != XXH_SCALAR);
+#endif
+#if !XXH_DISPATCH_AVX512
assert(vecID != XXH_AVX512);
#endif
-#ifndef XXH_DISPATCH_AVX2
+#if !XXH_DISPATCH_AVX2
assert(vecID != XXH_AVX2);
#endif
- g_dispatch = k_dispatch[vecID];
- g_dispatch128 = k_dispatch128[vecID];
+ XXH_g_dispatch = XXH_kDispatch[vecID];
+ XXH_g_dispatch128 = XXH_kDispatch128[vecID];
}
XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
{
(void)seed64; (void)secret; (void)secretLen;
- if (g_dispatch.hashLong64_default == NULL) setDispatch();
- return g_dispatch.hashLong64_default(input, len);
+ if (XXH_g_dispatch.hashLong64_default == NULL) XXH_setDispatch();
+ return XXH_g_dispatch.hashLong64_default(input, len);
}
XXH64_hash_t XXH3_64bits_dispatch(const void* input, size_t len)
XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
{
(void)secret; (void)secretLen;
- if (g_dispatch.hashLong64_seed == NULL) setDispatch();
- return g_dispatch.hashLong64_seed(input, len, seed64);
+ if (XXH_g_dispatch.hashLong64_seed == NULL) XXH_setDispatch();
+ return XXH_g_dispatch.hashLong64_seed(input, len, seed64);
}
XXH64_hash_t XXH3_64bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed)
XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
{
(void)seed64;
- if (g_dispatch.hashLong64_secret == NULL) setDispatch();
- return g_dispatch.hashLong64_secret(input, len, secret, secretLen);
+ if (XXH_g_dispatch.hashLong64_secret == NULL) XXH_setDispatch();
+ return XXH_g_dispatch.hashLong64_secret(input, len, secret, secretLen);
}
XXH64_hash_t XXH3_64bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen)
XXH_errorcode
XXH3_64bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len)
{
- if (g_dispatch.update == NULL) setDispatch();
- return g_dispatch.update(state, (const xxh_u8*)input, len);
+ if (XXH_g_dispatch.update == NULL) XXH_setDispatch();
+ return XXH_g_dispatch.update(state, (const xxh_u8*)input, len);
}
XXH64_hash_t seed64, const void* secret, size_t secretLen)
{
(void)seed64; (void)secret; (void)secretLen;
- if (g_dispatch128.hashLong128_default == NULL) setDispatch();
- return g_dispatch128.hashLong128_default(input, len);
+ if (XXH_g_dispatch128.hashLong128_default == NULL) XXH_setDispatch();
+ return XXH_g_dispatch128.hashLong128_default(input, len);
}
XXH128_hash_t XXH3_128bits_dispatch(const void* input, size_t len)
XXH64_hash_t seed64, const void* secret, size_t secretLen)
{
(void)secret; (void)secretLen;
- if (g_dispatch128.hashLong128_seed == NULL) setDispatch();
- return g_dispatch128.hashLong128_seed(input, len, seed64);
+ if (XXH_g_dispatch128.hashLong128_seed == NULL) XXH_setDispatch();
+ return XXH_g_dispatch128.hashLong128_seed(input, len, seed64);
}
XXH128_hash_t XXH3_128bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed)
XXH64_hash_t seed64, const void* secret, size_t secretLen)
{
(void)seed64;
- if (g_dispatch128.hashLong128_secret == NULL) setDispatch();
- return g_dispatch128.hashLong128_secret(input, len, secret, secretLen);
+ if (XXH_g_dispatch128.hashLong128_secret == NULL) XXH_setDispatch();
+ return XXH_g_dispatch128.hashLong128_secret(input, len, secret, secretLen);
}
XXH128_hash_t XXH3_128bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen)
XXH_errorcode
XXH3_128bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len)
{
- if (g_dispatch128.update == NULL) setDispatch();
- return g_dispatch128.update(state, (const xxh_u8*)input, len);
+ if (XXH_g_dispatch128.update == NULL) XXH_setDispatch();
+ return XXH_g_dispatch128.update(state, (const xxh_u8*)input, len);
}
#if defined (__cplusplus)
}
#endif
+/*! @} */
* - xxHash homepage: https://www.xxhash.com
* - xxHash source repository: https://github.com/Cyan4973/xxHash
*/
-
+/*!
+ * @mainpage xxHash
+ *
+ * @file xxhash.h
+ * xxHash prototypes and implementation
+ */
/* TODO: update */
/* Notice extracted from xxHash homepage:
Name Speed Q.Score Author
xxHash 5.4 GB/s 10
CrapWow 3.2 GB/s 2 Andrew
-MumurHash 3a 2.7 GB/s 10 Austin Appleby
+MurmurHash 3a 2.7 GB/s 10 Austin Appleby
SpookyHash 2.0 GB/s 10 Bob Jenkins
SBox 1.4 GB/s 9 Bret Mulvey
Lookup3 1.2 GB/s 9 Bob Jenkins
/*
* This part deals with the special case where a unit wants to inline xxHash,
- * but "xxhash.h" has previously been included without XXH_INLINE_ALL, such
- * as part of some previously included *.h header file.
+ * but "xxhash.h" has previously been included without XXH_INLINE_ALL,
+ * such as part of some previously included *.h header file.
* Without further action, the new include would just be ignored,
* and functions would effectively _not_ be inlined (silent failure).
* The following macros solve this situation by prefixing all inlined names,
* avoiding naming collision with previous inclusions.
*/
-# ifdef XXH_NAMESPACE
-# error "XXH_INLINE_ALL with XXH_NAMESPACE is not supported"
- /*
- * Note: Alternative: #undef all symbols (it's a pretty large list).
- * Without #error: it compiles, but functions are actually not inlined.
- */
-# endif
+ /* Before that, we unconditionally #undef all symbols,
+ * in case they were already defined with XXH_NAMESPACE.
+ * They will then be redefined for XXH_INLINE_ALL
+ */
+# undef XXH_versionNumber
+ /* XXH32 */
+# undef XXH32
+# undef XXH32_createState
+# undef XXH32_freeState
+# undef XXH32_reset
+# undef XXH32_update
+# undef XXH32_digest
+# undef XXH32_copyState
+# undef XXH32_canonicalFromHash
+# undef XXH32_hashFromCanonical
+ /* XXH64 */
+# undef XXH64
+# undef XXH64_createState
+# undef XXH64_freeState
+# undef XXH64_reset
+# undef XXH64_update
+# undef XXH64_digest
+# undef XXH64_copyState
+# undef XXH64_canonicalFromHash
+# undef XXH64_hashFromCanonical
+ /* XXH3_64bits */
+# undef XXH3_64bits
+# undef XXH3_64bits_withSecret
+# undef XXH3_64bits_withSeed
+# undef XXH3_64bits_withSecretandSeed
+# undef XXH3_createState
+# undef XXH3_freeState
+# undef XXH3_copyState
+# undef XXH3_64bits_reset
+# undef XXH3_64bits_reset_withSeed
+# undef XXH3_64bits_reset_withSecret
+# undef XXH3_64bits_update
+# undef XXH3_64bits_digest
+# undef XXH3_generateSecret
+ /* XXH3_128bits */
+# undef XXH128
+# undef XXH3_128bits
+# undef XXH3_128bits_withSeed
+# undef XXH3_128bits_withSecret
+# undef XXH3_128bits_reset
+# undef XXH3_128bits_reset_withSeed
+# undef XXH3_128bits_reset_withSecret
+# undef XXH3_128bits_reset_withSecretandSeed
+# undef XXH3_128bits_update
+# undef XXH3_128bits_digest
+# undef XXH128_isEqual
+# undef XXH128_cmp
+# undef XXH128_canonicalFromHash
+# undef XXH128_hashFromCanonical
+ /* Finally, free the namespace itself */
+# undef XXH_NAMESPACE
+
+ /* employ the namespace for XXH_INLINE_ALL */
# define XXH_NAMESPACE XXH_INLINE_
/*
- * Some identifiers (enums, type names) are not symbols, but they must
- * still be renamed to avoid redeclaration.
+ * Some identifiers (enums, type names) are not symbols,
+ * but they must nonetheless be renamed to avoid redeclaration.
* Alternative solution: do not redeclare them.
- * However, this requires some #ifdefs, and is a more dispersed action.
- * Meanwhile, renaming can be achieved in a single block
+ * However, this requires some #ifdefs, and has a more dispersed impact.
+ * Meanwhile, renaming can be achieved in a single place.
*/
-# define XXH_IPREF(Id) XXH_INLINE_ ## Id
+# define XXH_IPREF(Id) XXH_NAMESPACE ## Id
# define XXH_OK XXH_IPREF(XXH_OK)
# define XXH_ERROR XXH_IPREF(XXH_ERROR)
# define XXH_errorcode XXH_IPREF(XXH_errorcode)
#ifndef XXHASH_H_5627135585666179
#define XXHASH_H_5627135585666179 1
+
+/*!
+ * @defgroup public Public API
+ * Contains details on the public xxHash functions.
+ * @{
+ */
/* specific declaration modes for Windows */
#if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API)
# if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT))
# endif
#endif
+#ifdef XXH_DOXYGEN
/*!
- * XXH_NAMESPACE, aka Namespace Emulation:
+ * @brief Emulate a namespace by transparently prefixing all symbols.
*
* If you want to include _and expose_ xxHash functions from within your own
* library, but also want to avoid symbol collisions with other libraries which
* includes `xxhash.h`: Regular symbol names will be automatically translated
* by this header.
*/
+# define XXH_NAMESPACE /* YOUR NAME HERE */
+# undef XXH_NAMESPACE
+#endif
+
#ifdef XXH_NAMESPACE
# define XXH_CAT(A,B) A##B
# define XXH_NAME2(A,B) XXH_CAT(A,B)
# define XXH3_64bits XXH_NAME2(XXH_NAMESPACE, XXH3_64bits)
# define XXH3_64bits_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSecret)
# define XXH3_64bits_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSeed)
+# define XXH3_64bits_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSecretandSeed)
# define XXH3_createState XXH_NAME2(XXH_NAMESPACE, XXH3_createState)
# define XXH3_freeState XXH_NAME2(XXH_NAMESPACE, XXH3_freeState)
# define XXH3_copyState XXH_NAME2(XXH_NAMESPACE, XXH3_copyState)
# define XXH3_64bits_reset XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset)
# define XXH3_64bits_reset_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSeed)
# define XXH3_64bits_reset_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSecret)
+# define XXH3_64bits_reset_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSecretandSeed)
# define XXH3_64bits_update XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_update)
# define XXH3_64bits_digest XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_digest)
# define XXH3_generateSecret XXH_NAME2(XXH_NAMESPACE, XXH3_generateSecret)
+# define XXH3_generateSecret_fromSeed XXH_NAME2(XXH_NAMESPACE, XXH3_generateSecret_fromSeed)
/* XXH3_128bits */
# define XXH128 XXH_NAME2(XXH_NAMESPACE, XXH128)
# define XXH3_128bits XXH_NAME2(XXH_NAMESPACE, XXH3_128bits)
# define XXH3_128bits_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSeed)
# define XXH3_128bits_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSecret)
+# define XXH3_128bits_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSecretandSeed)
# define XXH3_128bits_reset XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset)
# define XXH3_128bits_reset_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSeed)
# define XXH3_128bits_reset_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSecret)
+# define XXH3_128bits_reset_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSecretandSeed)
# define XXH3_128bits_update XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_update)
# define XXH3_128bits_digest XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_digest)
# define XXH128_isEqual XXH_NAME2(XXH_NAMESPACE, XXH128_isEqual)
***************************************/
#define XXH_VERSION_MAJOR 0
#define XXH_VERSION_MINOR 8
-#define XXH_VERSION_RELEASE 0
+#define XXH_VERSION_RELEASE 1
#define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE)
+
+/*!
+ * @brief Obtains the xxHash version.
+ *
+ * This is mostly useful when xxHash is compiled as a shared library,
+ * since the returned value comes from the library, as opposed to header file.
+ *
+ * @return `XXH_VERSION_NUMBER` of the invoked library.
+ */
XXH_PUBLIC_API unsigned XXH_versionNumber (void);
/* ****************************
-* Definitions
+* Common basic types
******************************/
#include <stddef.h> /* size_t */
typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode;
/*-**********************************************************************
* 32-bit hash
************************************************************************/
-#if !defined (__VMS) \
+#if defined(XXH_DOXYGEN) /* Don't show <stdint.h> include */
+/*!
+ * @brief An unsigned 32-bit integer.
+ *
+ * Not necessarily defined to `uint32_t` but functionally equivalent.
+ */
+typedef uint32_t XXH32_hash_t;
+
+#elif !defined (__VMS) \
&& (defined (__cplusplus) \
|| (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
# include <stdint.h>
typedef uint32_t XXH32_hash_t;
+
#else
# include <limits.h>
# if UINT_MAX == 0xFFFFFFFFUL
#endif
/*!
- * XXH32():
- * Calculate the 32-bit hash of sequence "length" bytes stored at memory address "input".
- * The memory between input & input+length must be valid (allocated and read-accessible).
- * "seed" can be used to alter the result predictably.
- * Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark): 5.4 GB/s
+ * @}
+ *
+ * @defgroup xxh32_family XXH32 family
+ * @ingroup public
+ * Contains functions used in the classic 32-bit xxHash algorithm.
*
- * Note: XXH3 provides competitive speed for both 32-bit and 64-bit systems,
- * and offers true 64/128 bit hash results. It provides a superior level of
- * dispersion, and greatly reduces the risks of collisions.
+ * @note
+ * XXH32 is useful for older platforms, with no or poor 64-bit performance.
+ * Note that @ref xxh3_family provides competitive speed
+ * for both 32-bit and 64-bit systems, and offers true 64/128 bit hash results.
+ *
+ * @see @ref xxh64_family, @ref xxh3_family : Other xxHash families
+ * @see @ref xxh32_impl for implementation details
+ * @{
*/
-XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed);
-/******* Streaming *******/
+/*!
+ * @brief Calculates the 32-bit hash of @p input using xxHash32.
+ *
+ * Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark): 5.4 GB/s
+ *
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ * @param seed The 32-bit seed to alter the hash's output predictably.
+ *
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return The calculated 32-bit hash value.
+ *
+ * @see
+ * XXH64(), XXH3_64bits_withSeed(), XXH3_128bits_withSeed(), XXH128():
+ * Direct equivalents for the other variants of xxHash.
+ * @see
+ * XXH32_createState(), XXH32_update(), XXH32_digest(): Streaming version.
+ */
+XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed);
-/*
- * Streaming functions generate the xxHash value from an incrememtal input.
+/*!
+ * Streaming functions generate the xxHash value from an incremental input.
* This method is slower than single-call functions, due to state management.
* For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized.
*
* digest, and generate new hash values later on by invoking `XXH*_digest()`.
*
* When done, release the state using `XXH*_freeState()`.
+ *
+ * Example code for incrementally hashing a file:
+ * @code{.c}
+ * #include <stdio.h>
+ * #include <xxhash.h>
+ * #define BUFFER_SIZE 256
+ *
+ * // Note: XXH64 and XXH3 use the same interface.
+ * XXH32_hash_t
+ * hashFile(FILE* stream)
+ * {
+ * XXH32_state_t* state;
+ * unsigned char buf[BUFFER_SIZE];
+ * size_t amt;
+ * XXH32_hash_t hash;
+ *
+ * state = XXH32_createState(); // Create a state
+ * assert(state != NULL); // Error check here
+ * XXH32_reset(state, 0xbaad5eed); // Reset state with our seed
+ * while ((amt = fread(buf, 1, sizeof(buf), stream)) != 0) {
+ * XXH32_update(state, buf, amt); // Hash the file in chunks
+ * }
+ * hash = XXH32_digest(state); // Finalize the hash
+ * XXH32_freeState(state); // Clean up
+ * return hash;
+ * }
+ * @endcode
+ */
+
+/*!
+ * @typedef struct XXH32_state_s XXH32_state_t
+ * @brief The opaque state struct for the XXH32 streaming API.
+ *
+ * @see XXH32_state_s for details.
*/
+typedef struct XXH32_state_s XXH32_state_t;
-typedef struct XXH32_state_s XXH32_state_t; /* incomplete type */
+/*!
+ * @brief Allocates an @ref XXH32_state_t.
+ *
+ * Must be freed with XXH32_freeState().
+ * @return An allocated XXH32_state_t on success, `NULL` on failure.
+ */
XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void);
+/*!
+ * @brief Frees an @ref XXH32_state_t.
+ *
+ * Must be allocated with XXH32_createState().
+ * @param statePtr A pointer to an @ref XXH32_state_t allocated with @ref XXH32_createState().
+ * @return XXH_OK.
+ */
XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr);
+/*!
+ * @brief Copies one @ref XXH32_state_t to another.
+ *
+ * @param dst_state The state to copy to.
+ * @param src_state The state to copy from.
+ * @pre
+ * @p dst_state and @p src_state must not be `NULL` and must not overlap.
+ */
XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state);
+/*!
+ * @brief Resets an @ref XXH32_state_t to begin a new hash.
+ *
+ * This function resets and seeds a state. Call it before @ref XXH32_update().
+ *
+ * @param statePtr The state struct to reset.
+ * @param seed The 32-bit seed to alter the hash result predictably.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ */
XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, XXH32_hash_t seed);
+
+/*!
+ * @brief Consumes a block of @p input to an @ref XXH32_state_t.
+ *
+ * Call this to incrementally consume blocks of data.
+ *
+ * @param statePtr The state struct to update.
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return @ref XXH_OK on success, @ref XXH_ERROR on failure.
+ */
XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length);
+
+/*!
+ * @brief Returns the calculated hash value from an @ref XXH32_state_t.
+ *
+ * @note
+ * Calling XXH32_digest() will not affect @p statePtr, so you can update,
+ * digest, and update again.
+ *
+ * @param statePtr The state struct to calculate the hash from.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return The calculated xxHash32 value from that state.
+ */
XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr);
/******* Canonical representation *******/
* canonical format.
*/
-typedef struct { unsigned char digest[4]; } XXH32_canonical_t;
+/*!
+ * @brief Canonical (big endian) representation of @ref XXH32_hash_t.
+ */
+typedef struct {
+ unsigned char digest[4]; /*!< Hash bytes, big endian */
+} XXH32_canonical_t;
+
+/*!
+ * @brief Converts an @ref XXH32_hash_t to a big endian @ref XXH32_canonical_t.
+ *
+ * @param dst The @ref XXH32_canonical_t pointer to be stored to.
+ * @param hash The @ref XXH32_hash_t to be converted.
+ *
+ * @pre
+ * @p dst must not be `NULL`.
+ */
XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash);
+
+/*!
+ * @brief Converts an @ref XXH32_canonical_t to a native @ref XXH32_hash_t.
+ *
+ * @param src The @ref XXH32_canonical_t to convert.
+ *
+ * @pre
+ * @p src must not be `NULL`.
+ *
+ * @return The converted hash.
+ */
XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src);
+#ifdef __has_attribute
+# define XXH_HAS_ATTRIBUTE(x) __has_attribute(x)
+#else
+# define XXH_HAS_ATTRIBUTE(x) 0
+#endif
+
+/* C-language Attributes are added in C23. */
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ > 201710L) && defined(__has_c_attribute)
+# define XXH_HAS_C_ATTRIBUTE(x) __has_c_attribute(x)
+#else
+# define XXH_HAS_C_ATTRIBUTE(x) 0
+#endif
+
+#if defined(__cplusplus) && defined(__has_cpp_attribute)
+# define XXH_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
+#else
+# define XXH_HAS_CPP_ATTRIBUTE(x) 0
+#endif
+
+/*
+Define XXH_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute
+introduced in CPP17 and C23.
+CPP17 : https://en.cppreference.com/w/cpp/language/attributes/fallthrough
+C23 : https://en.cppreference.com/w/c/language/attributes/fallthrough
+*/
+#if XXH_HAS_C_ATTRIBUTE(x)
+# define XXH_FALLTHROUGH [[fallthrough]]
+#elif XXH_HAS_CPP_ATTRIBUTE(x)
+# define XXH_FALLTHROUGH [[fallthrough]]
+#elif XXH_HAS_ATTRIBUTE(__fallthrough__)
+# define XXH_FALLTHROUGH __attribute__ ((fallthrough))
+#else
+# define XXH_FALLTHROUGH
+#endif
+
+/*!
+ * @}
+ * @ingroup public
+ * @{
+ */
+
#ifndef XXH_NO_LONG_LONG
/*-**********************************************************************
* 64-bit hash
************************************************************************/
-#if !defined (__VMS) \
+#if defined(XXH_DOXYGEN) /* don't include <stdint.h> */
+/*!
+ * @brief An unsigned 64-bit integer.
+ *
+ * Not necessarily defined to `uint64_t` but functionally equivalent.
+ */
+typedef uint64_t XXH64_hash_t;
+#elif !defined (__VMS) \
&& (defined (__cplusplus) \
|| (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
-# include <stdint.h>
- typedef uint64_t XXH64_hash_t;
+# include <stdint.h>
+ typedef uint64_t XXH64_hash_t;
#else
- /* the following type must have a width of 64-bit */
- typedef unsigned long long XXH64_hash_t;
+# include <limits.h>
+# if defined(__LP64__) && ULONG_MAX == 0xFFFFFFFFFFFFFFFFULL
+ /* LP64 ABI says uint64_t is unsigned long */
+ typedef unsigned long XXH64_hash_t;
+# else
+ /* the following type must have a width of 64-bit */
+ typedef unsigned long long XXH64_hash_t;
+# endif
#endif
/*!
- * XXH64():
- * Returns the 64-bit hash of sequence of length @length stored at memory
- * address @input.
- * @seed can be used to alter the result predictably.
+ * @}
+ *
+ * @defgroup xxh64_family XXH64 family
+ * @ingroup public
+ * @{
+ * Contains functions used in the classic 64-bit xxHash algorithm.
+ *
+ * @note
+ * XXH3 provides competitive speed for both 32-bit and 64-bit systems,
+ * and offers true 64/128 bit hash results.
+ * It provides better speed for systems with vector processing capabilities.
+ */
+
+
+/*!
+ * @brief Calculates the 64-bit hash of @p input using xxHash64.
*
* This function usually runs faster on 64-bit systems, but slower on 32-bit
* systems (see benchmark).
*
- * Note: XXH3 provides competitive speed for both 32-bit and 64-bit systems,
- * and offers true 64/128 bit hash results. It provides a superior level of
- * dispersion, and greatly reduces the risks of collisions.
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ * @param seed The 64-bit seed to alter the hash's output predictably.
+ *
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return The calculated 64-bit hash.
+ *
+ * @see
+ * XXH32(), XXH3_64bits_withSeed(), XXH3_128bits_withSeed(), XXH128():
+ * Direct equivalents for the other variants of xxHash.
+ * @see
+ * XXH64_createState(), XXH64_update(), XXH64_digest(): Streaming version.
*/
-XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t length, XXH64_hash_t seed);
+XXH_PUBLIC_API XXH64_hash_t XXH64(const void* input, size_t length, XXH64_hash_t seed);
/******* Streaming *******/
+/*!
+ * @brief The opaque state struct for the XXH64 streaming API.
+ *
+ * @see XXH64_state_s for details.
+ */
typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */
XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void);
XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr);
XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash);
XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src);
-
-/*-**********************************************************************
-* XXH3 64-bit variant
-************************************************************************/
-
-/* ************************************************************************
- * XXH3 is a new hash algorithm featuring:
+/*!
+ * @}
+ * ************************************************************************
+ * @defgroup xxh3_family XXH3 family
+ * @ingroup public
+ * @{
+ *
+ * XXH3 is a more recent hash algorithm featuring:
* - Improved speed for both small and large inputs
* - True 64-bit and 128-bit outputs
* - SIMD acceleration
*
* https://fastcompression.blogspot.com/2019/03/presenting-xxh3.html
*
- * In general, expect XXH3 to run about ~2x faster on large inputs and >3x
- * faster on small ones compared to XXH64, though exact differences depend on
- * the platform.
- *
- * The algorithm is portable: Like XXH32 and XXH64, it generates the same hash
- * on all platforms.
- *
- * It benefits greatly from SIMD and 64-bit arithmetic, but does not require it.
+ * Compared to XXH64, expect XXH3 to run approximately
+ * ~2x faster on large inputs and >3x faster on small ones,
+ * exact differences vary depending on platform.
*
- * Almost all 32-bit and 64-bit targets that can run XXH32 smoothly can run
- * XXH3 at competitive speeds, even if XXH64 runs slowly. Further details are
- * explained in the implementation.
+ * XXH3's speed benefits greatly from SIMD and 64-bit arithmetic,
+ * but does not require it.
+ * Any 32-bit and 64-bit targets that can run XXH32 smoothly
+ * can run XXH3 at competitive speeds, even without vector support.
+ * Further details are explained in the implementation.
*
* Optimized implementations are provided for AVX512, AVX2, SSE2, NEON, POWER8,
- * ZVector and scalar targets. This can be controlled with the XXH_VECTOR macro.
+ * ZVector and scalar targets. This can be controlled via the XXH_VECTOR macro.
+ *
+ * XXH3 implementation is portable:
+ * it has a generic C90 formulation that can be compiled on any platform,
+ * all implementations generage exactly the same hash value on all platforms.
+ * Starting from v0.8.0, it's also labelled "stable", meaning that
+ * any future version will also generate the same hash value.
*
* XXH3 offers 2 variants, _64bits and _128bits.
- * When only 64 bits are needed, prefer calling the _64bits variant, as it
- * reduces the amount of mixing, resulting in faster speed on small inputs.
*
+ * When only 64 bits are needed, prefer invoking the _64bits variant, as it
+ * reduces the amount of mixing, resulting in faster speed on small inputs.
* It's also generally simpler to manipulate a scalar return type than a struct.
*
- * The 128-bit version adds additional strength, but it is slightly slower.
- *
- * The XXH3 algorithm is still in development.
- * The results it produces may still change in future versions.
- *
- * Results produced by v0.7.x are not comparable with results from v0.7.y.
- * However, the API is completely stable, and it can safely be used for
- * ephemeral data (local sessions).
- *
- * Avoid storing values in long-term storage until the algorithm is finalized.
- * XXH3's return values will be officially finalized upon reaching v0.8.0.
- *
- * After which, return values of XXH3 and XXH128 will no longer change in
- * future versions.
- *
* The API supports one-shot hashing, streaming mode, and custom secrets.
*/
+/*-**********************************************************************
+* XXH3 64-bit variant
+************************************************************************/
+
/* XXH3_64bits():
* default 64-bit variant, using default secret and default seed of 0.
* It's the fastest variant. */
*/
XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed(const void* data, size_t len, XXH64_hash_t seed);
+/*!
+ * The bare minimum size for a custom secret.
+ *
+ * @see
+ * XXH3_64bits_withSecret(), XXH3_64bits_reset_withSecret(),
+ * XXH3_128bits_withSecret(), XXH3_128bits_reset_withSecret().
+ */
+#define XXH3_SECRET_SIZE_MIN 136
+
/*
* XXH3_64bits_withSecret():
* It's possible to provide any blob of bytes as a "secret" to generate the hash.
* This makes it more difficult for an external actor to prepare an intentional collision.
* The main condition is that secretSize *must* be large enough (>= XXH3_SECRET_SIZE_MIN).
- * However, the quality of produced hash values depends on secret's entropy.
- * Technically, the secret must look like a bunch of random bytes.
+ * However, the quality of the secret impacts the dispersion of the hash algorithm.
+ * Therefore, the secret _must_ look like a bunch of random bytes.
* Avoid "trivial" or structured data such as repeated sequences or a text document.
- * Whenever unsure about the "randomness" of the blob of bytes,
- * consider relabelling it as a "custom seed" instead,
- * and employ "XXH3_generateSecret()" (see below)
- * to generate a high entropy secret derived from the custom seed.
+ * Whenever in doubt about the "randomness" of the blob of bytes,
+ * consider employing "XXH3_generateSecret()" instead (see below).
+ * It will generate a proper high entropy secret derived from the blob of bytes.
+ * Another advantage of using XXH3_generateSecret() is that
+ * it guarantees that all bits within the initial blob of bytes
+ * will impact every bit of the output.
+ * This is not necessarily the case when using the blob of bytes directly
+ * because, when hashing _small_ inputs, only a portion of the secret is employed.
*/
-#define XXH3_SECRET_SIZE_MIN 136
XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecret(const void* data, size_t len, const void* secret, size_t secretSize);
* As a consequence, streaming is slower than one-shot hashing.
* For better performance, prefer one-shot functions whenever applicable.
*/
+
+/*!
+ * @brief The state struct for the XXH3 streaming API.
+ *
+ * @see XXH3_state_s for details.
+ */
typedef struct XXH3_state_s XXH3_state_t;
XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void);
XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr);
* XXH3 128-bit variant
************************************************************************/
+/*!
+ * @brief The return value from 128-bit hashes.
+ *
+ * Stored in little endian order, although the fields themselves are in native
+ * endianness.
+ */
typedef struct {
- XXH64_hash_t low64;
- XXH64_hash_t high64;
+ XXH64_hash_t low64; /*!< `value & 0xFFFFFFFFFFFFFFFF` */
+ XXH64_hash_t high64; /*!< `value >> 64` */
} XXH128_hash_t;
XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(const void* data, size_t len);
#endif /* XXH_NO_LONG_LONG */
+/*!
+ * @}
+ */
#endif /* XXHASH_H_5627135585666179 */
* Never **ever** access their members directly.
*/
+/*!
+ * @internal
+ * @brief Structure for XXH32 streaming API.
+ *
+ * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY,
+ * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. Otherwise it is
+ * an opaque type. This allows fields to safely be changed.
+ *
+ * Typedef'd to @ref XXH32_state_t.
+ * Do not access the members of this struct directly.
+ * @see XXH64_state_s, XXH3_state_s
+ */
struct XXH32_state_s {
- XXH32_hash_t total_len_32;
- XXH32_hash_t large_len;
- XXH32_hash_t v1;
- XXH32_hash_t v2;
- XXH32_hash_t v3;
- XXH32_hash_t v4;
- XXH32_hash_t mem32[4];
- XXH32_hash_t memsize;
- XXH32_hash_t reserved; /* never read nor write, might be removed in a future version */
+ XXH32_hash_t total_len_32; /*!< Total length hashed, modulo 2^32 */
+ XXH32_hash_t large_len; /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */
+ XXH32_hash_t v[4]; /*!< Accumulator lanes */
+ XXH32_hash_t mem32[4]; /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */
+ XXH32_hash_t memsize; /*!< Amount of data in @ref mem32 */
+ XXH32_hash_t reserved; /*!< Reserved field. Do not read or write to it, it may be removed. */
}; /* typedef'd to XXH32_state_t */
#ifndef XXH_NO_LONG_LONG /* defined when there is no 64-bit support */
+/*!
+ * @internal
+ * @brief Structure for XXH64 streaming API.
+ *
+ * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY,
+ * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. Otherwise it is
+ * an opaque type. This allows fields to safely be changed.
+ *
+ * Typedef'd to @ref XXH64_state_t.
+ * Do not access the members of this struct directly.
+ * @see XXH32_state_s, XXH3_state_s
+ */
struct XXH64_state_s {
- XXH64_hash_t total_len;
- XXH64_hash_t v1;
- XXH64_hash_t v2;
- XXH64_hash_t v3;
- XXH64_hash_t v4;
- XXH64_hash_t mem64[4];
- XXH32_hash_t memsize;
- XXH32_hash_t reserved32; /* required for padding anyway */
- XXH64_hash_t reserved64; /* never read nor write, might be removed in a future version */
+ XXH64_hash_t total_len; /*!< Total length hashed. This is always 64-bit. */
+ XXH64_hash_t v[4]; /*!< Accumulator lanes */
+ XXH64_hash_t mem64[4]; /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */
+ XXH32_hash_t memsize; /*!< Amount of data in @ref mem64 */
+ XXH32_hash_t reserved32; /*!< Reserved field, needed for padding anyways*/
+ XXH64_hash_t reserved64; /*!< Reserved field. Do not read or write to it, it may be removed. */
}; /* typedef'd to XXH64_state_t */
-#if defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11+ */
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* >= C11 */
# include <stdalign.h>
# define XXH_ALIGN(n) alignas(n)
+#elif defined(__cplusplus) && (__cplusplus >= 201103L) /* >= C++11 */
+/* In C++ alignas() is a keyword */
+# define XXH_ALIGN(n) alignas(n)
#elif defined(__GNUC__)
# define XXH_ALIGN(n) __attribute__ ((aligned(n)))
#elif defined(_MSC_VER)
/* Old GCC versions only accept the attribute after the type in structures. */
#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) /* C11+ */ \
+ && ! (defined(__cplusplus) && (__cplusplus >= 201103L)) /* >= C++11 */ \
&& defined(__GNUC__)
# define XXH_ALIGN_MEMBER(align, type) type XXH_ALIGN(align)
#else
# define XXH_ALIGN_MEMBER(align, type) XXH_ALIGN(align) type
#endif
+/*!
+ * @brief The size of the internal XXH3 buffer.
+ *
+ * This is the optimal update size for incremental hashing.
+ *
+ * @see XXH3_64b_update(), XXH3_128b_update().
+ */
#define XXH3_INTERNALBUFFER_SIZE 256
+
+/*!
+ * @brief Default size of the secret buffer (and @ref XXH3_kSecret).
+ *
+ * This is the size used in @ref XXH3_kSecret and the seeded functions.
+ *
+ * Not to be confused with @ref XXH3_SECRET_SIZE_MIN.
+ */
#define XXH3_SECRET_DEFAULT_SIZE 192
+
+/*!
+ * @internal
+ * @brief Structure for XXH3 streaming API.
+ *
+ * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY,
+ * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined.
+ * Otherwise it is an opaque type.
+ * Never use this definition in combination with dynamic library.
+ * This allows fields to safely be changed in the future.
+ *
+ * @note ** This structure has a strict alignment requirement of 64 bytes!! **
+ * Do not allocate this with `malloc()` or `new`,
+ * it will not be sufficiently aligned.
+ * Use @ref XXH3_createState() and @ref XXH3_freeState(), or stack allocation.
+ *
+ * Typedef'd to @ref XXH3_state_t.
+ * Do never access the members of this struct directly.
+ *
+ * @see XXH3_INITSTATE() for stack initialization.
+ * @see XXH3_createState(), XXH3_freeState().
+ * @see XXH32_state_s, XXH64_state_s
+ */
struct XXH3_state_s {
XXH_ALIGN_MEMBER(64, XXH64_hash_t acc[8]);
- /* used to store a custom secret generated from a seed */
+ /*!< The 8 accumulators. Similar to `vN` in @ref XXH32_state_s::v1 and @ref XXH64_state_s */
XXH_ALIGN_MEMBER(64, unsigned char customSecret[XXH3_SECRET_DEFAULT_SIZE]);
+ /*!< Used to store a custom secret generated from a seed. */
XXH_ALIGN_MEMBER(64, unsigned char buffer[XXH3_INTERNALBUFFER_SIZE]);
+ /*!< The internal buffer. @see XXH32_state_s::mem32 */
XXH32_hash_t bufferedSize;
- XXH32_hash_t reserved32;
+ /*!< The amount of memory in @ref buffer, @see XXH32_state_s::memsize */
+ XXH32_hash_t useSeed;
+ /*!< Reserved field. Needed for padding on 64-bit. */
size_t nbStripesSoFar;
+ /*!< Number or stripes processed. */
XXH64_hash_t totalLen;
+ /*!< Total length hashed. 64-bit even on 32-bit targets. */
size_t nbStripesPerBlock;
+ /*!< Number of stripes per block. */
size_t secretLimit;
+ /*!< Size of @ref customSecret or @ref extSecret */
XXH64_hash_t seed;
+ /*!< Seed for _withSeed variants. Must be zero otherwise, @see XXH3_INITSTATE() */
XXH64_hash_t reserved64;
- const unsigned char* extSecret; /* reference to external secret;
- * if == NULL, use .customSecret instead */
+ /*!< Reserved field. */
+ const unsigned char* extSecret;
+ /*!< Reference to an external secret for the _withSecret variants, NULL
+ * for other variants. */
/* note: there may be some padding at the end due to alignment on 64 bytes */
}; /* typedef'd to XXH3_state_t */
#undef XXH_ALIGN_MEMBER
-/* When the XXH3_state_t structure is merely emplaced on stack,
+/*!
+ * @brief Initializes a stack-allocated `XXH3_state_s`.
+ *
+ * When the @ref XXH3_state_t structure is merely emplaced on stack,
* it should be initialized with XXH3_INITSTATE() or a memset()
* in case its first reset uses XXH3_NNbits_reset_withSeed().
* This init can be omitted if the first reset uses default or _withSecret mode.
#define XXH3_INITSTATE(XXH3_state_ptr) { (XXH3_state_ptr)->seed = 0; }
+/* XXH128() :
+ * simple alias to pre-selected XXH3_128bits variant
+ */
+XXH_PUBLIC_API XXH128_hash_t XXH128(const void* data, size_t len, XXH64_hash_t seed);
+
+
/* === Experimental API === */
/* Symbols defined below must be considered tied to a specific library version. */
* as it becomes much more difficult for an external actor to guess how to impact the calculation logic.
*
* The function accepts as input a custom seed of any length and any content,
- * and derives from it a high-entropy secret of length XXH3_SECRET_DEFAULT_SIZE
- * into an already allocated buffer secretBuffer.
- * The generated secret is _always_ XXH_SECRET_DEFAULT_SIZE bytes long.
+ * and derives from it a high-entropy secret of length @secretSize
+ * into an already allocated buffer @secretBuffer.
+ * @secretSize must be >= XXH3_SECRET_SIZE_MIN
*
* The generated secret can then be used with any `*_withSecret()` variant.
* Functions `XXH3_128bits_withSecret()`, `XXH3_64bits_withSecret()`,
* `XXH3_128bits_reset_withSecret()` and `XXH3_64bits_reset_withSecret()`
* are part of this list. They all accept a `secret` parameter
- * which must be very long for implementation reasons (>= XXH3_SECRET_SIZE_MIN)
+ * which must be large enough for implementation reasons (>= XXH3_SECRET_SIZE_MIN)
* _and_ feature very high entropy (consist of random-looking bytes).
* These conditions can be a high bar to meet, so
- * this function can be used to generate a secret of proper quality.
+ * XXH3_generateSecret() can be employed to ensure proper quality.
*
* customSeed can be anything. It can have any size, even small ones,
- * and its content can be anything, even stupidly "low entropy" source such as a bunch of zeroes.
- * The resulting `secret` will nonetheless provide all expected qualities.
+ * and its content can be anything, even "poor entropy" sources such as a bunch of zeroes.
+ * The resulting `secret` will nonetheless provide all required qualities.
*
- * Supplying NULL as the customSeed copies the default secret into `secretBuffer`.
* When customSeedSize > 0, supplying NULL as customSeed is undefined behavior.
*/
-XXH_PUBLIC_API void XXH3_generateSecret(void* secretBuffer, const void* customSeed, size_t customSeedSize);
+XXH_PUBLIC_API XXH_errorcode XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSeed, size_t customSeedSize);
-/* simple short-cut to pre-selected XXH3_128bits variant */
-XXH_PUBLIC_API XXH128_hash_t XXH128(const void* data, size_t len, XXH64_hash_t seed);
+/*
+ * XXH3_generateSecret_fromSeed():
+ *
+ * Generate the same secret as the _withSeed() variants.
+ *
+ * The resulting secret has a length of XXH3_SECRET_DEFAULT_SIZE (necessarily).
+ * @secretBuffer must be already allocated, of size at least XXH3_SECRET_DEFAULT_SIZE bytes.
+ *
+ * The generated secret can be used in combination with
+ *`*_withSecret()` and `_withSecretandSeed()` variants.
+ * This generator is notably useful in combination with `_withSecretandSeed()`,
+ * as a way to emulate a faster `_withSeed()` variant.
+ */
+XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed);
+/*
+ * *_withSecretandSeed() :
+ * These variants generate hash values using either
+ * @seed for "short" keys (< XXH3_MIDSIZE_MAX = 240 bytes)
+ * or @secret for "large" keys (>= XXH3_MIDSIZE_MAX).
+ *
+ * This generally benefits speed, compared to `_withSeed()` or `_withSecret()`.
+ * `_withSeed()` has to generate the secret on the fly for "large" keys.
+ * It's fast, but can be perceptible for "not so large" keys (< 1 KB).
+ * `_withSecret()` has to generate the masks on the fly for "small" keys,
+ * which requires more instructions than _withSeed() variants.
+ * Therefore, _withSecretandSeed variant combines the best of both worlds.
+ *
+ * When @secret has been generated by XXH3_generateSecret_fromSeed(),
+ * this variant produces *exactly* the same results as `_withSeed()` variant,
+ * hence offering only a pure speed benefit on "large" input,
+ * by skipping the need to regenerate the secret for every large input.
+ *
+ * Another usage scenario is to hash the secret to a 64-bit hash value,
+ * for example with XXH3_64bits(), which then becomes the seed,
+ * and then employ both the seed and the secret in _withSecretandSeed().
+ * On top of speed, an added benefit is that each bit in the secret
+ * has a 50% chance to swap each bit in the output,
+ * via its impact to the seed.
+ * This is not guaranteed when using the secret directly in "small data" scenarios,
+ * because only portions of the secret are employed for small data.
+ */
+XXH_PUBLIC_API XXH64_hash_t
+XXH3_64bits_withSecretandSeed(const void* data, size_t len,
+ const void* secret, size_t secretSize,
+ XXH64_hash_t seed);
-#endif /* XXH_NO_LONG_LONG */
+XXH_PUBLIC_API XXH128_hash_t
+XXH3_128bits_withSecretandSeed(const void* data, size_t len,
+ const void* secret, size_t secretSize,
+ XXH64_hash_t seed64);
+
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
+ const void* secret, size_t secretSize,
+ XXH64_hash_t seed64);
+
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr,
+ const void* secret, size_t secretSize,
+ XXH64_hash_t seed64);
+#endif /* XXH_NO_LONG_LONG */
#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)
# define XXH_IMPLEMENTATION
#endif
/* *************************************
* Tuning parameters
***************************************/
+
+/*!
+ * @defgroup tuning Tuning parameters
+ * @{
+ *
+ * Various macros to control xxHash's behavior.
+ */
+#ifdef XXH_DOXYGEN
/*!
- * XXH_FORCE_MEMORY_ACCESS:
+ * @brief Define this to disable 64-bit code.
+ *
+ * Useful if only using the @ref xxh32_family and you have a strict C90 compiler.
+ */
+# define XXH_NO_LONG_LONG
+# undef XXH_NO_LONG_LONG /* don't actually */
+/*!
+ * @brief Controls how unaligned memory is accessed.
+ *
* By default, access to unaligned memory is controlled by `memcpy()`, which is
* safe and portable.
*
*
* The below switch allow selection of a different access method
* in the search for improved performance.
- * Method 0 (default):
- * Use `memcpy()`. Safe and portable. Default.
- * Method 1:
- * `__attribute__((packed))` statement. It depends on compiler extensions
- * and is therefore not portable.
- * This method is safe if your compiler supports it, and *generally* as
- * fast or faster than `memcpy`.
- * Method 2:
- * Direct access via cast. This method doesn't depend on the compiler but
- * violates the C standard.
- * It can generate buggy code on targets which do not support unaligned
- * memory accesses.
- * But in some circumstances, it's the only known way to get the most
- * performance (example: GCC + ARMv6)
- * Method 3:
- * Byteshift. This can generate the best code on old compilers which don't
+ *
+ * @par Possible options:
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=0` (default): `memcpy`
+ * @par
+ * Use `memcpy()`. Safe and portable. Note that most modern compilers will
+ * eliminate the function call and treat it as an unaligned access.
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=1`: `__attribute__((packed))`
+ * @par
+ * Depends on compiler extensions and is therefore not portable.
+ * This method is safe _if_ your compiler supports it,
+ * and *generally* as fast or faster than `memcpy`.
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=2`: Direct cast
+ * @par
+ * Casts directly and dereferences. This method doesn't depend on the
+ * compiler, but it violates the C standard as it directly dereferences an
+ * unaligned pointer. It can generate buggy code on targets which do not
+ * support unaligned memory accesses, but in some circumstances, it's the
+ * only known way to get the most performance.
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=3`: Byteshift
+ * @par
+ * Also portable. This can generate the best code on old compilers which don't
* inline small `memcpy()` calls, and it might also be faster on big-endian
- * systems which lack a native byteswap instruction.
- * See https://stackoverflow.com/a/32095106/646947 for details.
- * Prefer these methods in priority order (0 > 1 > 2 > 3)
+ * systems which lack a native byteswap instruction. However, some compilers
+ * will emit literal byteshifts even if the target supports unaligned access.
+ * .
+ *
+ * @warning
+ * Methods 1 and 2 rely on implementation-defined behavior. Use these with
+ * care, as what works on one compiler/platform/optimization level may cause
+ * another to read garbage data or even crash.
+ *
+ * See http://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html for details.
+ *
+ * Prefer these methods in priority order (0 > 3 > 1 > 2)
*/
-#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */
-# if !defined(__clang__) && defined(__GNUC__) && defined(__ARM_FEATURE_UNALIGNED) && defined(__ARM_ARCH) && (__ARM_ARCH == 6)
-# define XXH_FORCE_MEMORY_ACCESS 2
-# elif !defined(__clang__) && ((defined(__INTEL_COMPILER) && !defined(_WIN32)) || \
- (defined(__GNUC__) && (defined(__ARM_ARCH) && __ARM_ARCH >= 7)))
-# define XXH_FORCE_MEMORY_ACCESS 1
-# endif
-#endif
-
-/*!
- * XXH_ACCEPT_NULL_INPUT_POINTER:
- * If the input pointer is NULL, xxHash's default behavior is to dereference it,
- * triggering a segfault.
- * When this macro is enabled, xxHash actively checks the input for a null pointer.
- * If it is, the result for null input pointers is the same as a zero-length input.
- */
-#ifndef XXH_ACCEPT_NULL_INPUT_POINTER /* can be defined externally */
-# define XXH_ACCEPT_NULL_INPUT_POINTER 0
-#endif
+# define XXH_FORCE_MEMORY_ACCESS 0
/*!
- * XXH_FORCE_ALIGN_CHECK:
- * This is an important performance trick
- * for architectures without decent unaligned memory access performance.
- * It checks for input alignment, and when conditions are met,
- * uses a "fast path" employing direct 32-bit/64-bit read,
- * resulting in _dramatically faster_ read speed.
+ * @def XXH_FORCE_ALIGN_CHECK
+ * @brief If defined to non-zero, adds a special path for aligned inputs (XXH32()
+ * and XXH64() only).
*
- * The check costs one initial branch per hash, which is generally negligible, but not zero.
- * Moreover, it's not useful to generate binary for an additional code path
- * if memory access uses same instruction for both aligned and unaligned adresses.
+ * This is an important performance trick for architectures without decent
+ * unaligned memory access performance.
+ *
+ * It checks for input alignment, and when conditions are met, uses a "fast
+ * path" employing direct 32-bit/64-bit reads, resulting in _dramatically
+ * faster_ read speed.
+ *
+ * The check costs one initial branch per hash, which is generally negligible,
+ * but not zero.
+ *
+ * Moreover, it's not useful to generate an additional code path if memory
+ * access uses the same instruction for both aligned and unaligned
+ * addresses (e.g. x86 and aarch64).
*
* In these cases, the alignment check can be removed by setting this macro to 0.
* Then the code will always use unaligned memory access.
*
* This option does not affect XXH3 (only XXH32 and XXH64).
*/
-#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */
-# if defined(__i386) || defined(__x86_64__) || defined(__aarch64__) \
- || defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64) /* visual */
-# define XXH_FORCE_ALIGN_CHECK 0
-# else
-# define XXH_FORCE_ALIGN_CHECK 1
-# endif
-#endif
+# define XXH_FORCE_ALIGN_CHECK 0
/*!
- * XXH_NO_INLINE_HINTS:
+ * @def XXH_NO_INLINE_HINTS
+ * @brief When non-zero, sets all functions to `static`.
*
* By default, xxHash tries to force the compiler to inline almost all internal
* functions.
* When not optimizing (-O0), optimizing for size (-Os, -Oz), or using
* -fno-inline with GCC or Clang, this will automatically be defined.
*/
+# define XXH_NO_INLINE_HINTS 0
+
+/*!
+ * @def XXH32_ENDJMP
+ * @brief Whether to use a jump for `XXH32_finalize`.
+ *
+ * For performance, `XXH32_finalize` uses multiple branches in the finalizer.
+ * This is generally preferable for performance,
+ * but depending on exact architecture, a jmp may be preferable.
+ *
+ * This setting is only possibly making a difference for very small inputs.
+ */
+# define XXH32_ENDJMP 0
+
+/*!
+ * @internal
+ * @brief Redefines old internal names.
+ *
+ * For compatibility with code that uses xxHash's internals before the names
+ * were changed to improve namespacing. There is no other reason to use this.
+ */
+# define XXH_OLD_NAMES
+# undef XXH_OLD_NAMES /* don't actually use, it is ugly. */
+#endif /* XXH_DOXYGEN */
+/*!
+ * @}
+ */
+
+#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */
+ /* prefer __packed__ structures (method 1) for gcc on armv7+ and mips */
+# if !defined(__clang__) && \
+( \
+ (defined(__INTEL_COMPILER) && !defined(_WIN32)) || \
+ ( \
+ defined(__GNUC__) && ( \
+ (defined(__ARM_ARCH) && __ARM_ARCH >= 7) || \
+ ( \
+ defined(__mips__) && \
+ (__mips <= 5 || __mips_isa_rev < 6) && \
+ (!defined(__mips16) || defined(__mips_mips16e2)) \
+ ) \
+ ) \
+ ) \
+)
+# define XXH_FORCE_MEMORY_ACCESS 1
+# endif
+#endif
+
+#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */
+# if defined(__i386) || defined(__x86_64__) || defined(__aarch64__) \
+ || defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64) /* visual */
+# define XXH_FORCE_ALIGN_CHECK 0
+# else
+# define XXH_FORCE_ALIGN_CHECK 1
+# endif
+#endif
+
#ifndef XXH_NO_INLINE_HINTS
# if defined(__OPTIMIZE_SIZE__) /* -Os, -Oz */ \
|| defined(__NO_INLINE__) /* -O0, -fno-inline */
# endif
#endif
+#ifndef XXH32_ENDJMP
+/* generally preferable for performance */
+# define XXH32_ENDJMP 0
+#endif
+
/*!
- * XXH_REROLL:
- * Whether to reroll XXH32_finalize, and XXH64_finalize,
- * instead of using an unrolled jump table/if statement loop.
- *
- * This is automatically defined on -Os/-Oz on GCC and Clang.
+ * @defgroup impl Implementation
+ * @{
*/
-#ifndef XXH_REROLL
-# if defined(__OPTIMIZE_SIZE__)
-# define XXH_REROLL 1
-# else
-# define XXH_REROLL 0
-# endif
-#endif
/* *************************************
* Includes & Memory related functions
***************************************/
-/*!
+/*
* Modify the local functions below should you wish to use
* different memory routines for malloc() and free()
*/
#include <stdlib.h>
+/*!
+ * @internal
+ * @brief Modify this function to use a different routine than malloc().
+ */
static void* XXH_malloc(size_t s) { return malloc(s); }
+
+/*!
+ * @internal
+ * @brief Modify this function to use a different routine than free().
+ */
static void XXH_free(void* p) { free(p); }
-/*! and for memcpy() */
#include <string.h>
+
+/*!
+ * @internal
+ * @brief Modify this function to use a different routine than memcpy().
+ */
static void* XXH_memcpy(void* dest, const void* src, size_t size)
{
return memcpy(dest,src,size);
#endif
#if XXH_NO_INLINE_HINTS /* disable inlining hints */
-# if defined(__GNUC__)
+# if defined(__GNUC__) || defined(__clang__)
# define XXH_FORCE_INLINE static __attribute__((unused))
# else
# define XXH_FORCE_INLINE static
# endif
# define XXH_NO_INLINE static
/* enable inlining hints */
+#elif defined(__GNUC__) || defined(__clang__)
+# define XXH_FORCE_INLINE static __inline__ __attribute__((always_inline, unused))
+# define XXH_NO_INLINE static __attribute__((noinline))
#elif defined(_MSC_VER) /* Visual Studio */
# define XXH_FORCE_INLINE static __forceinline
# define XXH_NO_INLINE static __declspec(noinline)
-#elif defined(__GNUC__)
-# define XXH_FORCE_INLINE static __inline__ __attribute__((always_inline, unused))
-# define XXH_NO_INLINE static __attribute__((noinline))
#elif defined (__cplusplus) \
|| (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) /* C99 */
# define XXH_FORCE_INLINE static inline
/* *************************************
* Debug
***************************************/
-/*
+/*!
+ * @ingroup tuning
+ * @def XXH_DEBUGLEVEL
+ * @brief Sets the debugging level.
+ *
* XXH_DEBUGLEVEL is expected to be defined externally, typically via the
* compiler's command line options. The value must be a number.
*/
#endif
/* note: use after variable declarations */
-#define XXH_STATIC_ASSERT(c) do { enum { XXH_sa = 1/(int)(!!(c)) }; } while (0)
+#ifndef XXH_STATIC_ASSERT
+# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */
+# include <assert.h>
+# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0)
+# elif defined(__cplusplus) && (__cplusplus >= 201103L) /* C++11 */
+# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0)
+# else
+# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { struct xxh_sa { char x[(c) ? 1 : -1]; }; } while(0)
+# endif
+# define XXH_STATIC_ASSERT(c) XXH_STATIC_ASSERT_WITH_MESSAGE((c),#c)
+#endif
+/*!
+ * @internal
+ * @def XXH_COMPILER_GUARD(var)
+ * @brief Used to prevent unwanted optimizations for @p var.
+ *
+ * It uses an empty GCC inline assembly statement with a register constraint
+ * which forces @p var into a general purpose register (eg eax, ebx, ecx
+ * on x86) and marks it as modified.
+ *
+ * This is used in a few places to avoid unwanted autovectorization (e.g.
+ * XXH32_round()). All vectorization we want is explicit via intrinsics,
+ * and _usually_ isn't wanted elsewhere.
+ *
+ * We also use it to prevent unwanted constant folding for AArch64 in
+ * XXH3_initCustomSecret_scalar().
+ */
+#if defined(__GNUC__) || defined(__clang__)
+# define XXH_COMPILER_GUARD(var) __asm__ __volatile__("" : "+r" (var))
+#else
+# define XXH_COMPILER_GUARD(var) ((void)0)
+#endif
/* *************************************
* Basic Types
/* *** Memory access *** */
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_read32(const void* ptr)
+ * @brief Reads an unaligned 32-bit integer from @p ptr in native endianness.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ *
+ * @param ptr The pointer to read from.
+ * @return The 32-bit native endian integer from the bytes at @p ptr.
+ */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_readLE32(const void* ptr)
+ * @brief Reads an unaligned 32-bit little endian integer from @p ptr.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ *
+ * @param ptr The pointer to read from.
+ * @return The 32-bit little endian integer from the bytes at @p ptr.
+ */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_readBE32(const void* ptr)
+ * @brief Reads an unaligned 32-bit big endian integer from @p ptr.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ *
+ * @param ptr The pointer to read from.
+ * @return The 32-bit big endian integer from the bytes at @p ptr.
+ */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_readLE32_align(const void* ptr, XXH_alignment align)
+ * @brief Like @ref XXH_readLE32(), but has an option for aligned reads.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ * Note that when @ref XXH_FORCE_ALIGN_CHECK == 0, the @p align parameter is
+ * always @ref XXH_alignment::XXH_unaligned.
+ *
+ * @param ptr The pointer to read from.
+ * @param align Whether @p ptr is aligned.
+ * @pre
+ * If @p align == @ref XXH_alignment::XXH_aligned, @p ptr must be 4 byte
+ * aligned.
+ * @return The 32-bit little endian integer from the bytes at @p ptr.
+ */
+
#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3))
/*
* Manual byteshift. Best for old compilers which don't inline memcpy.
/*
* Portable and safe solution. Generally efficient.
- * see: https://stackoverflow.com/a/32095106/646947
+ * see: http://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html
*/
static xxh_u32 XXH_read32(const void* memPtr)
{
xxh_u32 val;
- memcpy(&val, memPtr, sizeof(val));
+ XXH_memcpy(&val, memPtr, sizeof(val));
return val;
}
#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */
-/* *** Endianess *** */
-typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess;
+/* *** Endianness *** */
/*!
- * XXH_CPU_LITTLE_ENDIAN:
+ * @ingroup tuning
+ * @def XXH_CPU_LITTLE_ENDIAN
+ * @brief Whether the target is little endian.
+ *
* Defined to 1 if the target is little endian, or 0 if it is big endian.
* It can be defined externally, for example on the compiler command line.
*
- * If it is not defined, a runtime check (which is usually constant folded)
- * is used instead.
+ * If it is not defined,
+ * a runtime check (which is usually constant folded) is used instead.
+ *
+ * @note
+ * This is not necessarily defined to an integer constant.
+ *
+ * @see XXH_isLittleEndian() for the runtime check.
*/
#ifndef XXH_CPU_LITTLE_ENDIAN
/*
|| (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
# define XXH_CPU_LITTLE_ENDIAN 0
# else
-/*
- * runtime test, presumed to simplify to a constant by compiler
+/*!
+ * @internal
+ * @brief Runtime check for @ref XXH_CPU_LITTLE_ENDIAN.
+ *
+ * Most compilers will constant fold this.
*/
static int XXH_isLittleEndian(void)
{
# define XXH_HAS_BUILTIN(x) 0
#endif
+/*!
+ * @internal
+ * @def XXH_rotl32(x,r)
+ * @brief 32-bit rotate left.
+ *
+ * @param x The 32-bit integer to be rotated.
+ * @param r The number of bits to rotate.
+ * @pre
+ * @p r > 0 && @p r < 32
+ * @note
+ * @p x and @p r may be evaluated multiple times.
+ * @return The rotated result.
+ */
#if !defined(NO_CLANG_BUILTIN) && XXH_HAS_BUILTIN(__builtin_rotateleft32) \
&& XXH_HAS_BUILTIN(__builtin_rotateleft64)
# define XXH_rotl32 __builtin_rotateleft32
# define XXH_rotl64(x,r) (((x) << (r)) | ((x) >> (64 - (r))))
#endif
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_swap32(xxh_u32 x)
+ * @brief A 32-bit byteswap.
+ *
+ * @param x The 32-bit integer to byteswap.
+ * @return @p x, byteswapped.
+ */
#if defined(_MSC_VER) /* Visual Studio */
# define XXH_swap32 _byteswap_ulong
#elif XXH_GCC_VERSION >= 403
/* ***************************
* Memory reads
*****************************/
-typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment;
+
+/*!
+ * @internal
+ * @brief Enum to indicate whether a pointer is aligned.
+ */
+typedef enum {
+ XXH_aligned, /*!< Aligned */
+ XXH_unaligned /*!< Possibly unaligned */
+} XXH_alignment;
/*
* XXH_FORCE_MEMORY_ACCESS==3 is an endian-independent byteshift load.
/* *************************************
* Misc
***************************************/
+/*! @ingroup public */
XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; }
/* *******************************************************************
* 32-bit hash functions
*********************************************************************/
-static const xxh_u32 XXH_PRIME32_1 = 0x9E3779B1U; /* 0b10011110001101110111100110110001 */
-static const xxh_u32 XXH_PRIME32_2 = 0x85EBCA77U; /* 0b10000101111010111100101001110111 */
-static const xxh_u32 XXH_PRIME32_3 = 0xC2B2AE3DU; /* 0b11000010101100101010111000111101 */
-static const xxh_u32 XXH_PRIME32_4 = 0x27D4EB2FU; /* 0b00100111110101001110101100101111 */
-static const xxh_u32 XXH_PRIME32_5 = 0x165667B1U; /* 0b00010110010101100110011110110001 */
+/*!
+ * @}
+ * @defgroup xxh32_impl XXH32 implementation
+ * @ingroup impl
+ * @{
+ */
+ /* #define instead of static const, to be used as initializers */
+#define XXH_PRIME32_1 0x9E3779B1U /*!< 0b10011110001101110111100110110001 */
+#define XXH_PRIME32_2 0x85EBCA77U /*!< 0b10000101111010111100101001110111 */
+#define XXH_PRIME32_3 0xC2B2AE3DU /*!< 0b11000010101100101010111000111101 */
+#define XXH_PRIME32_4 0x27D4EB2FU /*!< 0b00100111110101001110101100101111 */
+#define XXH_PRIME32_5 0x165667B1U /*!< 0b00010110010101100110011110110001 */
#ifdef XXH_OLD_NAMES
# define PRIME32_1 XXH_PRIME32_1
# define PRIME32_5 XXH_PRIME32_5
#endif
+/*!
+ * @internal
+ * @brief Normal stripe processing routine.
+ *
+ * This shuffles the bits so that any bit from @p input impacts several bits in
+ * @p acc.
+ *
+ * @param acc The accumulator lane.
+ * @param input The stripe of input to mix.
+ * @return The mixed accumulator lane.
+ */
static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input)
{
acc += input * XXH_PRIME32_2;
acc = XXH_rotl32(acc, 13);
acc *= XXH_PRIME32_1;
-#if defined(__GNUC__) && defined(__SSE4_1__) && !defined(XXH_ENABLE_AUTOVECTORIZE)
+#if (defined(__SSE4_1__) || defined(__aarch64__)) && !defined(XXH_ENABLE_AUTOVECTORIZE)
/*
* UGLY HACK:
- * This inline assembly hack forces acc into a normal register. This is the
- * only thing that prevents GCC and Clang from autovectorizing the XXH32
- * loop (pragmas and attributes don't work for some resason) without globally
- * disabling SSE4.1.
+ * A compiler fence is the only thing that prevents GCC and Clang from
+ * autovectorizing the XXH32 loop (pragmas and attributes don't work for some
+ * reason) without globally disabling SSE4.1.
*
* The reason we want to avoid vectorization is because despite working on
* 4 integers at a time, there are multiple factors slowing XXH32 down on
* can load data, while v3 can multiply. SSE forces them to operate
* together.
*
- * How this hack works:
- * __asm__("" // Declare an assembly block but don't declare any instructions
- * : // However, as an Input/Output Operand,
- * "+r" // constrain a read/write operand (+) as a general purpose register (r).
- * (acc) // and set acc as the operand
- * );
- *
- * Because of the 'r', the compiler has promised that seed will be in a
- * general purpose register and the '+' says that it will be 'read/write',
- * so it has to assume it has changed. It is like volatile without all the
- * loads and stores.
- *
- * Since the argument has to be in a normal register (not an SSE register),
- * each time XXH32_round is called, it is impossible to vectorize.
+ * This is also enabled on AArch64, as Clang autovectorizes it incorrectly
+ * and it is pointless writing a NEON implementation that is basically the
+ * same speed as scalar for XXH32.
*/
- __asm__("" : "+r" (acc));
+ XXH_COMPILER_GUARD(acc);
#endif
return acc;
}
-/* mix all bits */
+/*!
+ * @internal
+ * @brief Mixes all bits to finalize the hash.
+ *
+ * The final mix ensures that all input bits have a chance to impact any bit in
+ * the output digest, resulting in an unbiased distribution.
+ *
+ * @param h32 The hash to avalanche.
+ * @return The avalanched hash.
+ */
static xxh_u32 XXH32_avalanche(xxh_u32 h32)
{
h32 ^= h32 >> 15;
#define XXH_get32bits(p) XXH_readLE32_align(p, align)
+/*!
+ * @internal
+ * @brief Processes the last 0-15 bytes of @p ptr.
+ *
+ * There may be up to 15 bytes remaining to consume from the input.
+ * This final stage will digest them to ensure that all input bytes are present
+ * in the final mix.
+ *
+ * @param h32 The hash to finalize.
+ * @param ptr The pointer to the remaining input.
+ * @param len The remaining length, modulo 16.
+ * @param align Whether @p ptr is aligned.
+ * @return The finalized hash.
+ */
static xxh_u32
XXH32_finalize(xxh_u32 h32, const xxh_u8* ptr, size_t len, XXH_alignment align)
{
h32 = XXH_rotl32(h32, 17) * XXH_PRIME32_4; \
} while (0)
- /* Compact rerolled version */
- if (XXH_REROLL) {
+ if (ptr==NULL) XXH_ASSERT(len == 0);
+
+ /* Compact rerolled version; generally faster */
+ if (!XXH32_ENDJMP) {
len &= 15;
while (len >= 4) {
XXH_PROCESS4;
} else {
switch(len&15) /* or switch(bEnd - p) */ {
case 12: XXH_PROCESS4;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 8: XXH_PROCESS4;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 4: XXH_PROCESS4;
return XXH32_avalanche(h32);
case 13: XXH_PROCESS4;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 9: XXH_PROCESS4;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 5: XXH_PROCESS4;
XXH_PROCESS1;
return XXH32_avalanche(h32);
case 14: XXH_PROCESS4;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 10: XXH_PROCESS4;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 6: XXH_PROCESS4;
XXH_PROCESS1;
XXH_PROCESS1;
return XXH32_avalanche(h32);
case 15: XXH_PROCESS4;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 11: XXH_PROCESS4;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 7: XXH_PROCESS4;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 3: XXH_PROCESS1;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 2: XXH_PROCESS1;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 1: XXH_PROCESS1;
- /* fallthrough */
+ XXH_FALLTHROUGH;
case 0: return XXH32_avalanche(h32);
}
XXH_ASSERT(0);
# undef XXH_PROCESS4
#endif
+/*!
+ * @internal
+ * @brief The implementation for @ref XXH32().
+ *
+ * @param input , len , seed Directly passed from @ref XXH32().
+ * @param align Whether @p input is aligned.
+ * @return The calculated hash.
+ */
XXH_FORCE_INLINE xxh_u32
XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment align)
{
- const xxh_u8* bEnd = input + len;
xxh_u32 h32;
-#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)
- if (input==NULL) {
- len=0;
- bEnd=input=(const xxh_u8*)(size_t)16;
- }
-#endif
+ if (input==NULL) XXH_ASSERT(len == 0);
if (len>=16) {
+ const xxh_u8* const bEnd = input + len;
const xxh_u8* const limit = bEnd - 15;
xxh_u32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
xxh_u32 v2 = seed + XXH_PRIME32_2;
return XXH32_finalize(h32, input, len&15, align);
}
-
+/*! @ingroup xxh32_family */
XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t len, XXH32_hash_t seed)
{
#if 0
XXH32_reset(&state, seed);
XXH32_update(&state, (const xxh_u8*)input, len);
return XXH32_digest(&state);
-
#else
-
if (XXH_FORCE_ALIGN_CHECK) {
if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */
return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_aligned);
/******* Hash streaming *******/
-
+/*!
+ * @ingroup xxh32_family
+ */
XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void)
{
return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t));
}
+/*! @ingroup xxh32_family */
XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr)
{
XXH_free(statePtr);
return XXH_OK;
}
+/*! @ingroup xxh32_family */
XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState)
{
- memcpy(dstState, srcState, sizeof(*dstState));
+ XXH_memcpy(dstState, srcState, sizeof(*dstState));
}
+/*! @ingroup xxh32_family */
XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, XXH32_hash_t seed)
{
XXH32_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */
memset(&state, 0, sizeof(state));
- state.v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
- state.v2 = seed + XXH_PRIME32_2;
- state.v3 = seed + 0;
- state.v4 = seed - XXH_PRIME32_1;
+ state.v[0] = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
+ state.v[1] = seed + XXH_PRIME32_2;
+ state.v[2] = seed + 0;
+ state.v[3] = seed - XXH_PRIME32_1;
/* do not write into reserved, planned to be removed in a future version */
- memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved));
+ XXH_memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved));
return XXH_OK;
}
+/*! @ingroup xxh32_family */
XXH_PUBLIC_API XXH_errorcode
XXH32_update(XXH32_state_t* state, const void* input, size_t len)
{
- if (input==NULL)
-#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)
+ if (input==NULL) {
+ XXH_ASSERT(len == 0);
return XXH_OK;
-#else
- return XXH_ERROR;
-#endif
+ }
{ const xxh_u8* p = (const xxh_u8*)input;
const xxh_u8* const bEnd = p + len;
if (state->memsize) { /* some data left from previous update */
XXH_memcpy((xxh_u8*)(state->mem32) + state->memsize, input, 16-state->memsize);
{ const xxh_u32* p32 = state->mem32;
- state->v1 = XXH32_round(state->v1, XXH_readLE32(p32)); p32++;
- state->v2 = XXH32_round(state->v2, XXH_readLE32(p32)); p32++;
- state->v3 = XXH32_round(state->v3, XXH_readLE32(p32)); p32++;
- state->v4 = XXH32_round(state->v4, XXH_readLE32(p32));
+ state->v[0] = XXH32_round(state->v[0], XXH_readLE32(p32)); p32++;
+ state->v[1] = XXH32_round(state->v[1], XXH_readLE32(p32)); p32++;
+ state->v[2] = XXH32_round(state->v[2], XXH_readLE32(p32)); p32++;
+ state->v[3] = XXH32_round(state->v[3], XXH_readLE32(p32));
}
p += 16-state->memsize;
state->memsize = 0;
if (p <= bEnd-16) {
const xxh_u8* const limit = bEnd - 16;
- xxh_u32 v1 = state->v1;
- xxh_u32 v2 = state->v2;
- xxh_u32 v3 = state->v3;
- xxh_u32 v4 = state->v4;
do {
- v1 = XXH32_round(v1, XXH_readLE32(p)); p+=4;
- v2 = XXH32_round(v2, XXH_readLE32(p)); p+=4;
- v3 = XXH32_round(v3, XXH_readLE32(p)); p+=4;
- v4 = XXH32_round(v4, XXH_readLE32(p)); p+=4;
+ state->v[0] = XXH32_round(state->v[0], XXH_readLE32(p)); p+=4;
+ state->v[1] = XXH32_round(state->v[1], XXH_readLE32(p)); p+=4;
+ state->v[2] = XXH32_round(state->v[2], XXH_readLE32(p)); p+=4;
+ state->v[3] = XXH32_round(state->v[3], XXH_readLE32(p)); p+=4;
} while (p<=limit);
- state->v1 = v1;
- state->v2 = v2;
- state->v3 = v3;
- state->v4 = v4;
}
if (p < bEnd) {
}
-XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* state)
+/*! @ingroup xxh32_family */
+XXH_PUBLIC_API XXH32_hash_t XXH32_digest(const XXH32_state_t* state)
{
xxh_u32 h32;
if (state->large_len) {
- h32 = XXH_rotl32(state->v1, 1)
- + XXH_rotl32(state->v2, 7)
- + XXH_rotl32(state->v3, 12)
- + XXH_rotl32(state->v4, 18);
+ h32 = XXH_rotl32(state->v[0], 1)
+ + XXH_rotl32(state->v[1], 7)
+ + XXH_rotl32(state->v[2], 12)
+ + XXH_rotl32(state->v[3], 18);
} else {
- h32 = state->v3 /* == seed */ + XXH_PRIME32_5;
+ h32 = state->v[2] /* == seed */ + XXH_PRIME32_5;
}
h32 += state->total_len_32;
/******* Canonical representation *******/
-/*
+/*!
+ * @ingroup xxh32_family
* The default return values from XXH functions are unsigned 32 and 64 bit
* integers.
*
{
XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t));
if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash);
- memcpy(dst, &hash, sizeof(*dst));
+ XXH_memcpy(dst, &hash, sizeof(*dst));
}
-
+/*! @ingroup xxh32_family */
XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src)
{
return XXH_readBE32(src);
/* *******************************************************************
* 64-bit hash functions
*********************************************************************/
-
+/*!
+ * @}
+ * @ingroup impl
+ * @{
+ */
/******* Memory access *******/
typedef XXH64_hash_t xxh_u64;
# define U64 xxh_u64
#endif
-/*!
- * XXH_REROLL_XXH64:
- * Whether to reroll the XXH64_finalize() loop.
- *
- * Just like XXH32, we can unroll the XXH64_finalize() loop. This can be a
- * performance gain on 64-bit hosts, as only one jump is required.
- *
- * However, on 32-bit hosts, because arithmetic needs to be done with two 32-bit
- * registers, and 64-bit arithmetic needs to be simulated, it isn't beneficial
- * to unroll. The code becomes ridiculously large (the largest function in the
- * binary on i386!), and rerolling it saves anywhere from 3kB to 20kB. It is
- * also slightly faster because it fits into cache better and is more likely
- * to be inlined by the compiler.
- *
- * If XXH_REROLL is defined, this is ignored and the loop is always rerolled.
- */
-#ifndef XXH_REROLL_XXH64
-# if (defined(__ILP32__) || defined(_ILP32)) /* ILP32 is often defined on 32-bit GCC family */ \
- || !(defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64) /* x86-64 */ \
- || defined(_M_ARM64) || defined(__aarch64__) || defined(__arm64__) /* aarch64 */ \
- || defined(__PPC64__) || defined(__PPC64LE__) || defined(__ppc64__) || defined(__powerpc64__) /* ppc64 */ \
- || defined(__mips64__) || defined(__mips64)) /* mips64 */ \
- || (!defined(SIZE_MAX) || SIZE_MAX < ULLONG_MAX) /* check limits */
-# define XXH_REROLL_XXH64 1
-# else
-# define XXH_REROLL_XXH64 0
-# endif
-#endif /* !defined(XXH_REROLL_XXH64) */
-
#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3))
/*
* Manual byteshift. Best for old compilers which don't inline memcpy.
#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))
/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */
-static xxh_u64 XXH_read64(const void* memPtr) { return *(const xxh_u64*) memPtr; }
+static xxh_u64 XXH_read64(const void* memPtr)
+{
+ return *(const xxh_u64*) memPtr;
+}
#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
/*
* Portable and safe solution. Generally efficient.
- * see: https://stackoverflow.com/a/32095106/646947
+ * see: http://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html
*/
static xxh_u64 XXH_read64(const void* memPtr)
{
xxh_u64 val;
- memcpy(&val, memPtr, sizeof(val));
+ XXH_memcpy(&val, memPtr, sizeof(val));
return val;
}
#elif XXH_GCC_VERSION >= 403
# define XXH_swap64 __builtin_bswap64
#else
-static xxh_u64 XXH_swap64 (xxh_u64 x)
+static xxh_u64 XXH_swap64(xxh_u64 x)
{
return ((x << 56) & 0xff00000000000000ULL) |
((x << 40) & 0x00ff000000000000ULL) |
/******* xxh64 *******/
-
-static const xxh_u64 XXH_PRIME64_1 = 0x9E3779B185EBCA87ULL; /* 0b1001111000110111011110011011000110000101111010111100101010000111 */
-static const xxh_u64 XXH_PRIME64_2 = 0xC2B2AE3D27D4EB4FULL; /* 0b1100001010110010101011100011110100100111110101001110101101001111 */
-static const xxh_u64 XXH_PRIME64_3 = 0x165667B19E3779F9ULL; /* 0b0001011001010110011001111011000110011110001101110111100111111001 */
-static const xxh_u64 XXH_PRIME64_4 = 0x85EBCA77C2B2AE63ULL; /* 0b1000010111101011110010100111011111000010101100101010111001100011 */
-static const xxh_u64 XXH_PRIME64_5 = 0x27D4EB2F165667C5ULL; /* 0b0010011111010100111010110010111100010110010101100110011111000101 */
+/*!
+ * @}
+ * @defgroup xxh64_impl XXH64 implementation
+ * @ingroup impl
+ * @{
+ */
+/* #define rather that static const, to be used as initializers */
+#define XXH_PRIME64_1 0x9E3779B185EBCA87ULL /*!< 0b1001111000110111011110011011000110000101111010111100101010000111 */
+#define XXH_PRIME64_2 0xC2B2AE3D27D4EB4FULL /*!< 0b1100001010110010101011100011110100100111110101001110101101001111 */
+#define XXH_PRIME64_3 0x165667B19E3779F9ULL /*!< 0b0001011001010110011001111011000110011110001101110111100111111001 */
+#define XXH_PRIME64_4 0x85EBCA77C2B2AE63ULL /*!< 0b1000010111101011110010100111011111000010101100101010111001100011 */
+#define XXH_PRIME64_5 0x27D4EB2F165667C5ULL /*!< 0b0010011111010100111010110010111100010110010101100110011111000101 */
#ifdef XXH_OLD_NAMES
# define PRIME64_1 XXH_PRIME64_1
static xxh_u64
XXH64_finalize(xxh_u64 h64, const xxh_u8* ptr, size_t len, XXH_alignment align)
{
-#define XXH_PROCESS1_64 do { \
- h64 ^= (*ptr++) * XXH_PRIME64_5; \
- h64 = XXH_rotl64(h64, 11) * XXH_PRIME64_1; \
-} while (0)
-
-#define XXH_PROCESS4_64 do { \
- h64 ^= (xxh_u64)(XXH_get32bits(ptr)) * XXH_PRIME64_1; \
- ptr += 4; \
- h64 = XXH_rotl64(h64, 23) * XXH_PRIME64_2 + XXH_PRIME64_3; \
-} while (0)
-
-#define XXH_PROCESS8_64 do { \
- xxh_u64 const k1 = XXH64_round(0, XXH_get64bits(ptr)); \
- ptr += 8; \
- h64 ^= k1; \
- h64 = XXH_rotl64(h64,27) * XXH_PRIME64_1 + XXH_PRIME64_4; \
-} while (0)
-
- /* Rerolled version for 32-bit targets is faster and much smaller. */
- if (XXH_REROLL || XXH_REROLL_XXH64) {
- len &= 31;
- while (len >= 8) {
- XXH_PROCESS8_64;
- len -= 8;
- }
- if (len >= 4) {
- XXH_PROCESS4_64;
- len -= 4;
- }
- while (len > 0) {
- XXH_PROCESS1_64;
- --len;
- }
- return XXH64_avalanche(h64);
- } else {
- switch(len & 31) {
- case 24: XXH_PROCESS8_64;
- /* fallthrough */
- case 16: XXH_PROCESS8_64;
- /* fallthrough */
- case 8: XXH_PROCESS8_64;
- return XXH64_avalanche(h64);
-
- case 28: XXH_PROCESS8_64;
- /* fallthrough */
- case 20: XXH_PROCESS8_64;
- /* fallthrough */
- case 12: XXH_PROCESS8_64;
- /* fallthrough */
- case 4: XXH_PROCESS4_64;
- return XXH64_avalanche(h64);
-
- case 25: XXH_PROCESS8_64;
- /* fallthrough */
- case 17: XXH_PROCESS8_64;
- /* fallthrough */
- case 9: XXH_PROCESS8_64;
- XXH_PROCESS1_64;
- return XXH64_avalanche(h64);
-
- case 29: XXH_PROCESS8_64;
- /* fallthrough */
- case 21: XXH_PROCESS8_64;
- /* fallthrough */
- case 13: XXH_PROCESS8_64;
- /* fallthrough */
- case 5: XXH_PROCESS4_64;
- XXH_PROCESS1_64;
- return XXH64_avalanche(h64);
-
- case 26: XXH_PROCESS8_64;
- /* fallthrough */
- case 18: XXH_PROCESS8_64;
- /* fallthrough */
- case 10: XXH_PROCESS8_64;
- XXH_PROCESS1_64;
- XXH_PROCESS1_64;
- return XXH64_avalanche(h64);
-
- case 30: XXH_PROCESS8_64;
- /* fallthrough */
- case 22: XXH_PROCESS8_64;
- /* fallthrough */
- case 14: XXH_PROCESS8_64;
- /* fallthrough */
- case 6: XXH_PROCESS4_64;
- XXH_PROCESS1_64;
- XXH_PROCESS1_64;
- return XXH64_avalanche(h64);
-
- case 27: XXH_PROCESS8_64;
- /* fallthrough */
- case 19: XXH_PROCESS8_64;
- /* fallthrough */
- case 11: XXH_PROCESS8_64;
- XXH_PROCESS1_64;
- XXH_PROCESS1_64;
- XXH_PROCESS1_64;
- return XXH64_avalanche(h64);
-
- case 31: XXH_PROCESS8_64;
- /* fallthrough */
- case 23: XXH_PROCESS8_64;
- /* fallthrough */
- case 15: XXH_PROCESS8_64;
- /* fallthrough */
- case 7: XXH_PROCESS4_64;
- /* fallthrough */
- case 3: XXH_PROCESS1_64;
- /* fallthrough */
- case 2: XXH_PROCESS1_64;
- /* fallthrough */
- case 1: XXH_PROCESS1_64;
- /* fallthrough */
- case 0: return XXH64_avalanche(h64);
- }
+ if (ptr==NULL) XXH_ASSERT(len == 0);
+ len &= 31;
+ while (len >= 8) {
+ xxh_u64 const k1 = XXH64_round(0, XXH_get64bits(ptr));
+ ptr += 8;
+ h64 ^= k1;
+ h64 = XXH_rotl64(h64,27) * XXH_PRIME64_1 + XXH_PRIME64_4;
+ len -= 8;
+ }
+ if (len >= 4) {
+ h64 ^= (xxh_u64)(XXH_get32bits(ptr)) * XXH_PRIME64_1;
+ ptr += 4;
+ h64 = XXH_rotl64(h64, 23) * XXH_PRIME64_2 + XXH_PRIME64_3;
+ len -= 4;
+ }
+ while (len > 0) {
+ h64 ^= (*ptr++) * XXH_PRIME64_5;
+ h64 = XXH_rotl64(h64, 11) * XXH_PRIME64_1;
+ --len;
}
- /* impossible to reach */
- XXH_ASSERT(0);
- return 0; /* unreachable, but some compilers complain without it */
+ return XXH64_avalanche(h64);
}
#ifdef XXH_OLD_NAMES
XXH_FORCE_INLINE xxh_u64
XXH64_endian_align(const xxh_u8* input, size_t len, xxh_u64 seed, XXH_alignment align)
{
- const xxh_u8* bEnd = input + len;
xxh_u64 h64;
-
-#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)
- if (input==NULL) {
- len=0;
- bEnd=input=(const xxh_u8*)(size_t)32;
- }
-#endif
+ if (input==NULL) XXH_ASSERT(len == 0);
if (len>=32) {
- const xxh_u8* const limit = bEnd - 32;
+ const xxh_u8* const bEnd = input + len;
+ const xxh_u8* const limit = bEnd - 31;
xxh_u64 v1 = seed + XXH_PRIME64_1 + XXH_PRIME64_2;
xxh_u64 v2 = seed + XXH_PRIME64_2;
xxh_u64 v3 = seed + 0;
v2 = XXH64_round(v2, XXH_get64bits(input)); input+=8;
v3 = XXH64_round(v3, XXH_get64bits(input)); input+=8;
v4 = XXH64_round(v4, XXH_get64bits(input)); input+=8;
- } while (input<=limit);
+ } while (input<limit);
h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18);
h64 = XXH64_mergeRound(h64, v1);
}
+/*! @ingroup xxh64_family */
XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t len, XXH64_hash_t seed)
{
#if 0
XXH64_reset(&state, seed);
XXH64_update(&state, (const xxh_u8*)input, len);
return XXH64_digest(&state);
-
#else
-
if (XXH_FORCE_ALIGN_CHECK) {
if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */
return XXH64_endian_align((const xxh_u8*)input, len, seed, XXH_aligned);
/******* Hash Streaming *******/
+/*! @ingroup xxh64_family*/
XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void)
{
return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t));
}
+/*! @ingroup xxh64_family */
XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr)
{
XXH_free(statePtr);
return XXH_OK;
}
+/*! @ingroup xxh64_family */
XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState)
{
- memcpy(dstState, srcState, sizeof(*dstState));
+ XXH_memcpy(dstState, srcState, sizeof(*dstState));
}
+/*! @ingroup xxh64_family */
XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, XXH64_hash_t seed)
{
XXH64_state_t state; /* use a local state to memcpy() in order to avoid strict-aliasing warnings */
memset(&state, 0, sizeof(state));
- state.v1 = seed + XXH_PRIME64_1 + XXH_PRIME64_2;
- state.v2 = seed + XXH_PRIME64_2;
- state.v3 = seed + 0;
- state.v4 = seed - XXH_PRIME64_1;
+ state.v[0] = seed + XXH_PRIME64_1 + XXH_PRIME64_2;
+ state.v[1] = seed + XXH_PRIME64_2;
+ state.v[2] = seed + 0;
+ state.v[3] = seed - XXH_PRIME64_1;
/* do not write into reserved64, might be removed in a future version */
- memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved64));
+ XXH_memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved64));
return XXH_OK;
}
+/*! @ingroup xxh64_family */
XXH_PUBLIC_API XXH_errorcode
XXH64_update (XXH64_state_t* state, const void* input, size_t len)
{
- if (input==NULL)
-#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)
+ if (input==NULL) {
+ XXH_ASSERT(len == 0);
return XXH_OK;
-#else
- return XXH_ERROR;
-#endif
+ }
{ const xxh_u8* p = (const xxh_u8*)input;
const xxh_u8* const bEnd = p + len;
if (state->memsize) { /* tmp buffer is full */
XXH_memcpy(((xxh_u8*)state->mem64) + state->memsize, input, 32-state->memsize);
- state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0));
- state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1));
- state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2));
- state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3));
- p += 32-state->memsize;
+ state->v[0] = XXH64_round(state->v[0], XXH_readLE64(state->mem64+0));
+ state->v[1] = XXH64_round(state->v[1], XXH_readLE64(state->mem64+1));
+ state->v[2] = XXH64_round(state->v[2], XXH_readLE64(state->mem64+2));
+ state->v[3] = XXH64_round(state->v[3], XXH_readLE64(state->mem64+3));
+ p += 32 - state->memsize;
state->memsize = 0;
}
if (p+32 <= bEnd) {
const xxh_u8* const limit = bEnd - 32;
- xxh_u64 v1 = state->v1;
- xxh_u64 v2 = state->v2;
- xxh_u64 v3 = state->v3;
- xxh_u64 v4 = state->v4;
do {
- v1 = XXH64_round(v1, XXH_readLE64(p)); p+=8;
- v2 = XXH64_round(v2, XXH_readLE64(p)); p+=8;
- v3 = XXH64_round(v3, XXH_readLE64(p)); p+=8;
- v4 = XXH64_round(v4, XXH_readLE64(p)); p+=8;
+ state->v[0] = XXH64_round(state->v[0], XXH_readLE64(p)); p+=8;
+ state->v[1] = XXH64_round(state->v[1], XXH_readLE64(p)); p+=8;
+ state->v[2] = XXH64_round(state->v[2], XXH_readLE64(p)); p+=8;
+ state->v[3] = XXH64_round(state->v[3], XXH_readLE64(p)); p+=8;
} while (p<=limit);
- state->v1 = v1;
- state->v2 = v2;
- state->v3 = v3;
- state->v4 = v4;
}
if (p < bEnd) {
}
-XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* state)
+/*! @ingroup xxh64_family */
+XXH_PUBLIC_API XXH64_hash_t XXH64_digest(const XXH64_state_t* state)
{
xxh_u64 h64;
if (state->total_len >= 32) {
- xxh_u64 const v1 = state->v1;
- xxh_u64 const v2 = state->v2;
- xxh_u64 const v3 = state->v3;
- xxh_u64 const v4 = state->v4;
-
- h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18);
- h64 = XXH64_mergeRound(h64, v1);
- h64 = XXH64_mergeRound(h64, v2);
- h64 = XXH64_mergeRound(h64, v3);
- h64 = XXH64_mergeRound(h64, v4);
+ h64 = XXH_rotl64(state->v[0], 1) + XXH_rotl64(state->v[1], 7) + XXH_rotl64(state->v[2], 12) + XXH_rotl64(state->v[3], 18);
+ h64 = XXH64_mergeRound(h64, state->v[0]);
+ h64 = XXH64_mergeRound(h64, state->v[1]);
+ h64 = XXH64_mergeRound(h64, state->v[2]);
+ h64 = XXH64_mergeRound(h64, state->v[3]);
} else {
- h64 = state->v3 /*seed*/ + XXH_PRIME64_5;
+ h64 = state->v[2] /*seed*/ + XXH_PRIME64_5;
}
h64 += (xxh_u64) state->total_len;
/******* Canonical representation *******/
+/*! @ingroup xxh64_family */
XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash)
{
XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t));
if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash);
- memcpy(dst, &hash, sizeof(*dst));
+ XXH_memcpy(dst, &hash, sizeof(*dst));
}
+/*! @ingroup xxh64_family */
XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src)
{
return XXH_readBE64(src);
}
-
+#ifndef XXH_NO_XXH3
/* *********************************************************************
* XXH3
* New generation hash designed for speed on small keys and vectorization
************************************************************************ */
+/*!
+ * @}
+ * @defgroup xxh3_impl XXH3 implementation
+ * @ingroup impl
+ * @{
+ */
/* === Compiler specifics === */
-#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* >= C99 */
+#if ((defined(sun) || defined(__sun)) && __cplusplus) /* Solaris includes __STDC_VERSION__ with C++. Tested with GCC 5.5 */
+# define XXH_RESTRICT /* disable */
+#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* >= C99 */
# define XXH_RESTRICT restrict
#else
/* Note: it might be useful to define __restrict or __restrict__ for some C++ compilers */
/* ==========================================
* Vectorization detection
* ========================================== */
-#define XXH_SCALAR 0 /* Portable scalar version */
-#define XXH_SSE2 1 /* SSE2 for Pentium 4 and all x86_64 */
-#define XXH_AVX2 2 /* AVX2 for Haswell and Bulldozer */
-#define XXH_AVX512 3 /* AVX512 for Skylake and Icelake */
-#define XXH_NEON 4 /* NEON for most ARMv7-A and all AArch64 */
-#define XXH_VSX 5 /* VSX and ZVector for POWER8/z13 */
+
+#ifdef XXH_DOXYGEN
+/*!
+ * @ingroup tuning
+ * @brief Overrides the vectorization implementation chosen for XXH3.
+ *
+ * Can be defined to 0 to disable SIMD or any of the values mentioned in
+ * @ref XXH_VECTOR_TYPE.
+ *
+ * If this is not defined, it uses predefined macros to determine the best
+ * implementation.
+ */
+# define XXH_VECTOR XXH_SCALAR
+/*!
+ * @ingroup tuning
+ * @brief Possible values for @ref XXH_VECTOR.
+ *
+ * Note that these are actually implemented as macros.
+ *
+ * If this is not defined, it is detected automatically.
+ * @ref XXH_X86DISPATCH overrides this.
+ */
+enum XXH_VECTOR_TYPE /* fake enum */ {
+ XXH_SCALAR = 0, /*!< Portable scalar version */
+ XXH_SSE2 = 1, /*!<
+ * SSE2 for Pentium 4, Opteron, all x86_64.
+ *
+ * @note SSE2 is also guaranteed on Windows 10, macOS, and
+ * Android x86.
+ */
+ XXH_AVX2 = 2, /*!< AVX2 for Haswell and Bulldozer */
+ XXH_AVX512 = 3, /*!< AVX512 for Skylake and Icelake */
+ XXH_NEON = 4, /*!< NEON for most ARMv7-A and all AArch64 */
+ XXH_VSX = 5, /*!< VSX and ZVector for POWER8/z13 (64-bit) */
+};
+/*!
+ * @ingroup tuning
+ * @brief Selects the minimum alignment for XXH3's accumulators.
+ *
+ * When using SIMD, this should match the alignment reqired for said vector
+ * type, so, for example, 32 for AVX2.
+ *
+ * Default: Auto detected.
+ */
+# define XXH_ACC_ALIGN 8
+#endif
+
+/* Actual definition */
+#ifndef XXH_DOXYGEN
+# define XXH_SCALAR 0
+# define XXH_SSE2 1
+# define XXH_AVX2 2
+# define XXH_AVX512 3
+# define XXH_NEON 4
+# define XXH_VSX 5
+#endif
#ifndef XXH_VECTOR /* can be defined on command line */
# if defined(__AVX512F__)
# define XXH_VECTOR XXH_AVX2
# elif defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP == 2))
# define XXH_VECTOR XXH_SSE2
-# elif defined(__GNUC__) /* msvc support maybe later */ \
- && (defined(__ARM_NEON__) || defined(__ARM_NEON)) \
- && (defined(__LITTLE_ENDIAN__) /* We only support little endian NEON */ \
- || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
+# elif ( \
+ defined(__ARM_NEON__) || defined(__ARM_NEON) /* gcc */ \
+ || defined(_M_ARM64) || defined(_M_ARM_ARMV7VE) /* msvc */ \
+ ) && ( \
+ defined(_WIN32) || defined(__LITTLE_ENDIAN__) /* little endian only */ \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) \
+ )
# define XXH_VECTOR XXH_NEON
# elif (defined(__PPC64__) && defined(__POWER8_VECTOR__)) \
|| (defined(__s390x__) && defined(__VEC__)) \
* This is available on ARMv7-A, but is less efficient than a single VZIP.32.
*/
-/*
+/*!
* Function-like macro:
* void XXH_SPLIT_IN_PLACE(uint64x2_t &in, uint32x2_t &outLo, uint32x2_t &outHi)
* {
*/
# if !defined(XXH_NO_VZIP_HACK) /* define to disable */ \
&& defined(__GNUC__) \
- && !defined(__aarch64__) && !defined(__arm64__)
+ && !defined(__aarch64__) && !defined(__arm64__) && !defined(_M_ARM64)
# define XXH_SPLIT_IN_PLACE(in, outLo, outHi) \
do { \
/* Undocumented GCC/Clang operand modifier: %e0 = lower D half, %f0 = upper D half */ \
# endif /* !defined(XXH_VSX_BE) */
# if XXH_VSX_BE
-/* A wrapper for POWER9's vec_revb. */
# if defined(__POWER9_VECTOR__) || (defined(__clang__) && defined(__s390x__))
# define XXH_vec_revb vec_revb
# else
+/*!
+ * A polyfill for POWER9's vec_revb().
+ */
XXH_FORCE_INLINE xxh_u64x2 XXH_vec_revb(xxh_u64x2 val)
{
xxh_u8x16 const vByteSwap = { 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
# endif
# endif /* XXH_VSX_BE */
-/*
- * Performs an unaligned load and byte swaps it on big endian.
+/*!
+ * Performs an unaligned vector load and byte swaps it on big endian.
*/
XXH_FORCE_INLINE xxh_u64x2 XXH_vec_loadu(const void *ptr)
{
xxh_u64x2 ret;
- memcpy(&ret, ptr, sizeof(xxh_u64x2));
+ XXH_memcpy(&ret, ptr, sizeof(xxh_u64x2));
# if XXH_VSX_BE
ret = XXH_vec_revb(ret);
# endif
#if defined(XXH_NO_PREFETCH)
# define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */
#else
-# if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_I86)) /* _mm_prefetch() is not defined outside of x86/x64 */
+# if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) /* _mm_prefetch() not defined outside of x86/x64 */
# include <mmintrin.h> /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */
# define XXH_PREFETCH(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0)
# elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) )
# error "default keyset is not large enough"
#endif
-/* Pseudorandom secret taken directly from FARSH */
+/*! Pseudorandom secret taken directly from FARSH. */
XXH_ALIGN(64) static const xxh_u8 XXH3_kSecret[XXH_SECRET_DEFAULT_SIZE] = {
0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
# define kSecret XXH3_kSecret
#endif
-/*
- * Calculates a 32-bit to 64-bit long multiply.
+#ifdef XXH_DOXYGEN
+/*!
+ * @brief Calculates a 32-bit to 64-bit long multiply.
*
- * Wraps __emulu on MSVC x86 because it tends to call __allmul when it doesn't
+ * Implemented as a macro.
+ *
+ * Wraps `__emulu` on MSVC x86 because it tends to call `__allmul` when it doesn't
* need to (but it shouldn't need to anyways, it is about 7 instructions to do
- * a 64x64 multiply...). Since we know that this will _always_ emit MULL, we
+ * a 64x64 multiply...). Since we know that this will _always_ emit `MULL`, we
* use that instead of the normal method.
*
* If you are compiling for platforms like Thumb-1 and don't have a better option,
* you may also want to write your own long multiply routine here.
*
- * XXH_FORCE_INLINE xxh_u64 XXH_mult32to64(xxh_u64 x, xxh_u64 y)
- * {
- * return (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF);
- * }
+ * @param x, y Numbers to be multiplied
+ * @return 64-bit product of the low 32 bits of @p x and @p y.
*/
-#if defined(_MSC_VER) && defined(_M_IX86)
+XXH_FORCE_INLINE xxh_u64
+XXH_mult32to64(xxh_u64 x, xxh_u64 y)
+{
+ return (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF);
+}
+#elif defined(_MSC_VER) && defined(_M_IX86)
# include <intrin.h>
# define XXH_mult32to64(x, y) __emulu((unsigned)(x), (unsigned)(y))
#else
# define XXH_mult32to64(x, y) ((xxh_u64)(xxh_u32)(x) * (xxh_u64)(xxh_u32)(y))
#endif
-/*
- * Calculates a 64->128-bit long multiply.
+/*!
+ * @brief Calculates a 64->128-bit long multiply.
*
- * Uses __uint128_t and _umul128 if available, otherwise uses a scalar version.
+ * Uses `__uint128_t` and `_umul128` if available, otherwise uses a scalar
+ * version.
+ *
+ * @param lhs , rhs The 64-bit integers to be multiplied
+ * @return The 128-bit result represented in an @ref XXH128_hash_t.
*/
static XXH128_hash_t
XXH_mult64to128(xxh_u64 lhs, xxh_u64 rhs)
r128.high64 = product_high;
return r128;
+ /*
+ * MSVC for ARM64's __umulh method.
+ *
+ * This compiles to the same MUL + UMULH as GCC/Clang's __uint128_t method.
+ */
+#elif defined(_M_ARM64)
+
+#ifndef _MSC_VER
+# pragma intrinsic(__umulh)
+#endif
+ XXH128_hash_t r128;
+ r128.low64 = lhs * rhs;
+ r128.high64 = __umulh(lhs, rhs);
+ return r128;
+
#else
/*
* Portable scalar method. Optimized for 32-bit and 64-bit ALUs.
#endif
}
-/*
- * Does a 64-bit to 128-bit multiply, then XOR folds it.
+/*!
+ * @brief Calculates a 64-bit to 128-bit multiply, then XOR folds it.
*
* The reason for the separate function is to prevent passing too many structs
* around by value. This will hopefully inline the multiply, but we don't force it.
+ *
+ * @param lhs , rhs The 64-bit integers to multiply
+ * @return The low 64 bits of the product XOR'd by the high 64 bits.
+ * @see XXH_mult64to128()
*/
static xxh_u64
XXH3_mul128_fold64(xxh_u64 lhs, xxh_u64 rhs)
return product.low64 ^ product.high64;
}
-/* Seems to produce slightly better code on GCC for some reason. */
+/*! Seems to produce slightly better code on GCC for some reason. */
XXH_FORCE_INLINE xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift)
{
XXH_ASSERT(0 <= shift && shift < 64);
{
XXH_ASSERT(input != NULL);
XXH_ASSERT(secret != NULL);
- XXH_ASSERT(4 <= len && len < 8);
+ XXH_ASSERT(4 <= len && len <= 8);
seed ^= (xxh_u64)XXH_swap32((xxh_u32)seed) << 32;
{ xxh_u32 const input1 = XXH_readLE32(input);
xxh_u32 const input2 = XXH_readLE32(input + len - 4);
{
XXH_ASSERT(input != NULL);
XXH_ASSERT(secret != NULL);
- XXH_ASSERT(8 <= len && len <= 16);
+ XXH_ASSERT(9 <= len && len <= 16);
{ xxh_u64 const bitflip1 = (XXH_readLE64(secret+24) ^ XXH_readLE64(secret+32)) + seed;
xxh_u64 const bitflip2 = (XXH_readLE64(secret+40) ^ XXH_readLE64(secret+48)) - seed;
xxh_u64 const input_lo = XXH_readLE64(input) ^ bitflip1;
* GCC generates much better scalar code than Clang for the rest of XXH3,
* which is why finding a more optimal codepath is an interest.
*/
- __asm__ ("" : "+r" (seed64));
+ XXH_COMPILER_GUARD(seed64);
#endif
{ xxh_u64 const input_lo = XXH_readLE64(input);
xxh_u64 const input_hi = XXH_readLE64(input+8);
XXH_FORCE_INLINE void XXH_writeLE64(void* dst, xxh_u64 v64)
{
if (!XXH_CPU_LITTLE_ENDIAN) v64 = XXH_swap64(v64);
- memcpy(dst, &v64, sizeof(v64));
+ XXH_memcpy(dst, &v64, sizeof(v64));
}
/* Several intrinsic functions below are supposed to accept __int64 as argument,
* Both XXH3_64bits and XXH3_128bits use this subroutine.
*/
-#if (XXH_VECTOR == XXH_AVX512) || defined(XXH_X86DISPATCH)
+#if (XXH_VECTOR == XXH_AVX512) \
+ || (defined(XXH_DISPATCH_AVX512) && XXH_DISPATCH_AVX512 != 0)
#ifndef XXH_TARGET_AVX512
# define XXH_TARGET_AVX512 /* disable attribute target */
const void* XXH_RESTRICT input,
const void* XXH_RESTRICT secret)
{
- XXH_ALIGN(64) __m512i* const xacc = (__m512i *) acc;
+ __m512i* const xacc = (__m512i *) acc;
XXH_ASSERT((((size_t)acc) & 63) == 0);
XXH_STATIC_ASSERT(XXH_STRIPE_LEN == sizeof(__m512i));
{
XXH_ASSERT((((size_t)acc) & 63) == 0);
XXH_STATIC_ASSERT(XXH_STRIPE_LEN == sizeof(__m512i));
- { XXH_ALIGN(64) __m512i* const xacc = (__m512i*) acc;
+ { __m512i* const xacc = (__m512i*) acc;
const __m512i prime32 = _mm512_set1_epi32((int)XXH_PRIME32_1);
/* xacc[0] ^= (xacc[0] >> 47) */
XXH_ASSERT(((size_t)customSecret & 63) == 0);
(void)(&XXH_writeLE64);
{ int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m512i);
- __m512i const seed = _mm512_mask_set1_epi64(_mm512_set1_epi64((xxh_i64)seed64), 0xAA, -(xxh_i64)seed64);
+ __m512i const seed = _mm512_mask_set1_epi64(_mm512_set1_epi64((xxh_i64)seed64), 0xAA, (xxh_i64)(0U - seed64));
- XXH_ALIGN(64) const __m512i* const src = (const __m512i*) XXH3_kSecret;
- XXH_ALIGN(64) __m512i* const dest = ( __m512i*) customSecret;
+ const __m512i* const src = (const __m512i*) ((const void*) XXH3_kSecret);
+ __m512i* const dest = ( __m512i*) customSecret;
int i;
+ XXH_ASSERT(((size_t)src & 63) == 0); /* control alignment */
+ XXH_ASSERT(((size_t)dest & 63) == 0);
for (i=0; i < nbRounds; ++i) {
/* GCC has a bug, _mm512_stream_load_si512 accepts 'void*', not 'void const*',
- * this will warn "discards ‘const’ qualifier". */
+ * this will warn "discards 'const' qualifier". */
union {
- XXH_ALIGN(64) const __m512i* cp;
- XXH_ALIGN(64) void* p;
+ const __m512i* cp;
+ void* p;
} remote_const_void;
remote_const_void.cp = src + i;
dest[i] = _mm512_add_epi64(_mm512_stream_load_si512(remote_const_void.p), seed);
#endif
-#if (XXH_VECTOR == XXH_AVX2) || defined(XXH_X86DISPATCH)
+#if (XXH_VECTOR == XXH_AVX2) \
+ || (defined(XXH_DISPATCH_AVX2) && XXH_DISPATCH_AVX2 != 0)
#ifndef XXH_TARGET_AVX2
# define XXH_TARGET_AVX2 /* disable attribute target */
const void* XXH_RESTRICT secret)
{
XXH_ASSERT((((size_t)acc) & 31) == 0);
- { XXH_ALIGN(32) __m256i* const xacc = (__m256i *) acc;
+ { __m256i* const xacc = (__m256i *) acc;
/* Unaligned. This is mainly for pointer arithmetic, and because
* _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */
const __m256i* const xinput = (const __m256i *) input;
XXH3_scrambleAcc_avx2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
{
XXH_ASSERT((((size_t)acc) & 31) == 0);
- { XXH_ALIGN(32) __m256i* const xacc = (__m256i*) acc;
+ { __m256i* const xacc = (__m256i*) acc;
/* Unaligned. This is mainly for pointer arithmetic, and because
* _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */
const __m256i* const xsecret = (const __m256i *) secret;
XXH_STATIC_ASSERT(XXH_SEC_ALIGN <= 64);
(void)(&XXH_writeLE64);
XXH_PREFETCH(customSecret);
- { __m256i const seed = _mm256_set_epi64x(-(xxh_i64)seed64, (xxh_i64)seed64, -(xxh_i64)seed64, (xxh_i64)seed64);
+ { __m256i const seed = _mm256_set_epi64x((xxh_i64)(0U - seed64), (xxh_i64)seed64, (xxh_i64)(0U - seed64), (xxh_i64)seed64);
- XXH_ALIGN(64) const __m256i* const src = (const __m256i*) XXH3_kSecret;
- XXH_ALIGN(64) __m256i* dest = ( __m256i*) customSecret;
+ const __m256i* const src = (const __m256i*) ((const void*) XXH3_kSecret);
+ __m256i* dest = ( __m256i*) customSecret;
# if defined(__GNUC__) || defined(__clang__)
/*
* On GCC & Clang, marking 'dest' as modified will cause the compiler:
* - do not extract the secret from sse registers in the internal loop
* - use less common registers, and avoid pushing these reg into stack
- * The asm hack causes Clang to assume that XXH3_kSecretPtr aliases with
- * customSecret, and on aarch64, this prevented LDP from merging two
- * loads together for free. Putting the loads together before the stores
- * properly generates LDP.
*/
- __asm__("" : "+r" (dest));
+ XXH_COMPILER_GUARD(dest);
# endif
+ XXH_ASSERT(((size_t)src & 31) == 0); /* control alignment */
+ XXH_ASSERT(((size_t)dest & 31) == 0);
/* GCC -O2 need unroll loop manually */
dest[0] = _mm256_add_epi64(_mm256_stream_load_si256(src+0), seed);
#endif
+/* x86dispatch always generates SSE2 */
#if (XXH_VECTOR == XXH_SSE2) || defined(XXH_X86DISPATCH)
#ifndef XXH_TARGET_SSE2
{
/* SSE2 is just a half-scale version of the AVX2 version. */
XXH_ASSERT((((size_t)acc) & 15) == 0);
- { XXH_ALIGN(16) __m128i* const xacc = (__m128i *) acc;
+ { __m128i* const xacc = (__m128i *) acc;
/* Unaligned. This is mainly for pointer arithmetic, and because
* _mm_loadu_si128 requires a const __m128i * pointer for some reason. */
const __m128i* const xinput = (const __m128i *) input;
XXH3_scrambleAcc_sse2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
{
XXH_ASSERT((((size_t)acc) & 15) == 0);
- { XXH_ALIGN(16) __m128i* const xacc = (__m128i*) acc;
+ { __m128i* const xacc = (__m128i*) acc;
/* Unaligned. This is mainly for pointer arithmetic, and because
* _mm_loadu_si128 requires a const __m128i * pointer for some reason. */
const __m128i* const xsecret = (const __m128i *) secret;
{ int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m128i);
# if defined(_MSC_VER) && defined(_M_IX86) && _MSC_VER < 1900
- // MSVC 32bit mode does not support _mm_set_epi64x before 2015
- XXH_ALIGN(16) const xxh_i64 seed64x2[2] = { (xxh_i64)seed64, -(xxh_i64)seed64 };
+ /* MSVC 32bit mode does not support _mm_set_epi64x before 2015 */
+ XXH_ALIGN(16) const xxh_i64 seed64x2[2] = { (xxh_i64)seed64, (xxh_i64)(0U - seed64) };
__m128i const seed = _mm_load_si128((__m128i const*)seed64x2);
# else
- __m128i const seed = _mm_set_epi64x(-(xxh_i64)seed64, (xxh_i64)seed64);
+ __m128i const seed = _mm_set_epi64x((xxh_i64)(0U - seed64), (xxh_i64)seed64);
# endif
int i;
- XXH_ALIGN(64) const float* const src = (float const*) XXH3_kSecret;
- XXH_ALIGN(XXH_SEC_ALIGN) __m128i* dest = (__m128i*) customSecret;
+ const void* const src16 = XXH3_kSecret;
+ __m128i* dst16 = (__m128i*) customSecret;
# if defined(__GNUC__) || defined(__clang__)
/*
* On GCC & Clang, marking 'dest' as modified will cause the compiler:
* - do not extract the secret from sse registers in the internal loop
* - use less common registers, and avoid pushing these reg into stack
*/
- __asm__("" : "+r" (dest));
+ XXH_COMPILER_GUARD(dst16);
# endif
+ XXH_ASSERT(((size_t)src16 & 15) == 0); /* control alignment */
+ XXH_ASSERT(((size_t)dst16 & 15) == 0);
for (i=0; i < nbRounds; ++i) {
- dest[i] = _mm_add_epi64(_mm_castps_si128(_mm_load_ps(src+i*4)), seed);
+ dst16[i] = _mm_add_epi64(_mm_load_si128((const __m128i *)src16+i), seed);
} }
}
{
XXH_ASSERT((((size_t)acc) & 15) == 0);
{
- XXH_ALIGN(16) uint64x2_t* const xacc = (uint64x2_t *) acc;
+ uint64x2_t* const xacc = (uint64x2_t *) acc;
/* We don't use a uint32x4_t pointer because it causes bus errors on ARMv7. */
uint8_t const* const xinput = (const uint8_t *) input;
uint8_t const* const xsecret = (const uint8_t *) secret;
uint64x2_t data_vec = veorq_u64 (acc_vec, shifted);
/* xacc[i] ^= xsecret[i]; */
- uint8x16_t key_vec = vld1q_u8(xsecret + (i * 16));
- uint64x2_t data_key = veorq_u64(data_vec, vreinterpretq_u64_u8(key_vec));
+ uint8x16_t key_vec = vld1q_u8 (xsecret + (i * 16));
+ uint64x2_t data_key = veorq_u64 (data_vec, vreinterpretq_u64_u8(key_vec));
/* xacc[i] *= XXH_PRIME32_1 */
uint32x2_t data_key_lo, data_key_hi;
const void* XXH_RESTRICT input,
const void* XXH_RESTRICT secret)
{
- xxh_u64x2* const xacc = (xxh_u64x2*) acc; /* presumed aligned */
+ /* presumed aligned */
+ unsigned long long* const xacc = (unsigned long long*) acc;
xxh_u64x2 const* const xinput = (xxh_u64x2 const*) input; /* no alignment restriction */
xxh_u64x2 const* const xsecret = (xxh_u64x2 const*) secret; /* no alignment restriction */
xxh_u64x2 const v32 = { 32, 32 };
xxh_u32x4 const shuffled = (xxh_u32x4)vec_rl(data_key, v32);
/* product = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)shuffled & 0xFFFFFFFF); */
xxh_u64x2 const product = XXH_vec_mulo((xxh_u32x4)data_key, shuffled);
- xacc[i] += product;
+ /* acc_vec = xacc[i]; */
+ xxh_u64x2 acc_vec = vec_xl(0, xacc + 2 * i);
+ acc_vec += product;
/* swap high and low halves */
#ifdef __s390x__
- xacc[i] += vec_permi(data_vec, data_vec, 2);
+ acc_vec += vec_permi(data_vec, data_vec, 2);
#else
- xacc[i] += vec_xxpermdi(data_vec, data_vec, 2);
+ acc_vec += vec_xxpermdi(data_vec, data_vec, 2);
#endif
+ /* xacc[i] = acc_vec; */
+ vec_xst(acc_vec, 0, xacc + 2 * i);
}
}
const void* XXH_RESTRICT input,
const void* XXH_RESTRICT secret)
{
- XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned */
+ xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned */
const xxh_u8* const xinput = (const xxh_u8*) input; /* no alignment restriction */
const xxh_u8* const xsecret = (const xxh_u8*) secret; /* no alignment restriction */
size_t i;
XXH_FORCE_INLINE void
XXH3_scrambleAcc_scalar(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
{
- XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned */
+ xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned */
const xxh_u8* const xsecret = (const xxh_u8*) secret; /* no alignment restriction */
size_t i;
XXH_ASSERT((((size_t)acc) & (XXH_ACC_ALIGN-1)) == 0);
* without hack: 2654.4 MB/s
* with hack: 3202.9 MB/s
*/
- __asm__("" : "+r" (kSecretPtr));
+ XXH_COMPILER_GUARD(kSecretPtr);
#endif
/*
* Note: in debug mode, this overrides the asm optimization
* without hack: 2063.7 MB/s
* with hack: 2560.7 MB/s
*/
- __asm__("" : "+r" (result64));
+ XXH_COMPILER_GUARD(result64);
#endif
}
}
/*
- * It's important for performance that XXH3_hashLong is not inlined.
+ * It's important for performance to transmit secret's size (when it's static)
+ * so that the compiler can properly optimize the vectorized loop.
+ * This makes a big performance difference for "medium" keys (<1 KB) when using AVX instruction set.
*/
-XXH_NO_INLINE XXH64_hash_t
+XXH_FORCE_INLINE XXH64_hash_t
XXH3_hashLong_64b_withSecret(const void* XXH_RESTRICT input, size_t len,
XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
{
}
/*
- * It's important for performance that XXH3_hashLong is not inlined.
- * Since the function is not inlined, the compiler may not be able to understand that,
- * in some scenarios, its `secret` argument is actually a compile time constant.
- * This variant enforces that the compiler can detect that,
- * and uses this opportunity to streamline the generated code for better performance.
+ * It's preferable for performance that XXH3_hashLong is not inlined,
+ * as it results in a smaller function for small data, easier to the instruction cache.
+ * Note that inside this no_inline function, we do inline the internal loop,
+ * and provide a statically defined secret size to allow optimization of vector loop.
*/
XXH_NO_INLINE XXH64_hash_t
XXH3_hashLong_64b_default(const void* XXH_RESTRICT input, size_t len,
/* === Public entry point === */
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(const void* input, size_t len)
{
return XXH3_64bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_default);
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH64_hash_t
XXH3_64bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize)
{
return XXH3_64bits_internal(input, len, 0, secret, secretSize, XXH3_hashLong_64b_withSecret);
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH64_hash_t
XXH3_64bits_withSeed(const void* input, size_t len, XXH64_hash_t seed)
{
return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed);
}
+XXH_PUBLIC_API XXH64_hash_t
+XXH3_64bits_withSecretandSeed(const void* input, size_t len, const void* secret, size_t secretSize, XXH64_hash_t seed)
+{
+ if (len <= XXH3_MIDSIZE_MAX)
+ return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL);
+ return XXH3_hashLong_64b_withSecret(input, len, seed, (const xxh_u8*)secret, secretSize);
+}
+
/* === XXH3 streaming === */
XXH_free(base);
}
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void)
{
XXH3_state_t* const state = (XXH3_state_t*)XXH_alignedMalloc(sizeof(XXH3_state_t), 64);
return state;
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr)
{
XXH_alignedFree(statePtr);
return XXH_OK;
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API void
XXH3_copyState(XXH3_state_t* dst_state, const XXH3_state_t* src_state)
{
- memcpy(dst_state, src_state, sizeof(*dst_state));
+ XXH_memcpy(dst_state, src_state, sizeof(*dst_state));
}
static void
-XXH3_64bits_reset_internal(XXH3_state_t* statePtr,
- XXH64_hash_t seed,
- const void* secret, size_t secretSize)
+XXH3_reset_internal(XXH3_state_t* statePtr,
+ XXH64_hash_t seed,
+ const void* secret, size_t secretSize)
{
size_t const initStart = offsetof(XXH3_state_t, bufferedSize);
size_t const initLength = offsetof(XXH3_state_t, nbStripesPerBlock) - initStart;
statePtr->acc[6] = XXH_PRIME64_5;
statePtr->acc[7] = XXH_PRIME32_1;
statePtr->seed = seed;
+ statePtr->useSeed = (seed != 0);
statePtr->extSecret = (const unsigned char*)secret;
XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
statePtr->secretLimit = secretSize - XXH_STRIPE_LEN;
statePtr->nbStripesPerBlock = statePtr->secretLimit / XXH_SECRET_CONSUME_RATE;
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH_errorcode
XXH3_64bits_reset(XXH3_state_t* statePtr)
{
if (statePtr == NULL) return XXH_ERROR;
- XXH3_64bits_reset_internal(statePtr, 0, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE);
+ XXH3_reset_internal(statePtr, 0, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE);
return XXH_OK;
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH_errorcode
XXH3_64bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize)
{
if (statePtr == NULL) return XXH_ERROR;
- XXH3_64bits_reset_internal(statePtr, 0, secret, secretSize);
+ XXH3_reset_internal(statePtr, 0, secret, secretSize);
if (secret == NULL) return XXH_ERROR;
if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
return XXH_OK;
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH_errorcode
XXH3_64bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed)
{
if (statePtr == NULL) return XXH_ERROR;
if (seed==0) return XXH3_64bits_reset(statePtr);
- if (seed != statePtr->seed) XXH3_initCustomSecret(statePtr->customSecret, seed);
- XXH3_64bits_reset_internal(statePtr, seed, NULL, XXH_SECRET_DEFAULT_SIZE);
+ if ((seed != statePtr->seed) || (statePtr->extSecret != NULL))
+ XXH3_initCustomSecret(statePtr->customSecret, seed);
+ XXH3_reset_internal(statePtr, seed, NULL, XXH_SECRET_DEFAULT_SIZE);
+ return XXH_OK;
+}
+
+/*! @ingroup xxh3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, size_t secretSize, XXH64_hash_t seed64)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ if (secret == NULL) return XXH_ERROR;
+ if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
+ XXH3_reset_internal(statePtr, seed64, secret, secretSize);
+ statePtr->useSeed = 1; /* always, even if seed64==0 */
return XXH_OK;
}
}
}
+#ifndef XXH3_STREAM_USE_STACK
+# ifndef __clang__ /* clang doesn't need additional stack space */
+# define XXH3_STREAM_USE_STACK 1
+# endif
+#endif
/*
* Both XXH3_64bits_update and XXH3_128bits_update use this routine.
*/
XXH_FORCE_INLINE XXH_errorcode
-XXH3_update(XXH3_state_t* state,
- const xxh_u8* input, size_t len,
+XXH3_update(XXH3_state_t* XXH_RESTRICT const state,
+ const xxh_u8* XXH_RESTRICT input, size_t len,
XXH3_f_accumulate_512 f_acc512,
XXH3_f_scrambleAcc f_scramble)
{
- if (input==NULL)
-#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1)
+ if (input==NULL) {
+ XXH_ASSERT(len == 0);
return XXH_OK;
-#else
- return XXH_ERROR;
-#endif
+ }
+ XXH_ASSERT(state != NULL);
{ const xxh_u8* const bEnd = input + len;
const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
-
+#if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1
+ /* For some reason, gcc and MSVC seem to suffer greatly
+ * when operating accumulators directly into state.
+ * Operating into stack space seems to enable proper optimization.
+ * clang, on the other hand, doesn't seem to need this trick */
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[8]; memcpy(acc, state->acc, sizeof(acc));
+#else
+ xxh_u64* XXH_RESTRICT const acc = state->acc;
+#endif
state->totalLen += len;
+ XXH_ASSERT(state->bufferedSize <= XXH3_INTERNALBUFFER_SIZE);
- if (state->bufferedSize + len <= XXH3_INTERNALBUFFER_SIZE) { /* fill in tmp buffer */
+ /* small input : just fill in tmp buffer */
+ if (state->bufferedSize + len <= XXH3_INTERNALBUFFER_SIZE) {
XXH_memcpy(state->buffer + state->bufferedSize, input, len);
state->bufferedSize += (XXH32_hash_t)len;
return XXH_OK;
}
- /* total input is now > XXH3_INTERNALBUFFER_SIZE */
+ /* total input is now > XXH3_INTERNALBUFFER_SIZE */
#define XXH3_INTERNALBUFFER_STRIPES (XXH3_INTERNALBUFFER_SIZE / XXH_STRIPE_LEN)
XXH_STATIC_ASSERT(XXH3_INTERNALBUFFER_SIZE % XXH_STRIPE_LEN == 0); /* clean multiple */
size_t const loadSize = XXH3_INTERNALBUFFER_SIZE - state->bufferedSize;
XXH_memcpy(state->buffer + state->bufferedSize, input, loadSize);
input += loadSize;
- XXH3_consumeStripes(state->acc,
+ XXH3_consumeStripes(acc,
&state->nbStripesSoFar, state->nbStripesPerBlock,
state->buffer, XXH3_INTERNALBUFFER_STRIPES,
secret, state->secretLimit,
}
XXH_ASSERT(input < bEnd);
- /* Consume input by a multiple of internal buffer size */
- if (input+XXH3_INTERNALBUFFER_SIZE < bEnd) {
- const xxh_u8* const limit = bEnd - XXH3_INTERNALBUFFER_SIZE;
- do {
- XXH3_consumeStripes(state->acc,
- &state->nbStripesSoFar, state->nbStripesPerBlock,
- input, XXH3_INTERNALBUFFER_STRIPES,
- secret, state->secretLimit,
- f_acc512, f_scramble);
- input += XXH3_INTERNALBUFFER_SIZE;
- } while (input<limit);
- /* for last partial stripe */
- memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN);
+ /* large input to consume : ingest per full block */
+ if ((size_t)(bEnd - input) > state->nbStripesPerBlock * XXH_STRIPE_LEN) {
+ size_t nbStripes = (size_t)(bEnd - 1 - input) / XXH_STRIPE_LEN;
+ XXH_ASSERT(state->nbStripesPerBlock >= state->nbStripesSoFar);
+ /* join to current block's end */
+ { size_t const nbStripesToEnd = state->nbStripesPerBlock - state->nbStripesSoFar;
+ XXH_ASSERT(nbStripes <= nbStripes);
+ XXH3_accumulate(acc, input, secret + state->nbStripesSoFar * XXH_SECRET_CONSUME_RATE, nbStripesToEnd, f_acc512);
+ f_scramble(acc, secret + state->secretLimit);
+ state->nbStripesSoFar = 0;
+ input += nbStripesToEnd * XXH_STRIPE_LEN;
+ nbStripes -= nbStripesToEnd;
+ }
+ /* consume per entire blocks */
+ while(nbStripes >= state->nbStripesPerBlock) {
+ XXH3_accumulate(acc, input, secret, state->nbStripesPerBlock, f_acc512);
+ f_scramble(acc, secret + state->secretLimit);
+ input += state->nbStripesPerBlock * XXH_STRIPE_LEN;
+ nbStripes -= state->nbStripesPerBlock;
+ }
+ /* consume last partial block */
+ XXH3_accumulate(acc, input, secret, nbStripes, f_acc512);
+ input += nbStripes * XXH_STRIPE_LEN;
+ XXH_ASSERT(input < bEnd); /* at least some bytes left */
+ state->nbStripesSoFar = nbStripes;
+ /* buffer predecessor of last partial stripe */
+ XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN);
+ XXH_ASSERT(bEnd - input <= XXH_STRIPE_LEN);
+ } else {
+ /* content to consume <= block size */
+ /* Consume input by a multiple of internal buffer size */
+ if (bEnd - input > XXH3_INTERNALBUFFER_SIZE) {
+ const xxh_u8* const limit = bEnd - XXH3_INTERNALBUFFER_SIZE;
+ do {
+ XXH3_consumeStripes(acc,
+ &state->nbStripesSoFar, state->nbStripesPerBlock,
+ input, XXH3_INTERNALBUFFER_STRIPES,
+ secret, state->secretLimit,
+ f_acc512, f_scramble);
+ input += XXH3_INTERNALBUFFER_SIZE;
+ } while (input<limit);
+ /* buffer predecessor of last partial stripe */
+ XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN);
+ }
}
- XXH_ASSERT(input < bEnd);
/* Some remaining input (always) : buffer it */
+ XXH_ASSERT(input < bEnd);
+ XXH_ASSERT(bEnd - input <= XXH3_INTERNALBUFFER_SIZE);
+ XXH_ASSERT(state->bufferedSize == 0);
XXH_memcpy(state->buffer, input, (size_t)(bEnd-input));
state->bufferedSize = (XXH32_hash_t)(bEnd-input);
+#if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1
+ /* save stack accumulators into state */
+ memcpy(state->acc, acc, sizeof(acc));
+#endif
}
return XXH_OK;
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH_errorcode
XXH3_64bits_update(XXH3_state_t* state, const void* input, size_t len)
{
* Digest on a local copy. This way, the state remains unaltered, and it can
* continue ingesting more input afterwards.
*/
- memcpy(acc, state->acc, sizeof(state->acc));
+ XXH_memcpy(acc, state->acc, sizeof(state->acc));
if (state->bufferedSize >= XXH_STRIPE_LEN) {
size_t const nbStripes = (state->bufferedSize - 1) / XXH_STRIPE_LEN;
size_t nbStripesSoFar = state->nbStripesSoFar;
xxh_u8 lastStripe[XXH_STRIPE_LEN];
size_t const catchupSize = XXH_STRIPE_LEN - state->bufferedSize;
XXH_ASSERT(state->bufferedSize > 0); /* there is always some input buffered */
- memcpy(lastStripe, state->buffer + sizeof(state->buffer) - catchupSize, catchupSize);
- memcpy(lastStripe + catchupSize, state->buffer, state->bufferedSize);
+ XXH_memcpy(lastStripe, state->buffer + sizeof(state->buffer) - catchupSize, catchupSize);
+ XXH_memcpy(lastStripe + catchupSize, state->buffer, state->bufferedSize);
XXH3_accumulate_512(acc,
lastStripe,
secret + state->secretLimit - XXH_SECRET_LASTACC_START);
}
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (const XXH3_state_t* state)
{
const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
(xxh_u64)state->totalLen * XXH_PRIME64_1);
}
/* totalLen <= XXH3_MIDSIZE_MAX: digesting a short input */
- if (state->seed)
+ if (state->useSeed)
return XXH3_64bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed);
return XXH3_64bits_withSecret(state->buffer, (size_t)(state->totalLen),
secret, state->secretLimit + XXH_STRIPE_LEN);
}
-#define XXH_MIN(x, y) (((x) > (y)) ? (y) : (x))
-
-XXH_PUBLIC_API void
-XXH3_generateSecret(void* secretBuffer, const void* customSeed, size_t customSeedSize)
-{
- XXH_ASSERT(secretBuffer != NULL);
- if (customSeedSize == 0) {
- memcpy(secretBuffer, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE);
- return;
- }
- XXH_ASSERT(customSeed != NULL);
-
- { size_t const segmentSize = sizeof(XXH128_hash_t);
- size_t const nbSegments = XXH_SECRET_DEFAULT_SIZE / segmentSize;
- XXH128_canonical_t scrambler;
- XXH64_hash_t seeds[12];
- size_t segnb;
- XXH_ASSERT(nbSegments == 12);
- XXH_ASSERT(segmentSize * nbSegments == XXH_SECRET_DEFAULT_SIZE); /* exact multiple */
- XXH128_canonicalFromHash(&scrambler, XXH128(customSeed, customSeedSize, 0));
-
- /*
- * Copy customSeed to seeds[], truncating or repeating as necessary.
- */
- { size_t toFill = XXH_MIN(customSeedSize, sizeof(seeds));
- size_t filled = toFill;
- memcpy(seeds, customSeed, toFill);
- while (filled < sizeof(seeds)) {
- toFill = XXH_MIN(filled, sizeof(seeds) - filled);
- memcpy((char*)seeds + filled, seeds, toFill);
- filled += toFill;
- } }
-
- /* generate secret */
- memcpy(secretBuffer, &scrambler, sizeof(scrambler));
- for (segnb=1; segnb < nbSegments; segnb++) {
- size_t const segmentStart = segnb * segmentSize;
- XXH128_canonical_t segment;
- XXH128_canonicalFromHash(&segment,
- XXH128(&scrambler, sizeof(scrambler), XXH_readLE64(seeds + segnb) + segnb) );
- memcpy((char*)secretBuffer + segmentStart, &segment, sizeof(segment));
- } }
-}
-
/* ==========================================
* XXH3 128 bits (a.k.a XXH128)
}
/*
- * It's important for performance that XXH3_hashLong is not inlined.
+ * It's important for performance to pass @secretLen (when it's static)
+ * to the compiler, so that it can properly optimize the vectorized loop.
*/
-XXH_NO_INLINE XXH128_hash_t
+XXH_FORCE_INLINE XXH128_hash_t
XXH3_hashLong_128b_withSecret(const void* XXH_RESTRICT input, size_t len,
XXH64_hash_t seed64,
const void* XXH_RESTRICT secret, size_t secretLen)
/* === Public XXH128 API === */
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(const void* input, size_t len)
{
return XXH3_128bits_internal(input, len, 0,
XXH3_hashLong_128b_default);
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH128_hash_t
XXH3_128bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize)
{
XXH3_hashLong_128b_withSecret);
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH128_hash_t
XXH3_128bits_withSeed(const void* input, size_t len, XXH64_hash_t seed)
{
XXH3_hashLong_128b_withSeed);
}
+/*! @ingroup xxh3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH3_128bits_withSecretandSeed(const void* input, size_t len, const void* secret, size_t secretSize, XXH64_hash_t seed)
+{
+ if (len <= XXH3_MIDSIZE_MAX)
+ return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL);
+ return XXH3_hashLong_128b_withSecret(input, len, seed, secret, secretSize);
+}
+
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH128_hash_t
XXH128(const void* input, size_t len, XXH64_hash_t seed)
{
/* === XXH3 128-bit streaming === */
/*
- * All the functions are actually the same as for 64-bit streaming variant.
- * The only difference is the finalizatiom routine.
+ * All initialization and update functions are identical to 64-bit streaming variant.
+ * The only difference is the finalization routine.
*/
-static void
-XXH3_128bits_reset_internal(XXH3_state_t* statePtr,
- XXH64_hash_t seed,
- const void* secret, size_t secretSize)
-{
- XXH3_64bits_reset_internal(statePtr, seed, secret, secretSize);
-}
-
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH_errorcode
XXH3_128bits_reset(XXH3_state_t* statePtr)
{
- if (statePtr == NULL) return XXH_ERROR;
- XXH3_128bits_reset_internal(statePtr, 0, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE);
- return XXH_OK;
+ return XXH3_64bits_reset(statePtr);
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH_errorcode
XXH3_128bits_reset_withSecret(XXH3_state_t* statePtr, const void* secret, size_t secretSize)
{
- if (statePtr == NULL) return XXH_ERROR;
- XXH3_128bits_reset_internal(statePtr, 0, secret, secretSize);
- if (secret == NULL) return XXH_ERROR;
- if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
- return XXH_OK;
+ return XXH3_64bits_reset_withSecret(statePtr, secret, secretSize);
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH_errorcode
XXH3_128bits_reset_withSeed(XXH3_state_t* statePtr, XXH64_hash_t seed)
{
- if (statePtr == NULL) return XXH_ERROR;
- if (seed==0) return XXH3_128bits_reset(statePtr);
- if (seed != statePtr->seed) XXH3_initCustomSecret(statePtr->customSecret, seed);
- XXH3_128bits_reset_internal(statePtr, seed, NULL, XXH_SECRET_DEFAULT_SIZE);
- return XXH_OK;
+ return XXH3_64bits_reset_withSeed(statePtr, seed);
}
+/*! @ingroup xxh3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset_withSecretandSeed(XXH3_state_t* statePtr, const void* secret, size_t secretSize, XXH64_hash_t seed)
+{
+ return XXH3_64bits_reset_withSecretandSeed(statePtr, secret, secretSize, seed);
+}
+
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH_errorcode
XXH3_128bits_update(XXH3_state_t* state, const void* input, size_t len)
{
XXH3_accumulate_512, XXH3_scrambleAcc);
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (const XXH3_state_t* state)
{
const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
#include <string.h> /* memcmp, memcpy */
/* return : 1 is equal, 0 if different */
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2)
{
/* note : XXH128_hash_t is compact, it has no padding byte */
* return : >0 if *h128_1 > *h128_2
* <0 if *h128_1 < *h128_2
* =0 if *h128_1 == *h128_2 */
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API int XXH128_cmp(const void* h128_1, const void* h128_2)
{
XXH128_hash_t const h1 = *(const XXH128_hash_t*)h128_1;
/*====== Canonical representation ======*/
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API void
XXH128_canonicalFromHash(XXH128_canonical_t* dst, XXH128_hash_t hash)
{
hash.high64 = XXH_swap64(hash.high64);
hash.low64 = XXH_swap64(hash.low64);
}
- memcpy(dst, &hash.high64, sizeof(hash.high64));
- memcpy((char*)dst + sizeof(hash.high64), &hash.low64, sizeof(hash.low64));
+ XXH_memcpy(dst, &hash.high64, sizeof(hash.high64));
+ XXH_memcpy((char*)dst + sizeof(hash.high64), &hash.low64, sizeof(hash.low64));
}
+/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH128_hash_t
XXH128_hashFromCanonical(const XXH128_canonical_t* src)
{
return h;
}
+
+
+/* ==========================================
+ * Secret generators
+ * ==========================================
+ */
+#define XXH_MIN(x, y) (((x) > (y)) ? (y) : (x))
+
+static void XXH3_combine16(void* dst, XXH128_hash_t h128)
+{
+ XXH_writeLE64( dst, XXH_readLE64(dst) ^ h128.low64 );
+ XXH_writeLE64( (char*)dst+8, XXH_readLE64((char*)dst+8) ^ h128.high64 );
+}
+
+/*! @ingroup xxh3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_generateSecret(void* secretBuffer, size_t secretSize, const void* customSeed, size_t customSeedSize)
+{
+ XXH_ASSERT(secretBuffer != NULL);
+ if (secretBuffer == NULL) return XXH_ERROR;
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
+ if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
+ if (customSeedSize == 0) {
+ customSeed = XXH3_kSecret;
+ customSeedSize = XXH_SECRET_DEFAULT_SIZE;
+ }
+ XXH_ASSERT(customSeed != NULL);
+ if (customSeed == NULL) return XXH_ERROR;
+
+ /* Fill secretBuffer with a copy of customSeed - repeat as needed */
+ { size_t pos = 0;
+ while (pos < secretSize) {
+ size_t const toCopy = XXH_MIN((secretSize - pos), customSeedSize);
+ memcpy((char*)secretBuffer + pos, customSeed, toCopy);
+ pos += toCopy;
+ } }
+
+ { size_t const nbSeg16 = secretSize / 16;
+ size_t n;
+ XXH128_canonical_t scrambler;
+ XXH128_canonicalFromHash(&scrambler, XXH128(customSeed, customSeedSize, 0));
+ for (n=0; n<nbSeg16; n++) {
+ XXH128_hash_t const h128 = XXH128(&scrambler, sizeof(scrambler), n);
+ XXH3_combine16((char*)secretBuffer + n*16, h128);
+ }
+ /* last segment */
+ XXH3_combine16((char*)secretBuffer + secretSize - 16, XXH128_hashFromCanonical(&scrambler));
+ }
+ return XXH_OK;
+}
+
+/*! @ingroup xxh3_family */
+XXH_PUBLIC_API void
+XXH3_generateSecret_fromSeed(void* secretBuffer, XXH64_hash_t seed)
+{
+ XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
+ XXH3_initCustomSecret(secret, seed);
+ XXH_ASSERT(secretBuffer != NULL);
+ memcpy(secretBuffer, secret, XXH_SECRET_DEFAULT_SIZE);
+}
+
+
+
/* Pop our optimization override from above */
#if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \
&& defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \
#endif /* XXH_NO_LONG_LONG */
+#endif /* XXH_NO_XXH3 */
+/*!
+ * @}
+ */
#endif /* XXH_IMPLEMENTATION */
--- /dev/null
+// Copyright (C) 2022 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 "Bytes.hpp"
+
+#include <assertions.hpp>
+
+namespace util {
+
+Bytes::Bytes(const Bytes& other) noexcept
+ : m_size(other.m_size),
+ m_capacity(other.m_size)
+{
+ delete[] m_data;
+ m_data = new uint8_t[m_size];
+ if (m_size > 0) {
+ std::memcpy(m_data, other.m_data, m_size);
+ }
+}
+
+Bytes::Bytes(Bytes&& other) noexcept
+{
+ delete[] m_data;
+ m_data = other.m_data;
+ m_size = other.m_size;
+ m_capacity = other.m_capacity;
+ other.m_data = nullptr;
+ other.m_size = 0;
+ other.m_capacity = 0;
+}
+
+Bytes&
+Bytes::operator=(const Bytes& other) noexcept
+{
+ if (&other == this) {
+ return *this;
+ }
+ delete[] m_data;
+ m_data = new uint8_t[other.m_size];
+ m_size = other.m_size;
+ m_capacity = other.m_size;
+ if (m_size > 0) {
+ std::memcpy(m_data, other.m_data, m_size);
+ }
+ return *this;
+}
+
+Bytes&
+Bytes::operator=(Bytes&& other) noexcept
+{
+ if (&other == this) {
+ return *this;
+ }
+ delete[] m_data;
+ m_data = other.m_data;
+ m_size = other.m_size;
+ m_capacity = other.m_capacity;
+ other.m_data = nullptr;
+ other.m_size = 0;
+ other.m_capacity = 0;
+ return *this;
+}
+
+void
+Bytes::reserve(size_t size) noexcept
+{
+ if (size > m_capacity) {
+ uint8_t* data = new uint8_t[size];
+ if (m_size > 0) {
+ std::memcpy(data, m_data, m_size);
+ }
+ delete[] m_data;
+ m_data = data;
+ m_capacity = size;
+ }
+}
+
+void
+Bytes::insert(const uint8_t* pos,
+ const uint8_t* first,
+ const uint8_t* last) noexcept
+{
+ const size_t inserted_size = last - first;
+ if (inserted_size == 0) {
+ return;
+ }
+ const size_t offset = pos - m_data;
+ if (m_size + inserted_size > m_capacity) {
+ m_capacity = std::max(2 * m_capacity, m_size + inserted_size);
+ uint8_t* new_data = new uint8_t[m_capacity];
+ if (offset > 0) {
+ std::memcpy(new_data, m_data, offset);
+ }
+ if (m_size > offset) {
+ std::memcpy(
+ new_data + offset + inserted_size, m_data + offset, m_size - offset);
+ }
+ delete[] m_data;
+ m_data = new_data;
+ } else if (m_size > offset) {
+ std::memmove(
+ m_data + offset + inserted_size, m_data + offset, m_size - offset);
+ }
+ std::memcpy(m_data + offset, first, inserted_size);
+ m_size += inserted_size;
+}
+
+void
+Bytes::resize(size_t size) noexcept
+{
+ if (size > m_capacity) {
+ uint8_t* new_data = new uint8_t[size];
+ if (m_size > 0) {
+ std::memcpy(new_data, m_data, m_size);
+ }
+ delete[] m_data;
+ m_data = new_data;
+ m_capacity = size;
+ }
+ m_size = size;
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2022 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/nonstd/span.hpp>
+
+#include <cstdint>
+#include <cstring>
+#include <initializer_list>
+
+namespace util {
+
+// This class represents a contiguous array of bytes.
+//
+// The primary motivation for this class instead of just using
+// std::vector<uint8_t> is to make zero copying without zero-filling possible
+// when retrieving data from syscalls like read(2), i.e, when
+// std::vector::insert cannot be used.
+class Bytes
+{
+public:
+ Bytes() noexcept = default;
+ explicit Bytes(size_t size) noexcept;
+
+ Bytes(const void* data, size_t size) noexcept;
+ Bytes(nonstd::span<const uint8_t> data) noexcept;
+
+ Bytes(const Bytes& other) noexcept;
+ Bytes(Bytes&& other) noexcept;
+
+ Bytes(std::initializer_list<uint8_t> init) noexcept;
+
+ ~Bytes() noexcept;
+
+ Bytes& operator=(const Bytes& other) noexcept;
+ Bytes& operator=(Bytes&& other) noexcept;
+
+ uint8_t operator[](size_t pos) const noexcept;
+ uint8_t& operator[](size_t pos) noexcept;
+
+ bool operator==(const Bytes& other) const noexcept;
+ bool operator!=(const Bytes& other) const noexcept;
+
+ const uint8_t* data() const noexcept;
+ uint8_t* data() noexcept;
+
+ uint8_t* begin() noexcept;
+ const uint8_t* begin() const noexcept;
+ const uint8_t* cbegin() const noexcept;
+
+ uint8_t* end() noexcept;
+ const uint8_t* end() const noexcept;
+ const uint8_t* cend() const noexcept;
+
+ bool empty() const noexcept;
+ size_t size() const noexcept;
+ size_t capacity() const noexcept;
+ void reserve(size_t size) noexcept;
+
+ void clear() noexcept;
+ void resize(size_t size) noexcept; // Note: New bytes will be uninitialized.
+
+ void insert(const uint8_t* pos,
+ const uint8_t* first,
+ const uint8_t* last) noexcept;
+ void
+ insert(const uint8_t* pos, const uint8_t* data, const size_t size) noexcept;
+ void insert(const uint8_t* pos, const char* first, const char* last) noexcept;
+ void insert(const uint8_t* pos, const char* data, size_t size) noexcept;
+
+private:
+ uint8_t* m_data = nullptr;
+ size_t m_size = 0;
+ size_t m_capacity = 0;
+};
+
+inline Bytes::Bytes(size_t size) noexcept
+ : m_data(new uint8_t[size]),
+ m_size(size),
+ m_capacity(size)
+{
+}
+
+inline Bytes::Bytes(const void* data, size_t size) noexcept
+ : m_data(new uint8_t[size]),
+ m_size(size),
+ m_capacity(size)
+{
+ std::memcpy(m_data, data, size);
+}
+
+inline Bytes::Bytes(nonstd::span<const uint8_t> data) noexcept
+ : Bytes(data.data(), data.size())
+{
+}
+
+inline Bytes::Bytes(std::initializer_list<uint8_t> init) noexcept
+ : Bytes({init.begin(), init.end()})
+{
+}
+
+inline Bytes::~Bytes() noexcept
+{
+ delete[] m_data;
+}
+
+inline uint8_t
+Bytes::operator[](size_t pos) const noexcept
+{
+ return m_data[pos];
+}
+
+inline uint8_t&
+Bytes::operator[](size_t pos) noexcept
+{
+ return m_data[pos];
+}
+
+inline bool
+Bytes::operator==(const Bytes& other) const noexcept
+{
+ return this == &other
+ || (m_size == other.m_size
+ && std::memcmp(m_data, other.m_data, m_size) == 0);
+}
+
+inline bool
+Bytes::operator!=(const Bytes& other) const noexcept
+{
+ return !(*this == other);
+}
+
+inline const uint8_t*
+Bytes::data() const noexcept
+{
+ return m_data;
+}
+
+inline uint8_t*
+Bytes::data() noexcept
+{
+ return m_data;
+}
+
+inline uint8_t*
+Bytes::begin() noexcept
+{
+ return m_data;
+}
+
+inline const uint8_t*
+Bytes::begin() const noexcept
+{
+ return m_data;
+}
+
+inline const uint8_t*
+Bytes::cbegin() const noexcept
+{
+ return m_data;
+}
+
+inline uint8_t*
+Bytes::end() noexcept
+{
+ return m_data + m_size;
+}
+
+inline const uint8_t*
+Bytes::end() const noexcept
+{
+ return m_data + m_size;
+}
+
+inline const uint8_t*
+Bytes::cend() const noexcept
+{
+ return m_data + m_size;
+}
+
+inline bool
+Bytes::empty() const noexcept
+{
+ return m_size == 0;
+}
+
+inline size_t
+Bytes::size() const noexcept
+{
+ return m_size;
+}
+
+inline size_t
+Bytes::capacity() const noexcept
+{
+ return m_capacity;
+}
+
+inline void
+Bytes::clear() noexcept
+{
+ m_size = 0;
+}
+
+inline void
+Bytes::insert(const uint8_t* pos,
+ const uint8_t* data,
+ const size_t size) noexcept
+{
+ return insert(pos, data, data + size);
+}
+
+inline void
+Bytes::insert(const uint8_t* pos, const char* first, const char* last) noexcept
+{
+ return insert(pos,
+ reinterpret_cast<const uint8_t*>(first),
+ reinterpret_cast<const uint8_t*>(last));
+}
+
+inline void
+Bytes::insert(const uint8_t* pos, const char* data, size_t size) noexcept
+{
+ return insert(pos, data, data + size);
+}
+
+} // namespace util
--- /dev/null
+set(
+ sources
+ Bytes.cpp
+ LockFile.cpp
+ TextTable.cpp
+ TimePoint.cpp
+ Tokenizer.cpp
+ file.cpp
+ path.cpp
+ string.cpp
+ zstd.cpp
+)
+
+file(GLOB headers *.hpp)
+list(APPEND sources ${headers})
+
+target_sources(ccache_framework PRIVATE ${sources})
--- /dev/null
+// Copyright (C) 2022 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 <cstdint>
+
+namespace util {
+
+class Duration
+{
+public:
+ explicit Duration(int64_t sec = 0, int64_t nsec = 0);
+
+ bool operator==(const Duration& other) const;
+ bool operator!=(const Duration& other) const;
+ bool operator<(const Duration& other) const;
+ bool operator>(const Duration& other) const;
+ bool operator<=(const Duration& other) const;
+ bool operator>=(const Duration& other) const;
+
+ Duration operator+(const Duration& other) const;
+ Duration operator-(const Duration& other) const;
+ Duration operator*(double factor) const;
+ Duration operator/(double factor) const;
+
+ Duration operator-() const;
+
+ int64_t sec() const;
+ int64_t nsec() const;
+ int32_t nsec_decimal_part() const;
+
+private:
+ int64_t m_ns = 0;
+};
+
+inline Duration::Duration(int64_t sec, int64_t nsec)
+ : m_ns(1'000'000'000 * sec + nsec)
+{
+}
+
+inline bool
+Duration::operator==(const Duration& other) const
+{
+ return m_ns == other.m_ns;
+}
+
+inline bool
+Duration::operator!=(const Duration& other) const
+{
+ return m_ns != other.m_ns;
+}
+
+inline bool
+Duration::operator<(const Duration& other) const
+{
+ return m_ns < other.m_ns;
+}
+
+inline bool
+Duration::operator>(const Duration& other) const
+{
+ return m_ns > other.m_ns;
+}
+
+inline bool
+Duration::operator<=(const Duration& other) const
+{
+ return m_ns <= other.m_ns;
+}
+
+inline bool
+Duration::operator>=(const Duration& other) const
+{
+ return m_ns >= other.m_ns;
+}
+
+inline Duration
+Duration::operator+(const Duration& other) const
+{
+ return Duration(0, m_ns + other.m_ns);
+}
+
+inline Duration
+Duration::operator-(const Duration& other) const
+{
+ return Duration(0, m_ns - other.m_ns);
+}
+
+inline Duration
+Duration::operator*(double factor) const
+{
+ return Duration(0, factor * m_ns);
+}
+
+inline Duration
+Duration::operator/(double factor) const
+{
+ return Duration(0, m_ns / factor);
+}
+
+inline int64_t
+Duration::sec() const
+{
+ return m_ns / 1'000'000'000;
+}
+
+inline int64_t
+Duration::nsec() const
+{
+ return m_ns;
+}
+
+inline int32_t
+Duration::nsec_decimal_part() const
+{
+ return m_ns % 1'000'000'000;
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2020-2022 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 "LockFile.hpp"
+
+#include "Logging.hpp"
+#include "Util.hpp"
+#include "Win32Util.hpp"
+#include "fmtmacros.hpp"
+
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <util/file.hpp>
+
+#include "third_party/fmt/core.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <algorithm>
+#include <random>
+#include <sstream>
+
+// Seconds.
+const double k_min_sleep_time = 0.010;
+const double k_max_sleep_time = 0.050;
+#ifndef _WIN32
+const util::Duration k_staleness_limit(2);
+const util::Duration k_keep_alive_interval(k_staleness_limit / 4);
+#endif
+
+namespace {
+
+class RandomNumberGenerator
+{
+public:
+ RandomNumberGenerator(int32_t min, int32_t max)
+ : m_random_engine(m_random_device()),
+ m_distribution(min, max)
+ {
+ }
+
+ int32_t
+ get()
+ {
+ return m_distribution(m_random_engine);
+ };
+
+private:
+ std::random_device m_random_device;
+ std::default_random_engine m_random_engine;
+ std::uniform_int_distribution<int32_t> m_distribution;
+};
+
+} // namespace
+
+namespace util {
+
+LockFile::LockFile(const std::string& path)
+ : m_lock_file(path + ".lock"),
+#ifndef _WIN32
+ m_acquired(false)
+#else
+ m_handle(INVALID_HANDLE_VALUE)
+#endif
+{
+}
+
+bool
+LockFile::acquire()
+{
+ LOG("Acquiring {}", m_lock_file);
+ return acquire(true);
+}
+
+bool
+LockFile::try_acquire()
+{
+ LOG("Trying to acquire {}", m_lock_file);
+ return acquire(false);
+}
+
+void
+LockFile::release()
+{
+ if (!acquired()) {
+ return;
+ }
+
+ LOG("Releasing {}", m_lock_file);
+#ifndef _WIN32
+ on_before_release();
+ Util::unlink_tmp(m_lock_file);
+#else
+ CloseHandle(m_handle);
+#endif
+ LOG("Released {}", m_lock_file);
+#ifndef _WIN32
+ m_acquired = false;
+#else
+ m_handle = INVALID_HANDLE_VALUE;
+#endif
+}
+
+bool
+LockFile::acquired() const
+{
+#ifndef _WIN32
+ return m_acquired;
+#else
+ return m_handle != INVALID_HANDLE_VALUE;
+#endif
+}
+
+bool
+LockFile::acquire(const bool blocking)
+{
+ ASSERT(!acquired());
+
+#ifndef _WIN32
+ m_acquired = do_acquire(blocking);
+#else
+ m_handle = do_acquire(blocking);
+#endif
+ if (acquired()) {
+ LOG("Acquired {}", m_lock_file);
+ on_after_acquire();
+ } else {
+ LOG("Failed to acquire lock {}", m_lock_file);
+ }
+
+ return acquired();
+}
+
+#ifndef _WIN32
+
+bool
+LockFile::do_acquire(const bool blocking)
+{
+ std::stringstream ss;
+ ss << Util::get_hostname() << '-' << getpid() << '-'
+ << std::this_thread::get_id();
+ const auto content_prefix = ss.str();
+
+ util::TimePoint last_seen_activity = [this] {
+ const auto last_lock_update = get_last_lock_update();
+ return last_lock_update ? *last_lock_update : util::TimePoint::now();
+ }();
+
+ std::string initial_content;
+ RandomNumberGenerator sleep_ms_generator(k_min_sleep_time * 1000,
+ k_max_sleep_time * 1000);
+
+ while (true) {
+ const auto now = util::TimePoint::now();
+ const auto my_content =
+ FMT("{}-{}.{}", content_prefix, now.sec(), now.nsec());
+
+ if (symlink(my_content.c_str(), m_lock_file.c_str()) == 0) {
+ // We got the lock.
+ return true;
+ }
+
+ int saved_errno = errno;
+ if (saved_errno == ENOENT) {
+ // Directory doesn't exist?
+ if (Util::create_dir(Util::dir_name(m_lock_file))) {
+ // OK. Retry.
+ continue;
+ }
+ }
+ LOG("Could not acquire {}: {}", m_lock_file, strerror(saved_errno));
+
+ if (saved_errno == EPERM) {
+ // The file system does not support symbolic links. We have no choice but
+ // to grant the lock anyway.
+ return true;
+ }
+
+ if (saved_errno != EEXIST) {
+ // Directory doesn't exist or isn't writable?
+ return false;
+ }
+
+ std::string content = Util::read_link(m_lock_file);
+ if (content.empty()) {
+ if (errno == ENOENT) {
+ // The symlink was removed after the symlink() call above, so retry
+ // acquiring it.
+ continue;
+ } else {
+ LOG("Could not read symlink {}: {}", m_lock_file, strerror(errno));
+ return false;
+ }
+ }
+
+ if (content == my_content) {
+ // Lost NFS reply?
+ LOG("Symlinking {} failed but we got the lock anyway", m_lock_file);
+ return true;
+ }
+
+ LOG("Lock info for {}: {}", m_lock_file, content);
+
+ if (initial_content.empty()) {
+ initial_content = content;
+ }
+
+ const auto last_lock_update = get_last_lock_update();
+ if (last_lock_update) {
+ last_seen_activity = std::max(last_seen_activity, *last_lock_update);
+ }
+
+ const util::Duration inactive_duration =
+ util::TimePoint::now() - last_seen_activity;
+
+ if (inactive_duration < k_staleness_limit) {
+ LOG("Lock {} held by another process active {}.{:03} seconds ago",
+ m_lock_file,
+ inactive_duration.sec(),
+ inactive_duration.nsec() / 1'000'000);
+ if (!blocking) {
+ return false;
+ }
+ } else if (content == initial_content) {
+ // The lock seems to be stale -- break it and try again.
+ LOG("Breaking {} since it has been inactive for {}.{:03} seconds",
+ m_lock_file,
+ inactive_duration.sec(),
+ inactive_duration.nsec() / 1'000'000);
+ if (!on_before_break() || !Util::unlink_tmp(m_lock_file)) {
+ return false;
+ }
+
+ // Note: There is an inherent race condition here where two processes may
+ // believe they both acquired the lock after breaking it:
+ //
+ // 1. A decides to break the lock.
+ // 2. B decides to break the lock.
+ // 3. A removes the file and retries.
+ // 4. A acquires the lock.
+ // 5. B removes the file and retries.
+ // 6. B acquires the lock.
+ //
+ // To reduce the risk we sleep for a while before retrying so that it's
+ // likely that step 5 happens before step 4.
+ } else {
+ LOG("Lock {} reacquired by another process", m_lock_file);
+ if (!blocking) {
+ return false;
+ }
+ initial_content = content;
+ }
+
+ const std::chrono::milliseconds to_sleep{sleep_ms_generator.get()};
+ LOG("Sleeping {} ms", to_sleep.count());
+ std::this_thread::sleep_for(to_sleep);
+ }
+}
+
+#else // !_WIN32
+
+void*
+LockFile::do_acquire(const bool blocking)
+{
+ void* handle;
+ RandomNumberGenerator sleep_ms_generator(k_min_sleep_time * 1000,
+ k_max_sleep_time * 1000);
+
+ while (true) {
+ DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE;
+ handle = CreateFile(m_lock_file.c_str(),
+ GENERIC_WRITE, // desired access
+ 0, // shared mode (0 = not shared)
+ nullptr, // security attributes
+ CREATE_ALWAYS, // creation disposition
+ flags, // flags and attributes
+ nullptr // template file
+ );
+ if (handle != INVALID_HANDLE_VALUE) {
+ break;
+ }
+
+ DWORD error = GetLastError();
+ if (error == ERROR_PATH_NOT_FOUND) {
+ // Directory doesn't exist?
+ if (Util::create_dir(Util::dir_name(m_lock_file))) {
+ // OK. Retry.
+ continue;
+ }
+ }
+
+ LOG("Could not acquire {}: {} ({})",
+ m_lock_file,
+ Win32Util::error_message(error),
+ error);
+
+ // ERROR_SHARING_VIOLATION: lock already held.
+ // ERROR_ACCESS_DENIED: maybe pending delete.
+ if (error != ERROR_SHARING_VIOLATION && error != ERROR_ACCESS_DENIED) {
+ // Fatal error, give up.
+ break;
+ }
+
+ LOG("Lock {} held by another process", m_lock_file);
+ if (!blocking) {
+ break;
+ }
+
+ const std::chrono::milliseconds to_sleep{sleep_ms_generator.get()};
+ LOG("Sleeping {} ms", to_sleep.count());
+ std::this_thread::sleep_for(to_sleep);
+ }
+
+ return handle;
+}
+
+#endif // !_WIN32
+
+ShortLivedLockFile::ShortLivedLockFile(const std::string& path) : LockFile(path)
+{
+}
+
+LongLivedLockFile::LongLivedLockFile(const std::string& path)
+ : LockFile(path)
+#ifndef _WIN32
+ ,
+ m_alive_file(path + ".alive")
+#endif
+{
+}
+
+#ifndef _WIN32
+
+void
+LongLivedLockFile::on_after_acquire()
+{
+ const auto result = util::write_file(m_alive_file, "");
+ if (!result) {
+ LOG("Failed to write {}: {}", m_alive_file, result.error());
+ }
+
+ LOG_RAW("Starting keep-alive thread");
+ m_keep_alive_thread = std::thread([=] {
+ while (true) {
+ std::unique_lock<std::mutex> lock(m_stop_keep_alive_mutex);
+ m_stop_keep_alive_condition.wait_for(
+ lock,
+ std::chrono::seconds(k_keep_alive_interval.sec())
+ + std::chrono::nanoseconds(k_keep_alive_interval.nsec()),
+ [this] { return m_stop_keep_alive; });
+ if (m_stop_keep_alive) {
+ return;
+ }
+ util::set_timestamps(m_alive_file);
+ }
+ });
+ LOG_RAW("Started keep-alive thread");
+}
+
+void
+LongLivedLockFile::on_before_release()
+{
+ if (m_keep_alive_thread.joinable()) {
+ {
+ std::unique_lock<std::mutex> lock(m_stop_keep_alive_mutex);
+ m_stop_keep_alive = true;
+ }
+ m_stop_keep_alive_condition.notify_one();
+ m_keep_alive_thread.join();
+
+ Util::unlink_tmp(m_alive_file);
+ }
+}
+
+bool
+LongLivedLockFile::on_before_break()
+{
+ return Util::unlink_tmp(m_alive_file);
+}
+
+std::optional<util::TimePoint>
+LongLivedLockFile::get_last_lock_update()
+{
+ if (const auto stat = Stat::stat(m_alive_file); stat) {
+ return stat.mtime();
+ } else {
+ return std::nullopt;
+ }
+}
+
+#endif
+
+LockFileGuard::LockFileGuard(LockFile& lock_file, Mode mode)
+ : m_lock_file(lock_file)
+{
+ if (mode == Mode::blocking) {
+ lock_file.acquire();
+ } else {
+ lock_file.try_acquire();
+ }
+}
+
+LockFileGuard::~LockFileGuard() noexcept
+{
+ m_lock_file.release();
+}
+
+bool
+LockFileGuard::acquired() const
+{
+ return m_lock_file.acquired();
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2020-2022 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 <NonCopyable.hpp>
+#include <util/TimePoint.hpp>
+
+#include <condition_variable>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <thread>
+
+namespace util {
+
+class LockFile : NonCopyable
+{
+public:
+ virtual ~LockFile() noexcept = default;
+
+ // Acquire lock, blocking. Returns true if acquired, otherwise false.
+ bool acquire();
+
+ // Acquire lock, non-blocking. Returns true if acquired, otherwise false.
+ bool try_acquire();
+
+ // Release lock. If not previously acquired, nothing happens.
+ void release();
+
+ // Return whether the lock was acquired successfully.
+ bool acquired() const;
+
+protected:
+ LockFile(const std::string& path);
+
+private:
+ std::string m_lock_file;
+#ifndef _WIN32
+ bool m_acquired;
+#else
+ void* m_handle;
+#endif
+
+ bool acquire(bool blocking);
+ virtual void on_after_acquire();
+ virtual void on_before_release();
+#ifndef _WIN32
+ bool do_acquire(bool blocking);
+ virtual bool on_before_break();
+ virtual std::optional<util::TimePoint> get_last_lock_update();
+#else
+ void* do_acquire(bool blocking);
+#endif
+};
+
+// A short-lived lock.
+//
+// The lock is expected to be released shortly after being acquired - if it is
+// held for more than two seconds it risks being considered stale by another
+// client.
+class ShortLivedLockFile : public LockFile
+{
+public:
+ ShortLivedLockFile(const std::string& path);
+};
+
+// A long-lived lock.
+//
+// The lock will (depending on implementation) be kept alive by a helper thread.
+class LongLivedLockFile : public LockFile
+{
+public:
+ LongLivedLockFile(const std::string& path);
+
+private:
+#ifndef _WIN32
+ std::string m_alive_file;
+ std::thread m_keep_alive_thread;
+ std::mutex m_stop_keep_alive_mutex;
+ bool m_stop_keep_alive = false;
+ std::condition_variable m_stop_keep_alive_condition;
+
+ void on_after_acquire() override;
+ void on_before_release() override;
+ bool on_before_break() override;
+ std::optional<util::TimePoint> get_last_lock_update() override;
+#endif
+};
+
+class LockFileGuard : NonCopyable
+{
+public:
+ enum class Mode { blocking, non_blocking };
+
+ LockFileGuard(LockFile& lock_file, Mode mode = Mode::blocking);
+ ~LockFileGuard() noexcept;
+
+ bool acquired() const;
+
+private:
+ LockFile& m_lock_file;
+};
+
+inline void
+LockFile::on_after_acquire()
+{
+}
+
+inline void
+LockFile::on_before_release()
+{
+}
+
+#ifndef _WIN32
+
+inline bool
+LockFile::on_before_break()
+{
+ return true;
+}
+
+inline std::optional<util::TimePoint>
+LockFile::get_last_lock_update()
+{
+ return std::nullopt;
+}
+
+#endif
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021 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 "TextTable.hpp"
+
+#include <assertions.hpp>
+
+#include <third_party/fmt/core.h>
+
+#include <algorithm>
+
+namespace util {
+
+void
+TextTable::add_heading(const std::string& text)
+{
+ Cell cell(text);
+ cell.m_heading = true;
+ m_rows.push_back({cell});
+}
+
+void
+TextTable::add_row(const std::vector<Cell>& cells)
+{
+ m_rows.emplace_back();
+ for (const auto& cell : cells) {
+ for (size_t i = 0; i < cell.m_colspan - 1; ++i) {
+ Cell dummy("");
+ dummy.m_colspan = 0;
+ m_rows.back().push_back(dummy);
+ }
+ m_rows.back().push_back(cell);
+
+ m_columns = std::max(m_columns, m_rows.back().size());
+ }
+}
+
+std::vector<size_t>
+TextTable::compute_column_widths() const
+{
+ std::vector<size_t> result(m_columns, 0);
+
+ for (size_t column_index = 0; column_index < m_columns; ++column_index) {
+ for (const auto& row : m_rows) {
+ if (column_index >= row.size()) {
+ continue;
+ }
+ const auto& cell = row[column_index];
+ if (cell.m_heading || cell.m_colspan == 0) {
+ continue;
+ }
+
+ size_t width_of_left_cols_in_span = 0;
+ for (size_t i = 0; i < cell.m_colspan - 1; ++i) {
+ width_of_left_cols_in_span += 1 + result[column_index - i - 1];
+ }
+ result[column_index] = std::max(
+ result[column_index],
+ cell.m_text.length()
+ - std::min(width_of_left_cols_in_span, cell.m_text.length()));
+ }
+ }
+
+ return result;
+}
+
+std::string
+TextTable::render() const
+{
+ auto column_widths = compute_column_widths();
+
+ std::string result;
+ for (const auto& row : m_rows) {
+ std::string r;
+ bool first = true;
+ for (size_t i = 0; i < row.size(); ++i) {
+ const auto& cell = row[i];
+ if (cell.m_colspan == 0) {
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ r += ' ';
+ }
+
+ size_t width = 0;
+ for (size_t j = i + 1 - cell.m_colspan; j <= i; ++j) {
+ width += column_widths[j] + (j == i ? 0 : 1);
+ }
+ r += fmt::format(
+ (cell.m_right_align ? "{:>{}}" : "{:<{}}"), cell.m_text, width);
+ }
+ result.append(r, 0, r.find_last_not_of(' ') + 1);
+ result += '\n';
+ }
+ return result;
+}
+
+TextTable::Cell::Cell(const std::string& text)
+ : m_text(text),
+ m_right_align(false)
+{
+}
+
+TextTable::Cell::Cell(const char* text) : Cell(std::string(text))
+{
+}
+
+TextTable::Cell::Cell(const uint64_t number)
+ : m_text(fmt::format("{}", number)),
+ m_right_align(true)
+{
+}
+
+TextTable::Cell&
+TextTable::Cell::left_align()
+{
+ m_right_align = false;
+ return *this;
+}
+
+TextTable::Cell&
+TextTable::Cell::colspan(const size_t columns)
+{
+ ASSERT(columns >= 1);
+ m_colspan = columns;
+ return *this;
+}
+
+TextTable::Cell&
+TextTable::Cell::right_align()
+{
+ m_right_align = true;
+ return *this;
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021-2022 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 <cstdint>
+#include <string>
+#include <vector>
+
+namespace util {
+
+class TextTable
+{
+public:
+ class Cell
+ {
+ public:
+ Cell(const std::string& text);
+ Cell(const char* text);
+ Cell(uint64_t number);
+
+ Cell& colspan(size_t columns);
+ Cell& left_align();
+ Cell& right_align();
+
+ private:
+ friend TextTable;
+
+ const std::string m_text;
+ bool m_right_align = false;
+ bool m_heading = false;
+ size_t m_colspan = 1;
+ };
+
+ void add_heading(const std::string& text);
+ void add_row(const std::vector<Cell>& cells);
+ std::string render() const;
+
+private:
+ std::vector<std::vector<Cell>> m_rows;
+ size_t m_columns = 0;
+
+ std::vector<size_t> compute_column_widths() const;
+};
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2022 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 "TimePoint.hpp"
+
+#include <chrono>
+
+namespace util {
+
+TimePoint
+TimePoint::now()
+{
+ return TimePoint(0,
+ std::chrono::time_point_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now())
+ .time_since_epoch()
+ .count());
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2022 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 <util/Duration.hpp>
+
+#include <cstdint>
+#include <ctime>
+
+namespace util {
+
+class TimePoint
+{
+public:
+ explicit TimePoint(int64_t sec = 0, int64_t nsec = 0);
+ TimePoint(const TimePoint& other);
+ explicit TimePoint(const timespec& timespec);
+
+ TimePoint& operator=(const TimePoint& other);
+
+ static TimePoint now();
+
+ timespec to_timespec() const;
+
+ int64_t sec() const;
+ int64_t nsec() const;
+ int32_t nsec_decimal_part() const;
+
+ void set_sec(int64_t sec, uint32_t nsec = 0);
+ void set_nsec(int64_t nsec);
+
+ bool operator==(const TimePoint& other) const;
+ bool operator!=(const TimePoint& other) const;
+ bool operator<(const TimePoint& other) const;
+ bool operator>(const TimePoint& other) const;
+ bool operator<=(const TimePoint& other) const;
+ bool operator>=(const TimePoint& other) const;
+
+ TimePoint operator+(const util::Duration& duration) const;
+ TimePoint operator-(const util::Duration& duration) const;
+
+ util::Duration operator-(const TimePoint& other) const;
+
+private:
+ int64_t m_ns = 0;
+};
+
+inline TimePoint::TimePoint(int64_t sec, int64_t nsec)
+ : m_ns(1'000'000'000 * sec + nsec)
+{
+}
+
+inline TimePoint::TimePoint(const TimePoint& other) : m_ns(other.m_ns)
+{
+}
+
+inline TimePoint::TimePoint(const timespec& timespec)
+ : TimePoint(timespec.tv_sec, timespec.tv_nsec)
+{
+}
+
+inline TimePoint&
+TimePoint::operator=(const TimePoint& other)
+{
+ m_ns = other.m_ns;
+ return *this;
+}
+
+inline timespec
+TimePoint::to_timespec() const
+{
+ return {static_cast<time_t>(sec()), nsec_decimal_part()};
+}
+
+inline int64_t
+TimePoint::sec() const
+{
+ return m_ns / 1'000'000'000;
+}
+
+inline int64_t
+TimePoint::nsec() const
+{
+ return m_ns;
+}
+
+inline int32_t
+TimePoint::nsec_decimal_part() const
+{
+ return m_ns % 1'000'000'000;
+}
+
+inline void
+TimePoint::set_sec(int64_t sec, uint32_t nsec)
+{
+ m_ns = 1'000'000'000 * sec + nsec;
+}
+
+inline void
+TimePoint::set_nsec(int64_t nsec)
+{
+ m_ns = nsec;
+}
+
+inline bool
+TimePoint::operator==(const TimePoint& other) const
+{
+ return m_ns == other.m_ns;
+}
+
+inline bool
+TimePoint::operator!=(const TimePoint& other) const
+{
+ return m_ns != other.m_ns;
+}
+
+inline bool
+TimePoint::operator<(const TimePoint& other) const
+{
+ return m_ns < other.m_ns;
+}
+
+inline bool
+TimePoint::operator>(const TimePoint& other) const
+{
+ return m_ns > other.m_ns;
+}
+
+inline bool
+TimePoint::operator<=(const TimePoint& other) const
+{
+ return m_ns <= other.m_ns;
+}
+
+inline bool
+TimePoint::operator>=(const TimePoint& other) const
+{
+ return m_ns >= other.m_ns;
+}
+
+inline TimePoint
+TimePoint::operator+(const util::Duration& duration) const
+{
+ return TimePoint(0, nsec() + duration.nsec());
+}
+
+inline TimePoint
+TimePoint::operator-(const util::Duration& duration) const
+{
+ return TimePoint(0, nsec() - duration.nsec());
+}
+
+inline util::Duration
+TimePoint::operator-(const TimePoint& other) const
+{
+ return util::Duration(0, nsec() - other.nsec());
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021 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 <chrono>
+#include <string>
+
+class Timer
+{
+public:
+ Timer();
+
+ double measure_s() const;
+ double measure_ms() const;
+
+private:
+ std::chrono::steady_clock::time_point m_start;
+};
+
+inline Timer::Timer() : m_start(std::chrono::steady_clock::now())
+{
+}
+
+inline double
+Timer::measure_s() const
+{
+ using namespace std::chrono;
+ return duration_cast<duration<double>>(steady_clock::now() - m_start).count();
+}
+
+inline double
+Timer::measure_ms() const
+{
+ return measure_s() * 1000;
+}
--- /dev/null
+// Copyright (C) 2021-2022 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 "Tokenizer.hpp"
+
+namespace util {
+
+void
+Tokenizer::Iterator::advance(bool initial)
+{
+ constexpr auto npos = std::string_view::npos;
+ const auto string = m_tokenizer.m_string;
+ const auto delimiters = m_tokenizer.m_delimiters;
+ const auto mode = m_tokenizer.m_mode;
+
+ DEBUG_ASSERT(m_left <= m_right);
+ DEBUG_ASSERT(m_right <= string.length());
+
+ do {
+ if (initial) {
+ initial = false;
+ } else if (m_right == string.length()) {
+ m_left = npos;
+ } else {
+ m_left = m_right + 1;
+ }
+ if (m_left != npos) {
+ const auto delim_pos = string.find_first_of(delimiters, m_left);
+ m_right = delim_pos == npos ? string.length() : delim_pos;
+ }
+ } while (mode == Mode::skip_empty && m_left == m_right);
+}
+
+std::string_view
+Tokenizer::Iterator::operator*() const
+{
+ DEBUG_ASSERT(m_left <= m_right);
+ DEBUG_ASSERT(m_right <= m_tokenizer.m_string.length());
+ const bool include_delim =
+ m_tokenizer.m_include_delimiter == IncludeDelimiter::yes;
+ const int with_delim =
+ include_delim && m_right < m_tokenizer.m_string.length() ? 1 : 0;
+ return m_tokenizer.m_string.substr(m_left, m_right - m_left + with_delim);
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021-2022 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 <assertions.hpp>
+
+#include <third_party/fmt/core.h>
+
+#include <string_view>
+
+namespace util {
+
+// An instance of this class can be used in a range-based for loop to split a
+// string into tokens at any of the characters in a string of delimiters.
+class Tokenizer
+{
+public:
+ enum class Mode {
+ include_empty, // Include empty tokens.
+ skip_empty, // Skip empty tokens.
+ };
+
+ enum class IncludeDelimiter { no, yes };
+
+ // Split `string` into tokens at any of the characters in `separators` which
+ // must neither be the empty string nor a nullptr.
+ Tokenizer(std::string_view string,
+ const char* delimiters,
+ Mode mode = Mode::skip_empty,
+ IncludeDelimiter include_delimiter = IncludeDelimiter::no);
+
+ class Iterator
+ {
+ public:
+ Iterator(const Tokenizer& tokenizer, size_t start_pos);
+
+ Iterator operator++();
+ bool operator!=(const Iterator& other) const;
+ std::string_view operator*() const;
+
+ private:
+ const Tokenizer& m_tokenizer;
+ size_t m_left;
+ size_t m_right;
+
+ void advance(bool initial);
+ };
+
+ Iterator begin();
+ Iterator end();
+
+private:
+ friend Iterator;
+
+ const std::string_view m_string;
+ const char* const m_delimiters;
+ const Mode m_mode;
+ const IncludeDelimiter m_include_delimiter;
+};
+
+inline Tokenizer::Tokenizer(const std::string_view string,
+ const char* const delimiters,
+ Tokenizer::Mode mode,
+ Tokenizer::IncludeDelimiter include_delimiter)
+ : m_string(string),
+ m_delimiters(delimiters),
+ m_mode(mode),
+ m_include_delimiter(include_delimiter)
+{
+ DEBUG_ASSERT(delimiters != nullptr && delimiters[0] != '\0');
+}
+
+inline Tokenizer::Iterator::Iterator(const Tokenizer& tokenizer,
+ const size_t start_pos)
+ : m_tokenizer(tokenizer),
+ m_left(start_pos),
+ m_right(start_pos)
+{
+ if (start_pos == 0) {
+ advance(true);
+ } else {
+ DEBUG_ASSERT(start_pos == std::string_view::npos);
+ }
+}
+
+inline Tokenizer::Iterator
+Tokenizer::Iterator::operator++()
+{
+ advance(false);
+ return *this;
+}
+
+inline bool
+Tokenizer::Iterator::operator!=(const Iterator& other) const
+{
+ return &m_tokenizer != &other.m_tokenizer || m_left != other.m_left;
+}
+
+inline Tokenizer::Iterator
+Tokenizer::begin()
+{
+ return Iterator(*this, 0);
+}
+
+inline Tokenizer::Iterator
+Tokenizer::end()
+{
+ return Iterator(*this, std::string_view::npos);
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021-2022 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 <Util.hpp>
+#include <util/Bytes.hpp>
+
+#include <third_party/nonstd/span.hpp>
+#ifdef USE_XXH_DISPATCH
+# include <third_party/xxh_x86dispatch.h>
+#else
+# include <third_party/xxhash.h>
+#endif
+
+#include <cstdint>
+#include <cstring>
+
+namespace util {
+
+class XXH3_128
+{
+public:
+ static constexpr size_t k_digest_size = 16;
+
+ XXH3_128();
+ ~XXH3_128();
+
+ void reset();
+ void update(nonstd::span<const uint8_t> data);
+ util::Bytes digest() const;
+
+private:
+ XXH3_state_t* m_state;
+};
+
+inline XXH3_128::XXH3_128() : m_state(XXH3_createState())
+{
+ reset();
+}
+
+inline XXH3_128::~XXH3_128()
+{
+ XXH3_freeState(m_state);
+}
+
+inline void
+XXH3_128::reset()
+{
+ XXH3_128bits_reset(m_state);
+}
+
+inline void
+XXH3_128::update(nonstd::span<const uint8_t> data)
+{
+ XXH3_128bits_update(m_state, data.data(), data.size());
+}
+
+inline util::Bytes
+XXH3_128::digest() const
+{
+ const auto result = XXH3_128bits_digest(m_state);
+ util::Bytes digest(k_digest_size);
+ Util::int_to_big_endian(result.high64, &digest[0]);
+ Util::int_to_big_endian(result.low64, &digest[8]);
+ return digest;
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2019-2021 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
+
+#ifdef USE_XXH_DISPATCH
+# include "third_party/xxh_x86dispatch.h"
+#else
+# include "third_party/xxhash.h"
+#endif
+
+#include <cstdint>
+
+namespace util {
+
+class XXH3_64
+{
+public:
+ XXH3_64();
+ ~XXH3_64();
+
+ void reset();
+ void update(const void* data, size_t length);
+ uint64_t digest() const;
+
+private:
+ XXH3_state_t* m_state;
+};
+
+inline XXH3_64::XXH3_64() : m_state(XXH3_createState())
+{
+ reset();
+}
+
+inline XXH3_64::~XXH3_64()
+{
+ XXH3_freeState(m_state);
+}
+
+inline void
+XXH3_64::reset()
+{
+ XXH3_64bits_reset(m_state);
+}
+
+inline void
+XXH3_64::update(const void* data, size_t length)
+{
+ XXH3_64bits_update(m_state, data, length);
+}
+
+inline uint64_t
+XXH3_64::digest() const
+{
+ return XXH3_64bits_digest(m_state);
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021-2022 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 <fmtmacros.hpp>
+
+#include <string_view>
+#include <utility>
+
+namespace util {
+
+// --- Interface ---
+
+// Return value of `value` (where `T` typically is `nonstd::expected`) or throw
+// an exception of type `E` with a `T::error_type` as the argument.
+template<typename E, typename T>
+typename T::value_type value_or_throw(const T& value);
+template<typename E, typename T>
+typename T::value_type value_or_throw(T&& value);
+
+// Like above for with `prefix` added to the error message.
+template<typename E, typename T>
+typename T::value_type value_or_throw(const T& value, std::string_view prefix);
+template<typename E, typename T>
+typename T::value_type value_or_throw(T&& value, std::string_view prefix);
+
+// Throw an exception of type `E` with a `T::error_type` as the argument if
+// `value` is false.
+template<typename E, typename T> void throw_on_error(const T& value);
+
+// Like above for with `prefix` added to the error message.
+template<typename E, typename T>
+void throw_on_error(const T& value, std::string_view prefix);
+
+#define TRY(x_) \
+ do { \
+ const auto result = x_; \
+ if (!result) { \
+ return nonstd::make_unexpected(result.error()); \
+ } \
+ } while (false)
+
+// --- Inline implementations ---
+
+template<typename E, typename T>
+inline typename T::value_type
+value_or_throw(const T& value)
+{
+ if (value) {
+ return *value;
+ } else {
+ throw E(value.error());
+ }
+}
+
+template<typename E, typename T>
+inline typename T::value_type
+value_or_throw(T&& value)
+{
+ if (value) {
+ return std::move(*value);
+ } else {
+ throw E(value.error());
+ }
+}
+
+template<typename E, typename T>
+inline typename T::value_type
+value_or_throw(const T& value, std::string_view prefix)
+{
+ if (value) {
+ return *value;
+ } else {
+ throw E(FMT("{}{}", prefix, value.error()));
+ }
+}
+
+template<typename E, typename T>
+inline typename T::value_type
+value_or_throw(T&& value, std::string_view prefix)
+{
+ if (value) {
+ return std::move(*value);
+ } else {
+ throw E(FMT("{}{}", prefix, value.error()));
+ }
+}
+
+template<typename E, typename T>
+inline void
+throw_on_error(const T& value)
+{
+ if (!value) {
+ throw E(value.error());
+ }
+}
+
+// Like above for with `prefix` added to the error message.
+template<typename E, typename T>
+inline void
+throw_on_error(const T& value, std::string_view prefix)
+{
+ if (!value) {
+ throw E(FMT("{}{}", prefix, value.error()));
+ }
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021-2022 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 "file.hpp"
+
+#include <Fd.hpp>
+#include <Logging.hpp>
+#include <Stat.hpp>
+#include <Win32Util.hpp>
+#include <fmtmacros.hpp>
+#include <util/Bytes.hpp>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_UTIMENSAT
+# include <fcntl.h>
+# include <sys/stat.h>
+#elif defined(HAVE_UTIMES)
+# include <sys/time.h>
+#else
+# include <sys/types.h>
+# ifdef HAVE_UTIME_H
+# include <utime.h>
+# elif defined(HAVE_SYS_UTIME_H)
+# include <sys/utime.h>
+# endif
+#endif
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <locale>
+#include <type_traits>
+
+namespace util {
+
+void
+create_cachedir_tag(const std::string& dir)
+{
+ constexpr char cachedir_tag[] =
+ "Signature: 8a477f597d28d172789f06886806bc55\n"
+ "# This file is a cache directory tag created by ccache.\n"
+ "# For information about cache directory tags, see:\n"
+ "#\thttp://www.brynosaurus.com/cachedir/\n";
+
+ const std::string path = FMT("{}/CACHEDIR.TAG", dir);
+ const auto stat = Stat::stat(path);
+ if (stat) {
+ return;
+ }
+ const auto result = util::write_file(path, cachedir_tag);
+ if (!result) {
+ LOG("Failed to create {}: {}", path, result.error());
+ }
+}
+
+nonstd::expected<void, std::string>
+read_fd(int fd, DataReceiver data_receiver)
+{
+ int64_t n;
+ uint8_t buffer[CCACHE_READ_BUFFER_SIZE];
+ while ((n = read(fd, buffer, sizeof(buffer))) != 0) {
+ if (n == -1 && errno != EINTR) {
+ break;
+ }
+ if (n > 0) {
+ data_receiver(buffer, n);
+ }
+ }
+ if (n == -1) {
+ return nonstd::make_unexpected(strerror(errno));
+ }
+ return {};
+}
+
+#ifdef _WIN32
+static bool
+has_utf16_le_bom(std::string_view text)
+{
+ return text.size() > 1
+ && ((static_cast<uint8_t>(text[0]) == 0xff
+ && static_cast<uint8_t>(text[1]) == 0xfe));
+}
+#endif
+
+template<typename T>
+nonstd::expected<T, std::string>
+read_file(const std::string& path, size_t size_hint)
+{
+ if (size_hint == 0) {
+ const auto stat = Stat::stat(path);
+ if (!stat) {
+ return nonstd::make_unexpected(strerror(errno));
+ }
+ size_hint = stat.size();
+ }
+
+ // +1 to be able to detect EOF in the first read call
+ size_hint = (size_hint < 1024) ? 1024 : size_hint + 1;
+
+ const int open_flags = [] {
+ if constexpr (std::is_same<T, std::string>::value) {
+ return O_RDONLY | O_TEXT;
+ } else {
+ return O_RDONLY | O_BINARY;
+ }
+ }();
+ Fd fd(open(path.c_str(), open_flags));
+ if (!fd) {
+ return nonstd::make_unexpected(strerror(errno));
+ }
+
+ int64_t ret = 0;
+ size_t pos = 0;
+ T result;
+ result.resize(size_hint);
+
+ while (true) {
+ if (pos == result.size()) {
+ result.resize(2 * result.size());
+ }
+ const size_t max_read = result.size() - pos;
+ ret = read(*fd, &result[pos], max_read);
+ if (ret == 0 || (ret == -1 && errno != EINTR)) {
+ break;
+ }
+ if (ret > 0) {
+ pos += ret;
+ if (static_cast<size_t>(ret) < max_read) {
+ break;
+ }
+ }
+ }
+
+ if (ret == -1) {
+ return nonstd::make_unexpected(strerror(errno));
+ }
+
+ result.resize(pos);
+
+#ifdef _WIN32
+ if constexpr (std::is_same<T, std::string>::value) {
+ // Convert to UTF-8 if the content starts with a UTF-16 little-endian BOM.
+ if (has_utf16_le_bom(result)) {
+ result.erase(0, 2); // Remove BOM.
+ if (result.empty()) {
+ return result;
+ }
+
+ std::wstring result_as_u16((result.size() / 2) + 1, '\0');
+ result_as_u16 = reinterpret_cast<const wchar_t*>(result.c_str());
+ const int size = WideCharToMultiByte(CP_UTF8,
+ WC_ERR_INVALID_CHARS,
+ result_as_u16.c_str(),
+ int(result_as_u16.size()),
+ nullptr,
+ 0,
+ nullptr,
+ nullptr);
+ if (size <= 0) {
+ return nonstd::make_unexpected(
+ FMT("Failed to convert {} from UTF-16LE to UTF-8: {}",
+ path,
+ Win32Util::error_message(GetLastError())));
+ }
+
+ result = std::string(size, '\0');
+ WideCharToMultiByte(CP_UTF8,
+ 0,
+ result_as_u16.c_str(),
+ int(result_as_u16.size()),
+ &result.at(0),
+ size,
+ nullptr,
+ nullptr);
+ }
+ }
+#endif
+
+ return result;
+}
+
+template nonstd::expected<util::Bytes, std::string>
+read_file(const std::string& path, size_t size_hint);
+
+template nonstd::expected<std::string, std::string>
+read_file(const std::string& path, size_t size_hint);
+
+template nonstd::expected<std::vector<uint8_t>, std::string>
+read_file(const std::string& path, size_t size_hint);
+
+template<typename T>
+nonstd::expected<T, std::string>
+read_file_part(const std::string& path, size_t pos, size_t count)
+{
+ Fd fd(open(path.c_str(), O_RDONLY | O_BINARY));
+ if (!fd) {
+ LOG("Failed to open {}: {}", path, strerror(errno));
+ return nonstd::make_unexpected(strerror(errno));
+ }
+
+ if (pos != 0 && lseek(*fd, pos, SEEK_SET) != static_cast<off_t>(pos)) {
+ return nonstd::make_unexpected(strerror(errno));
+ }
+
+ int64_t ret = 0;
+ size_t bytes_read = 0;
+ T result;
+ result.resize(count);
+
+ while (true) {
+ const size_t max_read = count - bytes_read;
+ ret = read(*fd, &result[bytes_read], max_read);
+ if (ret == 0 || (ret == -1 && errno != EINTR)) {
+ break;
+ }
+ if (ret > 0) {
+ bytes_read += ret;
+ if (bytes_read == count) {
+ break;
+ }
+ }
+ }
+
+ if (ret == -1) {
+ LOG("Failed to read {}: {}", path, strerror(errno));
+ return nonstd::make_unexpected(strerror(errno));
+ }
+
+ result.resize(bytes_read);
+ return result;
+}
+
+template nonstd::expected<util::Bytes, std::string>
+read_file_part(const std::string& path, size_t pos, size_t count);
+
+template nonstd::expected<std::vector<uint8_t>, std::string>
+read_file_part(const std::string& path, size_t pos, size_t count);
+
+void
+set_timestamps(const std::string& path,
+ std::optional<util::TimePoint> mtime,
+ std::optional<util::TimePoint> atime)
+{
+#ifdef HAVE_UTIMENSAT
+ timespec atime_mtime[2];
+ if (mtime) {
+ atime_mtime[0] = (atime ? *atime : *mtime).to_timespec();
+ atime_mtime[1] = mtime->to_timespec();
+ }
+ utimensat(AT_FDCWD, path.c_str(), mtime ? atime_mtime : nullptr, 0);
+#elif defined(HAVE_UTIMES)
+ timeval atime_mtime[2];
+ if (mtime) {
+ atime_mtime[0].tv_sec = atime ? atime->sec() : mtime->sec();
+ atime_mtime[0].tv_usec =
+ (atime ? atime->nsec_decimal_part() : mtime->nsec_decimal_part()) / 1000;
+ atime_mtime[1].tv_sec = mtime->sec();
+ atime_mtime[1].tv_usec = mtime->nsec_decimal_part() / 1000;
+ }
+ utimes(path.c_str(), mtime ? atime_mtime : nullptr);
+#else
+ utimbuf atime_mtime;
+ if (mtime) {
+ atime_mtime.actime = atime ? atime->sec() : mtime->sec();
+ atime_mtime.modtime = mtime->sec();
+ utime(path.c_str(), &atime_mtime);
+ } else {
+ utime(path.c_str(), nullptr);
+ }
+#endif
+}
+
+nonstd::expected<void, std::string>
+write_fd(int fd, const void* data, size_t size)
+{
+ int64_t written = 0;
+ while (static_cast<size_t>(written) < size) {
+ const auto count =
+ write(fd, static_cast<const uint8_t*>(data) + written, size - written);
+ if (count == -1) {
+ if (errno != EAGAIN && errno != EINTR) {
+ return nonstd::make_unexpected(strerror(errno));
+ }
+ } else {
+ written += count;
+ }
+ }
+ return {};
+}
+
+nonstd::expected<void, std::string>
+write_file(const std::string& path, std::string_view data, InPlace in_place)
+{
+ if (in_place == InPlace::no) {
+ unlink(path.c_str());
+ }
+ Fd fd(open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_TEXT, 0666));
+ if (!fd) {
+ return nonstd::make_unexpected(strerror(errno));
+ }
+ return write_fd(*fd, data.data(), data.size());
+}
+
+nonstd::expected<void, std::string>
+write_file(const std::string& path,
+ nonstd::span<const uint8_t> data,
+ InPlace in_place)
+{
+ if (in_place == InPlace::no) {
+ unlink(path.c_str());
+ }
+ Fd fd(open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666));
+ if (!fd) {
+ return nonstd::make_unexpected(strerror(errno));
+ }
+ return write_fd(*fd, data.data(), data.size());
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021-2022 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 <util/TimePoint.hpp>
+#include <util/types.hpp>
+
+#include <third_party/nonstd/expected.hpp>
+#include <third_party/nonstd/span.hpp>
+
+#include <cstddef>
+#include <cstdint>
+#include <ctime>
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace util {
+
+// --- Interface ---
+
+enum class InPlace { yes, no };
+
+void create_cachedir_tag(const std::string& dir);
+
+// Read data from `fd` until end of file and call `data_receiver` with the read
+// data. Returns an error if the underlying read(2) call returned -1.
+nonstd::expected<void, std::string> read_fd(int fd, DataReceiver data_receiver);
+
+// Return contents of file at `path`.
+//
+// `T` should be `util::Bytes` or `std::vector<uint8_t>` for binary data and
+// `std::string` for text data. If `T` is `std::string` and the content starts
+// with a UTF-16 little-endian BOM on Windows then it will be converted to
+// UTF-8.
+//
+// If `size_hint` is not 0 then it is assumed that `path` has this size (this
+// saves system calls).
+template<typename T>
+nonstd::expected<T, std::string> read_file(const std::string& path,
+ size_t size_hint = 0);
+
+// Return (at most) `count` bytes from `path` starting at position `pos`.
+//
+// `T` should be `util::Bytes` or `std::vector<uint8_t>`. If `T` is
+// `std::string` and the content starts with a UTF-16 little-endian BOM on
+// Windows then it will be converted to UTF-8.
+template<typename T>
+nonstd::expected<T, std::string>
+read_file_part(const std::string& path, size_t pos, size_t count);
+
+// Set atime/mtime of `path`. If `mtime` is std::nullopt, set to the current
+// time. If `atime` is std::nullopt, set to what `mtime` specifies.
+void set_timestamps(const std::string& path,
+ std::optional<util::TimePoint> mtime = std::nullopt,
+ std::optional<util::TimePoint> atime = std::nullopt);
+
+// Write `size` bytes from binary `data` to `fd`.
+nonstd::expected<void, std::string>
+write_fd(int fd, const void* data, size_t size);
+
+// Write text `data` to `path`. If `in_place` is no, unlink any existing file
+// first (i.e., break hard links).
+nonstd::expected<void, std::string> write_file(const std::string& path,
+ std::string_view data,
+ InPlace in_place = InPlace::no);
+
+// Write binary `data` to `path`. If `in_place` is no, unlink any existing
+// file first (i.e., break hard links).
+nonstd::expected<void, std::string> write_file(const std::string& path,
+ nonstd::span<const uint8_t> data,
+ InPlace in_place = InPlace::no);
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021-2022 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 "path.hpp"
+
+#include <Util.hpp>
+#include <fmtmacros.hpp>
+
+#ifdef _WIN32
+const char k_dev_null_path[] = "nul:";
+const char k_path_delimiter[] = ";";
+#else
+const char k_dev_null_path[] = "/dev/null";
+const char k_path_delimiter[] = ":";
+#endif
+
+namespace util {
+
+const char*
+get_dev_null_path()
+{
+ return k_dev_null_path;
+}
+
+bool
+is_absolute_path(std::string_view path)
+{
+#ifdef _WIN32
+ if (path.length() >= 2 && path[1] == ':'
+ && (path[2] == '/' || path[2] == '\\')) {
+ return true;
+ }
+#endif
+ return !path.empty() && path[0] == '/';
+}
+
+bool
+path_starts_with(std::string_view path, std::string_view prefix)
+{
+ if (path.empty()) {
+ return false;
+ }
+ for (size_t i = 0, j = 0; i < path.length() && j < prefix.length();
+ ++i, ++j) {
+#ifdef _WIN32
+ // Skip escaped backslashes \\\\ as seen by the preprocessor.
+ if (i > 0 && path[i] == '\\' && path[i - 1] == '\\') {
+ ++i;
+ }
+ if (j > 0 && prefix[j] == '\\' && prefix[j - 1] == '\\') {
+ ++j;
+ }
+
+ // Handle back and forward slashes as equal.
+ if (path[i] == '/' && prefix[j] == '\\') {
+ continue;
+ }
+ if (path[i] == '\\' && prefix[j] == '/') {
+ continue;
+ }
+#endif
+ if (path[i] != prefix[j]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+std::vector<std::string>
+split_path_list(std::string_view path_list)
+{
+ return Util::split_into_strings(path_list, k_path_delimiter);
+}
+
+std::string
+to_absolute_path(std::string_view path)
+{
+ if (util::is_absolute_path(path)) {
+ return std::string(path);
+ } else {
+ return Util::normalize_abstract_absolute_path(
+ FMT("{}/{}", Util::get_actual_cwd(), path));
+ }
+}
+
+std::string
+to_absolute_path_no_drive(std::string_view path)
+{
+ std::string abs_path = to_absolute_path(path);
+#ifdef _WIN32
+ if (abs_path.length() >= 2 && abs_path[1] == ':') {
+ abs_path.erase(0, 2);
+ }
+#endif
+ return abs_path;
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021-2022 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 <string>
+#include <string_view>
+#include <vector>
+
+namespace util {
+
+// --- Interface ---
+
+const char* get_dev_null_path();
+
+// Return whether `path` is absolute.
+bool is_absolute_path(std::string_view path);
+
+// Return whether `path` includes at least one directory separator.
+bool is_full_path(std::string_view path);
+
+// Return whether `path` starts with `prefix` considering path specifics on
+// Windows
+bool path_starts_with(std::string_view path, std::string_view prefix);
+
+// Split a list of paths (such as the content of $PATH on Unix platforms or
+// %PATH% on Windows platforms) into paths.
+std::vector<std::string> split_path_list(std::string_view path_list);
+
+// Make `path` an absolute path.
+std::string to_absolute_path(std::string_view path);
+
+// Make `path` an absolute path, but do not include Windows drive.
+std::string to_absolute_path_no_drive(std::string_view path);
+
+// --- Inline implementations ---
+
+inline bool
+is_full_path(const std::string_view path)
+{
+#ifdef _WIN32
+ if (path.find('\\') != std::string_view::npos) {
+ return true;
+ }
+#endif
+ return path.find('/') != std::string_view::npos;
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021-2022 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 "string.hpp"
+
+#include <assertions.hpp>
+#include <fmtmacros.hpp>
+
+#include <algorithm>
+#include <cctype>
+#include <iostream>
+
+namespace util {
+
+nonstd::expected<double, std::string>
+parse_double(const std::string& value)
+{
+ size_t end;
+ double result;
+ bool failed = false;
+ try {
+ result = std::stod(value, &end);
+ } catch (const std::exception&) {
+ failed = true;
+ }
+
+ if (failed || end != value.size()) {
+ return nonstd::make_unexpected(
+ FMT("invalid floating point: \"{}\"", value));
+ } else {
+ return result;
+ }
+}
+
+nonstd::expected<int64_t, std::string>
+parse_signed(std::string_view value,
+ const std::optional<int64_t> min_value,
+ const std::optional<int64_t> max_value,
+ const std::string_view description)
+{
+ const std::string stripped_value = strip_whitespace(value);
+
+ size_t end = 0;
+ long long result = 0;
+ bool failed = false;
+ try {
+ // Note: sizeof(long long) is guaranteed to be >= sizeof(int64_t)
+ result = std::stoll(stripped_value, &end, 10);
+ } catch (std::exception&) {
+ failed = true;
+ }
+ if (failed || end != stripped_value.size()) {
+ return nonstd::make_unexpected(
+ FMT("invalid integer: \"{}\"", stripped_value));
+ }
+
+ const int64_t min = min_value ? *min_value : INT64_MIN;
+ const int64_t max = max_value ? *max_value : INT64_MAX;
+ if (result < min || result > max) {
+ return nonstd::make_unexpected(
+ FMT("{} must be between {} and {}", description, min, max));
+ } else {
+ return result;
+ }
+}
+
+nonstd::expected<mode_t, std::string>
+parse_umask(std::string_view value)
+{
+ return util::parse_unsigned(value, 0, 0777, "umask", 8);
+}
+
+nonstd::expected<uint64_t, std::string>
+parse_unsigned(std::string_view value,
+ const std::optional<uint64_t> min_value,
+ const std::optional<uint64_t> max_value,
+ const std::string_view description,
+ const int base)
+{
+ const std::string stripped_value = strip_whitespace(value);
+
+ size_t end = 0;
+ unsigned long long result = 0;
+ bool failed = false;
+ if (starts_with(stripped_value, "-")) {
+ failed = true;
+ } else {
+ try {
+ // Note: sizeof(unsigned long long) is guaranteed to be >=
+ // sizeof(uint64_t)
+ result = std::stoull(stripped_value, &end, base);
+ } catch (std::exception&) {
+ failed = true;
+ }
+ }
+ if (failed || end != stripped_value.size()) {
+ const auto base_info = base == 8 ? "octal " : "";
+ return nonstd::make_unexpected(
+ FMT("invalid unsigned {}integer: \"{}\"", base_info, stripped_value));
+ }
+
+ const uint64_t min = min_value ? *min_value : 0;
+ const uint64_t max = max_value ? *max_value : UINT64_MAX;
+ if (result < min || result > max) {
+ return nonstd::make_unexpected(
+ FMT("{} must be between {} and {}", description, min, max));
+ } else {
+ return result;
+ }
+}
+
+nonstd::expected<std::string, std::string>
+percent_decode(std::string_view string)
+{
+ const auto from_hex = [](const char digit) {
+ return static_cast<uint8_t>(
+ std::isdigit(digit) ? digit - '0' : std::tolower(digit) - 'a' + 10);
+ };
+
+ std::string result;
+ result.reserve(string.size());
+ size_t i = 0;
+ while (i < string.size()) {
+ if (string[i] != '%') {
+ result += string[i];
+ } else if (i + 2 >= string.size() || !std::isxdigit(string[i + 1])
+ || !std::isxdigit(string[i + 2])) {
+ return nonstd::make_unexpected(
+ FMT("invalid percent-encoded string at position {}: {}", i, string));
+ } else {
+ const char ch = static_cast<char>(from_hex(string[i + 1]) << 4
+ | from_hex(string[i + 2]));
+ result += ch;
+ i += 2;
+ }
+ ++i;
+ }
+
+ return result;
+}
+
+std::string
+replace_all(const std::string_view string,
+ const std::string_view from,
+ const std::string_view to)
+{
+ if (from.empty()) {
+ return std::string(string);
+ }
+
+ std::string result;
+ size_t left = 0;
+ size_t right = 0;
+ while (left < string.size()) {
+ right = string.find(from, left);
+ if (right == std::string_view::npos) {
+ result.append(string.data() + left);
+ break;
+ }
+ result.append(string.data() + left, right - left);
+ result.append(to.data(), to.size());
+ left = right + from.size();
+ }
+ return result;
+}
+
+std::string
+replace_first(const std::string_view string,
+ const std::string_view from,
+ const std::string_view to)
+{
+ if (from.empty()) {
+ return std::string(string);
+ }
+
+ std::string result;
+ const auto pos = string.find(from);
+ if (pos != std::string_view::npos) {
+ result.append(string.data(), pos);
+ result.append(to.data(), to.length());
+ result.append(string.data() + pos + from.size());
+ } else {
+ result = std::string(string);
+ }
+ return result;
+}
+
+std::pair<std::string_view, std::optional<std::string_view>>
+split_once(const std::string_view string, const char split_char)
+{
+ const size_t sep_pos = string.find(split_char);
+ if (sep_pos == std::string_view::npos) {
+ return std::make_pair(string, std::nullopt);
+ } else {
+ return std::make_pair(string.substr(0, sep_pos),
+ string.substr(sep_pos + 1));
+ }
+}
+
+std::string
+strip_whitespace(const std::string_view string)
+{
+ const auto is_space = [](const int ch) { return std::isspace(ch); };
+ const auto start = std::find_if_not(string.begin(), string.end(), is_space);
+ const auto end =
+ std::find_if_not(string.rbegin(), string.rend(), is_space).base();
+ return start < end ? std::string(start, end) : std::string();
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2021-2022 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 <util/Bytes.hpp>
+
+#include <third_party/nonstd/expected.hpp>
+#include <third_party/nonstd/span.hpp>
+
+#include <sys/stat.h> // for mode_t
+
+#include <cstdint>
+#include <cstring>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace util {
+
+// --- Interface ---
+
+// Return true if `suffix` is a suffix of `string`.
+bool ends_with(std::string_view string, std::string_view suffix);
+
+// Join stringified elements of `container` delimited by `delimiter` into a
+// string. There must exist an `std::string to_string(T::value_type)` function.
+template<typename T>
+std::string join(const T& container, const std::string_view delimiter);
+
+// Join stringified elements between input iterators `begin` and `end` delimited
+// by `delimiter` into a string. There must exist an `std::string
+// to_string(T::value_type)` function.
+template<typename T>
+std::string
+join(const T& begin, const T& end, const std::string_view delimiter);
+
+// Parse a string into a double.
+//
+// Returns an error string if `value` cannot be parsed as a double.
+nonstd::expected<double, std::string> parse_double(const std::string& value);
+
+// Parse a string into a signed integer.
+//
+// Returns an error string if `value` cannot be parsed as an int64_t or if the
+// value falls out of the range [`min_value`, `max_value`]. `min_value` and
+// `max_value` default to min and max values of int64_t. `description` is
+// included in the error message for range violations.
+nonstd::expected<int64_t, std::string>
+parse_signed(std::string_view value,
+ std::optional<int64_t> min_value = std::nullopt,
+ std::optional<int64_t> max_value = std::nullopt,
+ std::string_view description = "integer");
+
+// Parse `value` (an octal integer).
+nonstd::expected<mode_t, std::string> parse_umask(std::string_view value);
+
+// Parse a string into an unsigned integer.
+//
+// Returns an error string if `value` cannot be parsed as an uint64_t with base
+// `base`, or if the value falls out of the range [`min_value`, `max_value`].
+// `min_value` and `max_value` default to min and max values of uint64_t.
+// `description` is included in the error message for range violations.
+nonstd::expected<uint64_t, std::string>
+parse_unsigned(std::string_view value,
+ std::optional<uint64_t> min_value = std::nullopt,
+ std::optional<uint64_t> max_value = std::nullopt,
+ std::string_view description = "integer",
+ int base = 10);
+
+// Percent-decode[1] `string`.
+//
+// [1]: https://en.wikipedia.org/wiki/Percent-encoding
+nonstd::expected<std::string, std::string>
+percent_decode(std::string_view string);
+
+// Replace the all occurrences of `from` to `to` in `string`.
+std::string replace_all(std::string_view string,
+ std::string_view from,
+ std::string_view to);
+
+// Replace the first occurrence of `from` to `to` in `string`.
+std::string replace_first(std::string_view string,
+ std::string_view from,
+ std::string_view to);
+
+// Split `string` into two parts using `split_char` as the delimiter. The second
+// part will be `nullopt` if there is no `split_char` in `string.`
+std::pair<std::string_view, std::optional<std::string_view>>
+split_once(std::string_view string, char split_char);
+
+// Return true if `prefix` is a prefix of `string`.
+bool starts_with(const char* string, std::string_view prefix);
+
+// Return true if `prefix` is a prefix of `string`.
+bool starts_with(std::string_view string, std::string_view prefix);
+
+// Strip whitespace from left and right side of a string.
+[[nodiscard]] std::string strip_whitespace(std::string_view string);
+
+// Convert `value` to a `nonstd::span<const uint8_t>`.
+nonstd::span<const uint8_t> to_span(std::string_view value);
+
+// Convert `value` to a string. This function is used when joining
+// `std::string`s with `util::join`.
+template<typename T> std::string to_string(const T& value);
+
+// Convert `data` to a `std::string_view`.
+std::string_view to_string_view(nonstd::span<const uint8_t> data);
+
+// --- Inline implementations ---
+
+inline bool
+ends_with(const std::string_view string, const std::string_view suffix)
+{
+ return string.length() >= suffix.length()
+ && string.substr(string.length() - suffix.length()) == suffix;
+}
+
+template<typename T>
+inline std::string
+join(const T& container, const std::string_view delimiter)
+{
+ return join(container.begin(), container.end(), delimiter);
+}
+
+template<typename T>
+inline std::string
+join(const T& begin, const T& end, const std::string_view delimiter)
+{
+ std::string result;
+ for (auto it = begin; it != end; ++it) {
+ if (it != begin) {
+ result.append(delimiter.data(), delimiter.length());
+ }
+ result += to_string(*it);
+ }
+ return result;
+}
+
+inline bool
+starts_with(const char* const string, const std::string_view prefix)
+{
+ // Optimized version of starts_with(string_view, string_view): avoid computing
+ // the length of the string argument.
+ return std::strncmp(string, prefix.data(), prefix.length()) == 0;
+}
+
+inline bool
+starts_with(const std::string_view string, const std::string_view prefix)
+{
+ return string.substr(0, prefix.size()) == prefix;
+}
+
+inline nonstd::span<const uint8_t>
+to_span(std::string_view data)
+{
+ return nonstd::span<const uint8_t>(
+ reinterpret_cast<const uint8_t*>(data.data()), data.size());
+}
+
+template<typename T>
+inline std::string
+to_string(const T& t)
+{
+ using std::to_string;
+ return to_string(std::forward<T>(t));
+}
+
+template<>
+inline std::string
+to_string(const std::string& string)
+{
+ return std::string(string);
+}
+
+template<>
+inline std::string
+to_string(const std::string_view& sv)
+{
+ return std::string(sv);
+}
+
+template<>
+inline std::string
+to_string(const nonstd::span<const uint8_t>& bytes)
+{
+ return std::string(to_string_view(bytes));
+}
+
+template<>
+inline std::string
+to_string(const util::Bytes& bytes)
+{
+ return std::string(to_string_view(bytes));
+}
+
+inline std::string_view
+to_string_view(nonstd::span<const uint8_t> data)
+{
+ return std::string_view(reinterpret_cast<const char*>(data.data()),
+ data.size());
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2022 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 <cstddef>
+#include <cstdint>
+#include <functional>
+#include <vector>
+
+namespace util {
+
+using DataReceiver = std::function<void(const uint8_t* data, size_t size)>;
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2022 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 "zstd.hpp"
+
+#include <zstd.h>
+
+namespace util {
+
+nonstd::expected<void, std::string>
+zstd_compress(nonstd::span<const uint8_t> input,
+ util::Bytes& output,
+ int8_t compression_level)
+{
+ const size_t original_output_size = output.size();
+ const size_t compress_bound = zstd_compress_bound(input.size());
+ output.resize(original_output_size + compress_bound);
+
+ const size_t ret = ZSTD_compress(&output[original_output_size],
+ compress_bound,
+ input.data(),
+ input.size(),
+ compression_level);
+ if (ZSTD_isError(ret)) {
+ return nonstd::make_unexpected(ZSTD_getErrorName(ret));
+ }
+
+ output.resize(original_output_size + ret);
+ return {};
+}
+
+nonstd::expected<void, std::string>
+zstd_decompress(nonstd::span<const uint8_t> input,
+ util::Bytes& output,
+ size_t original_size)
+{
+ const size_t original_output_size = output.size();
+
+ output.resize(original_output_size + original_size);
+ const size_t ret = ZSTD_decompress(
+ &output[original_output_size], original_size, input.data(), input.size());
+ if (ZSTD_isError(ret)) {
+ return nonstd::make_unexpected(ZSTD_getErrorName(ret));
+ }
+
+ output.resize(original_output_size + ret);
+
+ return {};
+}
+
+size_t
+zstd_compress_bound(size_t input_size)
+{
+ return ZSTD_compressBound(input_size);
+}
+
+std::tuple<int8_t, std::string>
+zstd_supported_compression_level(int8_t wanted_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 && wanted_level < 1) {
+ return {1, "minimum level supported by libzstd"};
+ }
+
+ const int8_t level = std::min<int>(wanted_level, ZSTD_maxCLevel());
+ if (level != wanted_level) {
+ return {level, "max libzstd level"};
+ }
+
+ return {level, {}};
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2022 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 <util/Bytes.hpp>
+
+#include <third_party/nonstd/expected.hpp>
+#include <third_party/nonstd/span.hpp>
+
+#include <cstdint>
+#include <string>
+#include <tuple>
+
+namespace util {
+
+[[nodiscard]] nonstd::expected<void, std::string>
+zstd_compress(nonstd::span<const uint8_t> input,
+ util::Bytes& output,
+ int8_t compression_level);
+
+[[nodiscard]] nonstd::expected<void, std::string> zstd_decompress(
+ nonstd::span<const uint8_t> input, util::Bytes& output, size_t original_size);
+
+size_t zstd_compress_bound(size_t input_size);
+
+std::tuple<int8_t, std::string>
+zstd_supported_compression_level(int8_t wanted_level);
+
+} // namespace util
set_tests_properties(
"test.${name}"
PROPERTIES
- ENVIRONMENT "CCACHE=${CMAKE_BINARY_DIR}/ccache;EXIT_IF_SKIPPED=true")
-
- if(${CMAKE_VERSION} VERSION_LESS "3.9")
- # Older CMake versions treat skipped tests as errors. Therefore, resort to
- # parsing output for those cases (exit code is not considered). Skipped
- # tests will appear as "Passed".
- set_tests_properties(
- "test.${name}"
- PROPERTIES
- PASS_REGULAR_EXPRESSION "PASSED|Passed|Skipped"
- FAIL_REGULAR_EXPRESSION "[Ww]arning|[Ff]ail|[Er]rror")
- else()
- set_tests_properties("test.${name}" PROPERTIES SKIP_RETURN_CODE 125)
- endif()
-
+ ENVIRONMENT "CCACHE=$<TARGET_FILE:ccache>;EXIT_IF_SKIPPED=true"
+ SKIP_RETURN_CODE 125)
endfunction()
if(${CMAKE_VERSION} VERSION_LESS "3.15")
addtest(cache_levels)
addtest(cleanup)
addtest(color_diagnostics)
+addtest(config)
addtest(cpp1)
addtest(debug_prefix_map)
addtest(depend)
addtest(direct)
-addtest(direct_gcc)
addtest(fileclone)
addtest(hardlink)
addtest(inode_cache)
addtest(masquerading)
addtest(modules)
addtest(multi_arch)
+addtest(namespace)
addtest(no_compression)
addtest(nocpp2)
addtest(nvcc)
addtest(profiling_hip_clang)
addtest(readonly)
addtest(readonly_direct)
+addtest(remote_file)
+addtest(remote_http)
+addtest(remote_only)
+addtest(remote_redis)
+addtest(remote_redis_unix)
+addtest(remote_url)
addtest(sanitize_blacklist)
addtest(serialize_diagnostics)
addtest(source_date_epoch)
addtest(split_dwarf)
+addtest(stats_log)
+addtest(trim_dir)
addtest(upgrade)
--- /dev/null
+#!/usr/bin/env python3
+
+# This is a simple HTTP client to test readiness of the asynchronously
+# launched HTTP server.
+
+import sys
+import time
+import urllib.request
+
+
+def run(url, timeout, basic_auth):
+ deadline = time.time() + timeout
+ req = urllib.request.Request(url, method="HEAD")
+ if basic_auth:
+ import base64
+
+ encoded_credentials = base64.b64encode(
+ basic_auth.encode("ascii")
+ ).decode("ascii")
+ req.add_header("Authorization", f"Basic {encoded_credentials}")
+ while True:
+ try:
+ response = urllib.request.urlopen(req)
+ print(f"Connection successful (code: {response.getcode()})")
+ break
+ except urllib.error.URLError as e:
+ print(e.reason)
+ if time.time() > deadline:
+ print(
+ f"All connection attempts failed within {timeout} seconds."
+ )
+ sys.exit(1)
+ time.sleep(0.5)
+
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--basic-auth", "-B", help="Basic auth tuple like user:pass"
+ )
+ parser.add_argument(
+ "--timeout",
+ "-t",
+ metavar="TIMEOUT",
+ default=10,
+ type=int,
+ help="Maximum seconds to wait for successful connection attempt "
+ "[default: 10 seconds]",
+ )
+ parser.add_argument("url", type=str, help="URL to connect to")
+ args = parser.parse_args()
+
+ run(
+ url=args.url,
+ timeout=args.timeout,
+ basic_auth=args.basic_auth,
+ )
--- /dev/null
+#!/usr/bin/env python3
+
+# This is a simple HTTP server based on the HTTPServer and
+# SimpleHTTPRequestHandler. It has been extended with PUT
+# and DELETE functionality to store or delete results.
+#
+# See: https://github.com/python/cpython/blob/main/Lib/http/server.py
+
+from functools import partial
+from http import HTTPStatus
+from http.server import HTTPServer, SimpleHTTPRequestHandler
+import os
+import signal
+import socket
+import sys
+
+
+class AuthenticationError(Exception):
+ pass
+
+
+class PUTEnabledHTTPRequestHandler(SimpleHTTPRequestHandler):
+ def __init__(self, *args, basic_auth=None, **kwargs):
+ self.basic_auth = None
+ if basic_auth:
+ import base64
+
+ self.basic_auth = base64.b64encode(
+ basic_auth.encode("ascii")
+ ).decode("ascii")
+ super().__init__(*args, **kwargs)
+
+ def do_GET(self):
+ try:
+ self._handle_auth()
+ super().do_GET()
+ except AuthenticationError:
+ self.send_error(HTTPStatus.UNAUTHORIZED, "Need Authentication")
+
+ def do_HEAD(self):
+ try:
+ self._handle_auth()
+ super().do_HEAD()
+ except AuthenticationError:
+ self.send_error(HTTPStatus.UNAUTHORIZED, "Need Authentication")
+
+ def do_PUT(self):
+ path = self.translate_path(self.path)
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ try:
+ self._handle_auth()
+ file_length = int(self.headers["Content-Length"])
+ with open(path, "wb") as output_file:
+ output_file.write(self.rfile.read(file_length))
+ self.send_response(HTTPStatus.CREATED)
+ self.send_header("Content-Length", "0")
+ self.end_headers()
+ except AuthenticationError:
+ self.send_error(HTTPStatus.UNAUTHORIZED, "Need Authentication")
+ except OSError:
+ self.send_error(
+ HTTPStatus.INTERNAL_SERVER_ERROR, "Cannot open file for writing"
+ )
+
+ def do_DELETE(self):
+ path = self.translate_path(self.path)
+ try:
+ self._handle_auth()
+ os.remove(path)
+ self.send_response(HTTPStatus.OK)
+ self.send_header("Content-Length", "0")
+ self.end_headers()
+ except AuthenticationError:
+ self.send_error(HTTPStatus.UNAUTHORIZED, "Need Authentication")
+ except OSError:
+ self.send_error(
+ HTTPStatus.INTERNAL_SERVER_ERROR, "Cannot delete file"
+ )
+
+ def _handle_auth(self):
+ if not self.basic_auth:
+ return
+ authorization = self.headers.get("authorization")
+ if authorization:
+ authorization = authorization.split()
+ if len(authorization) == 2:
+ if (
+ authorization[0] == "Basic"
+ and authorization[1] == self.basic_auth
+ ):
+ return
+ raise AuthenticationError("Authentication required")
+
+
+def _get_best_family(*address):
+ infos = socket.getaddrinfo(
+ *address,
+ type=socket.SOCK_STREAM,
+ flags=socket.AI_PASSIVE,
+ )
+ family, type, proto, canonname, sockaddr = next(iter(infos))
+ return family, sockaddr
+
+
+def run(HandlerClass, ServerClass, port, bind):
+ HandlerClass.protocol_version = "HTTP/1.1"
+ ServerClass.address_family, addr = _get_best_family(bind, port)
+
+ with ServerClass(addr, HandlerClass) as httpd:
+ host, port = httpd.socket.getsockname()[:2]
+ url_host = f"[{host}]" if ":" in host else host
+ print(
+ f"Serving HTTP on {host} port {port} "
+ f"(http://{url_host}:{port}/) ..."
+ )
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ print("\nKeyboard interrupt received, exiting.")
+ sys.exit(0)
+
+
+def on_terminate(signum, frame):
+ sys.stdout.flush()
+ sys.stderr.flush()
+ sys.exit(128 + signum)
+
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--basic-auth", "-B", help="Basic auth tuple like user:pass"
+ )
+ parser.add_argument(
+ "--bind",
+ "-b",
+ metavar="ADDRESS",
+ help="Specify alternate bind address " "[default: all interfaces]",
+ )
+ parser.add_argument(
+ "--directory",
+ "-d",
+ default=os.getcwd(),
+ help="Specify alternative directory " "[default:current directory]",
+ )
+ parser.add_argument(
+ "port",
+ action="store",
+ default=8080,
+ type=int,
+ nargs="?",
+ help="Specify alternate port [default: 8080]",
+ )
+ args = parser.parse_args()
+
+ handler_class = partial(
+ PUTEnabledHTTPRequestHandler, basic_auth=args.basic_auth
+ )
+
+ os.chdir(args.directory)
+
+ signal.signal(signal.SIGINT, on_terminate)
+ signal.signal(signal.SIGTERM, on_terminate)
+
+ run(
+ HandlerClass=handler_class,
+ ServerClass=HTTPServer,
+ port=args.port,
+ bind=args.bind,
+ )
# A simple test suite for ccache.
#
# Copyright (C) 2002-2007 Andrew Tridgell
-# Copyright (C) 2009-2021 Joel Rosdahl and other contributors
+# Copyright (C) 2009-2022 Joel Rosdahl and other contributors
#
# See doc/AUTHORS.adoc for a complete list of contributors.
#
if [[ -t 1 ]]; then
ansi_boldgreen='\033[1;32m'
ansi_boldred='\033[1;31m'
+ ansi_boldyellow='\033[1;93m'
ansi_bold='\033[1m'
ansi_reset='\033[1;0m'
fi
green() {
- printf "$ansi_boldgreen%s$ansi_reset\n" "$*"
+ echo -e "$ansi_boldgreen$*$ansi_reset"
}
red() {
- printf "$ansi_boldred%s$ansi_reset\n" "$*"
+ echo -e "$ansi_boldred$*$ansi_reset"
+}
+
+yellow() {
+ echo -e "$ansi_boldyellow$*$ansi_reset"
}
bold() {
- printf "$ansi_bold%s$ansi_reset\n" "$*"
+ echo -e "$ansi_bold$*$ansi_reset"
}
test_failed_internal() {
echo "Test case: $(bold $CURRENT_TEST)"
echo "Failure reason: $(red "$1")"
echo
- echo "ccache -s:"
- $CCACHE -s
+ echo "Actual statistics counters"
+ echo "=========================="
+ while read -r key value; do
+ if [[ $value > 0 ]]; then
+ printf "$(yellow %-32s) $(yellow %s)\n" "$key" "$value"
+ else
+ printf "%-32s %s\n" "$key" "$value"
+ fi
+ done < <($CCACHE --print-stats | grep -v '^stats_')
echo
echo "Test data and log file have been left in $TESTDIR / $TEST_FAILED_SYMLINK"
symlink_testdir_on_failure
objdump_grep_cmd() {
if $HOST_OS_APPLE; then
fgrep -q "\"$1\""
+ elif $HOST_OS_WINDOWS || $HOST_OS_CYGWIN; then
+ fgrep -q "$1"
else
fgrep -q ": $1"
fi
local line
local value=""
- while IFS= read -r line; do
- if [[ $line = *"$stat"* ]]; then
- value="${line:32}"
- # remove leading & trailing whitespace
- value="${value#${value%%[![:space:]]*}}"
- value="${value%${value##*[![:space:]]}}"
+ if $HOST_OS_WINDOWS ; then
+ filter="sed -e s/\r//g"
+ else
+ filter=cat
+ fi
+
+ while read -r key value; do
+ if [[ $key == $stat ]]; then
break
fi
- done < <($CCACHE -s)
+ done < <($CCACHE --print-stats | $filter )
if [ "$expected_value" != "$value" ]; then
- test_failed_internal "Expected \"$stat\" to be $expected_value, actual $value"
+ test_failed_internal "Expected $stat to be $expected_value, actual $value"
fi
}
test_failed_internal "expect_equal_text_content: $2 missing"
fi
if ! cmp -s "$1" "$2"; then
- test_failed_internal "$1 and $2 differ: $(echo; diff -u "$1" "$2")"
+ if $HOST_OS_WINDOWS && diff -u --strip-trailing-cr "$1" "$2" > /dev/null ; then
+ test_failed_internal "$1 and $2 with differ line endings."
+ else
+ test_failed_internal "$1 and $2 differ: $(echo; diff -u "$1" "$2")"
+ fi
fi
}
test_failed_internal "$file not found"
fi
if [ "$(cat $file)" != "$content" ]; then
- test_failed_internal "Bad content of $file.\nExpected: $content\nActual: $(cat $file)"
+ test_failed_internal "Bad content of $file\nExpected: $content\nActual: $(cat $file)"
+ fi
+}
+
+expect_content_pattern() {
+ local file="$1"
+ local pattern="$2"
+
+ if [ ! -e "$file" ]; then
+ test_failed_internal "$file not found"
+ fi
+
+ local content="$(<$file)"
+ if [[ $pattern == !* ]]; then
+ pattern=${pattern:1}
+ if [[ "${content}" == $pattern ]]; then
+ test_failed_internal "Bad content of $file\nContent: $(<$file)\nMatched pattern: $pattern"
+ fi
+ else
+ if [[ "${content}" != $pattern ]]; then
+ test_failed_internal "Bad content of $file\nContent: $(<$file)\nDid not match pattern: $pattern"
+ fi
fi
+
}
expect_contains() {
local expected=$1
local pattern=$2
local dir=$3
- local actual=`find $dir -type f -name "$pattern" | wc -l`
+ local actual=`find "$dir" -type f -name "$pattern" | wc -l`
if [ $actual -ne $expected ]; then
test_failed_internal "Found $actual (expected $expected) $pattern files in $dir"
fi
fi
done < <(compgen -e)
+ unset DEPENDENCIES_OUTPUT
unset GCC_COLORS
+ unset SUNPRO_DEPENDENCIES
unset TERM
unset XDG_CACHE_HOME
unset XDG_CONFIG_HOME
+ export PWD=$(pwd)
export CCACHE_DETECT_SHEBANG=1
export CCACHE_DIR=$ABS_TESTDIR/.ccache
- export CCACHE_CONFIGPATH=$CCACHE_DIR/ccache.conf # skip secondary config
+ export CCACHE_CONFIGPATH=$CCACHE_DIR/ccache.conf # skip system config
export CCACHE_LOGFILE=$ABS_TESTDIR/ccache.log
export CCACHE_NODIRECT=1
SUITE_$suite_name
echo
+ terminate_all_children
+
return 0
}
+terminate_all_children() {
+ local pids="$(jobs -p)"
+ if [[ -n "$pids" ]]; then
+ kill $pids >/dev/null 2>&1
+ wait >/dev/null 2>&1
+ fi
+}
+
TEST() {
CURRENT_TEST=$1
CCACHE_COMPILE="$CCACHE $COMPILER"
+ terminate_all_children
reset_environment
if $verbose; then
export LC_ALL=C
+trap terminate_all_children EXIT # also clean up after exceptional code flow
+
if pwd | grep '[^A-Za-z0-9/.,=_%+-]' >/dev/null 2>&1; then
cat <<EOF
Error: The test suite doesn't work in directories with whitespace or other
done
export PATH
-if [ -n "$CC" ]; then
- COMPILER="$CC"
-else
- COMPILER=gcc
+if [ -z "$CC" ]; then
+ if [[ "$OSTYPE" == "darwin"* && -x "$(command -v clang)" ]]; then
+ CC=clang
+ else
+ CC=gcc
+ fi
fi
+
if [ -z "$CCACHE" ]; then
CCACHE=`pwd`/ccache
fi
COMPILER_USES_LLVM=false
COMPILER_USES_MINGW=false
+COMPILER_USES_MSVC=false
+
+ABS_ROOT_DIR="$(cd $(dirname "$0"); pwd)"
+readonly HTTP_CLIENT="${ABS_ROOT_DIR}/http-client"
+readonly HTTP_SERVER="${ABS_ROOT_DIR}/http-server"
HOST_OS_APPLE=false
HOST_OS_LINUX=false
HOST_OS_WINDOWS=false
HOST_OS_CYGWIN=false
-compiler_version="`$COMPILER --version 2>&1 | head -1`"
+compiler_version="`$CC --version 2>/dev/null | head -1`"
+
case $compiler_version in
*gcc*|*g++*|2.95*)
COMPILER_TYPE_GCC=true
;;
*clang*)
COMPILER_TYPE_CLANG=true
- CLANG_VERSION_SUFFIX=$(echo "${COMPILER%% *}" | sed 's/.*clang//')
+ CLANG_VERSION_SUFFIX=$(echo "${CC%% *}" | sed 's/.*clang//')
;;
*)
- echo "WARNING: Compiler $COMPILER not supported (version: $compiler_version) -- not running tests" >&2
- exit 0
+ echo "WARNING: Compiler $CC not supported (version: $compiler_version) -- Skipped running tests" >&2
+ exit $skip_code
+ ;;
+esac
+
+case $CC in
+ *MSVC*|*msvc*)
+ COMPILER_USES_MSVC=true
;;
esac
*MINGW*|*mingw*)
COMPILER_USES_MINGW=true
;;
+ *MSVC*|*msvc*)
+ COMPILER_USES_MSVC=true
+ ;;
esac
case $(uname -s) in
else
PATH_DELIM=":"
fi
+RUN_WIN_XFAIL=true
+if $HOST_OS_WINDOWS && [ -z "${RUN_FAULTY_TESTS}" ] ; then
+ RUN_WIN_XFAIL=false
+fi
+
+
if [[ $OSTYPE = msys* ]]; then
# Native symlink support for Windows.
ln -s "$$" "$TEST_FAILED_SYMLINK"
}
-cd $TESTDIR || exit 1
-
-COMPILER_BIN=$(echo $COMPILER | awk '{print $1}')
-COMPILER_ARGS=$(echo $COMPILER | awk '{$1 = ""; print}')
+COMPILER_BIN=$(echo $CC | awk '{print $1}')
+COMPILER_ARGS=$(echo $CC | awk '{$1 = ""; print}')
REAL_COMPILER_BIN=$(find_compiler $COMPILER_BIN)
REAL_COMPILER="$REAL_COMPILER_BIN$COMPILER_ARGS"
+REAL_NVCC=$(find_compiler nvcc)
-if [ "$REAL_COMPILER" = "$COMPILER" ]; then
- echo "Compiler: $COMPILER"
+if [ "$REAL_COMPILER_BIN" = "$COMPILER_BIN" ]; then
+ echo "Compiler: $CC"
else
- echo "Compiler: $COMPILER ($REAL_COMPILER)"
+ echo "Compiler: $CC ($REAL_COMPILER)"
fi
-echo "Compiler version: $($COMPILER --version | head -n 1)"
+echo "Compiler version: $($CC --version 2>/dev/null | head -n 1)"
-REAL_NVCC=$(find_compiler nvcc)
-REAL_CUOBJDUMP=$(find_compiler cuobjdump)
if [ -n "$REAL_NVCC" ]; then
echo "CUDA compiler: $($REAL_NVCC --version | tail -n 1) ($REAL_NVCC)"
else
fi
echo
+cd $TESTDIR || exit 1
+
+mkdir compiler
+
+COMPILER="$(pwd)/compiler/$(basename "$REAL_COMPILER_BIN")"
+if $HOST_OS_WINDOWS; then
+ COMPILER="$COMPILER.sh"
+fi
+cat >"$COMPILER" <<EOF
+#!/bin/sh
+
+CCACHE_DISABLE=1 CCACHE_COMPILER= CCACHE_PREFIX= \
+ exec $REAL_COMPILER_BIN$COMPILER_ARGS "\$@"
+EOF
+chmod +x "$COMPILER"
+
[ -z "${VERBOSE:-}" ] && verbose=false || verbose=true
[ "$1" = "-v" ] && { verbose=true; shift; }
done
cd /
-if [ -z "$KEEP_TESTDIR"]; then
+if [ -z "$KEEP_TESTDIR" ]; then
rm -rf $ABS_TESTDIR
fi
if $skipped; then
green PASSED
exit 0
fi
-
# -------------------------------------------------------------------------
TEST "Base case"
- $REAL_COMPILER -c -o reference_test1.o test1.c
+ $COMPILER -c -o reference_test1.o test1.c
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat direct_cache_miss 0
+ expect_stat preprocessed_cache_miss 1
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 1
+ expect_stat local_storage_write 1
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 0
+ expect_stat remote_storage_write 0
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat direct_cache_miss 0
+ expect_stat preprocessed_cache_miss 1
+ expect_stat local_storage_hit 1
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 1
+ expect_stat local_storage_read_miss 1
+ expect_stat local_storage_write 1
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 0
+ expect_stat remote_storage_write 0
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
# -------------------------------------------------------------------------
# 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
+ $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_stat preprocessed_cache_hit 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_stat preprocessed_cache_hit 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_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
# -------------------------------------------------------------------------
# and not random memory.
local version_pattern=$'^ccache version [a-zA-Z0-9_./+-]*\r?$'
if [ $($CCACHE --version | grep -E -c "$version_pattern") -ne 1 ]; then
- test_failed "Unexpected output of --version"
+ test_failed "Unexpected output of --version. Output is '$($CCACHE --version)'"
fi
# -------------------------------------------------------------------------
TEST "Debug option"
$CCACHE_COMPILE -c test1.c -g
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$CCACHE_COMPILE -c test1.c -g
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
- $REAL_COMPILER -c -o reference_test1.o test1.c -g
+ $COMPILER -c -o reference_test1.o test1.c -g
expect_equal_object_files reference_test1.o test1.o
# -------------------------------------------------------------------------
TEST "Output option"
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test1.c -o foo.o
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
- $REAL_COMPILER -c -o reference_test1.o test1.c
+ $COMPILER -c -o reference_test1.o test1.c
expect_equal_object_files reference_test1.o foo.o
# -------------------------------------------------------------------------
TEST "Output option without space"
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test1.c -odir
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test1.c -optf
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
- $REAL_COMPILER -c -o reference_test1.o test1.c
+ $COMPILER -c -o reference_test1.o test1.c
expect_equal_object_files reference_test1.o dir
expect_equal_object_files reference_test1.o ptf
TEST "Called for link"
$CCACHE_COMPILE test1.c -o test 2>/dev/null
- expect_stat 'called for link' 1
+ expect_stat called_for_link 1
$CCACHE_COMPILE -c test1.c
$CCACHE_COMPILE test1.o -o test 2>/dev/null
- expect_stat 'called for link' 2
+ expect_stat called_for_link 2
# -------------------------------------------------------------------------
- TEST "No input file"
+ TEST "No existing input file"
$CCACHE_COMPILE -c foo.c 2>/dev/null
- expect_stat 'no input file' 1
+ expect_stat no_input_file 1
+
+ # -------------------------------------------------------------------------
+ TEST "No input file on command line"
+
+ $CCACHE_COMPILE -c -O2 2>/dev/null
+ expect_stat no_input_file 1
# -------------------------------------------------------------------------
TEST "Called for preprocessing"
$CCACHE_COMPILE -E -c test1.c >/dev/null 2>&1
- expect_stat 'called for preprocessing' 1
+ expect_stat called_for_preprocessing 1
# -------------------------------------------------------------------------
TEST "Multiple source files"
touch test2.c
$CCACHE_COMPILE -c test1.c test2.c
- expect_stat 'multiple source files' 1
+ expect_stat multiple_source_files 1
# -------------------------------------------------------------------------
TEST "Couldn't find the compiler"
TEST "Bad compiler arguments"
$CCACHE_COMPILE -c test1.c -I 2>/dev/null
- expect_stat 'bad compiler arguments' 1
+ expect_stat bad_compiler_arguments 1
# -------------------------------------------------------------------------
TEST "Unsupported source language"
ln -f test1.c test1.ccc
$CCACHE_COMPILE -c test1.ccc 2>/dev/null
- expect_stat 'unsupported source language' 1
+ expect_stat unsupported_source_language 1
# -------------------------------------------------------------------------
TEST "Unsupported compiler option"
$CCACHE_COMPILE -M foo -c test1.c >/dev/null 2>&1
- expect_stat 'unsupported compiler option' 1
+ expect_stat unsupported_compiler_option 1
# -------------------------------------------------------------------------
- TEST "Compiler produced stdout"
+ for variable in DEPENDENCIES_OUTPUT SUNPRO_DEPENDENCIES; do
+ TEST "Unsupported environment variable ${variable}"
- $CCACHE echo foo -c test1.c >/dev/null
- expect_stat 'compiler produced stdout' 1
+ eval "export ${variable}=1"
+ $CCACHE_COMPILE -c test1.c
+ expect_stat unsupported_environment_variable 1
+ done
# -------------------------------------------------------------------------
TEST "Output to directory"
mkdir testd
$CCACHE_COMPILE -o testd -c test1.c >/dev/null 2>&1
rmdir testd >/dev/null 2>&1
- expect_stat 'could not write to output file' 1
+ expect_stat bad_output_file 1
# -------------------------------------------------------------------------
TEST "Output to file in nonexistent directory"
mkdir out
$CCACHE_COMPILE -c test1.c -o out/foo.o
- expect_stat 'could not write to output file' ""
- expect_stat 'cache miss' 1
+ expect_stat bad_output_file 0
+ expect_stat cache_miss 1
rm -rf out
$CCACHE_COMPILE -c test1.c -o out/foo.o 2>/dev/null
- expect_stat 'could not write to output file' 1
- expect_stat 'cache miss' 1
+ expect_stat bad_output_file 1
+ expect_stat cache_miss 1
expect_missing out/foo.o
- # -------------------------------------------------------------------------
- TEST "No input file"
-
- $CCACHE_COMPILE -c -O2 2>/dev/null
- expect_stat 'no input file' 1
-
# -------------------------------------------------------------------------
TEST "No file extension"
touch src/foo
$CCACHE_COMPILE -x c -c src/foo
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists foo.o
rm foo.o
$CCACHE_COMPILE -x c -c src/foo
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
expect_exists foo.o
rm foo.o
touch src/foo.
$CCACHE_COMPILE -x c -c src/foo.
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists foo.o
rm foo.o
$CCACHE_COMPILE -x c -c src/foo.
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
expect_exists foo.o
rm foo.o
touch src/foo.c.c
$CCACHE_COMPILE -c src/foo.c.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists foo.c.o
rm foo.c.o
$CCACHE_COMPILE -c src/foo.c.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
expect_exists foo.c.o
rm foo.c.o
TEST "LANG"
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
LANG=foo $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
LANG=foo $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
# -------------------------------------------------------------------------
TEST "LANG with sloppiness"
CCACHE_SLOPPINESS=locale LANG=foo $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
CCACHE_SLOPPINESS=locale LANG=foo $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
CCACHE_SLOPPINESS=locale LANG=bar $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
# -------------------------------------------------------------------------
TEST "Result file is compressed"
$CCACHE_COMPILE -c test1.c
result_file=$(find $CCACHE_DIR -name '*R')
- if ! $CCACHE --dump-result $result_file | grep 'Compression type: zstd' >/dev/null 2>&1; then
+ if ! $CCACHE --inspect $result_file | grep 'Compression type: zstd' >/dev/null 2>&1; then
test_failed "Result file not uncompressed according to metadata"
fi
if [ $(file_size $result_file) -ge $(file_size test1.o) ]; then
TEST "Corrupt result file"
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
result_file=$(find $CCACHE_DIR -name '*R')
printf foo | dd of=$result_file bs=3 count=1 seek=20 conv=notrunc >&/dev/null
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 1
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 1
# -------------------------------------------------------------------------
TEST "CCACHE_DEBUG"
unset CCACHE_LOGFILE
unset CCACHE_NODIRECT
CCACHE_DEBUG=1 $CCACHE_COMPILE -c test1.c
- if ! grep -q Result: test1.o.ccache-log; then
+ if ! grep -q Result: test1.o.*.ccache-log; then
test_failed "Unexpected data in <obj>.ccache-log"
fi
- if ! grep -q "PREPROCESSOR MODE" test1.o.ccache-input-text; then
- test_failed "Unexpected data in <obj>.ccache-input-text"
+ if ! grep -q "PREPROCESSOR MODE" test1.o.*.ccache-input-text; then
+ test_failed "Unexpected data in <obj>.<timestamp>.ccache-input-text"
fi
for ext in c p d; do
- if ! [ -f test1.o.ccache-input-$ext ]; then
+ if ! [ -f test1.o.*.ccache-input-$ext ]; then
test_failed "<obj>.ccache-input-$ext missing"
fi
done
TEST "CCACHE_DEBUG with too hard option"
CCACHE_DEBUG=1 $CCACHE_COMPILE -c test1.c -save-temps
- expect_stat 'unsupported compiler option' 1
- expect_exists test1.o.ccache-log
+ expect_stat unsupported_compiler_option 1
+ expect_exists test1.o.*.ccache-log
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL;then
+# TODO: Leading slash is missing. (debugdirC/... instead of debugdir/C/... )
+ TEST "CCACHE_DEBUGDIR"
+
+ CCACHE_DEBUG=1 CCACHE_DEBUGDIR=debugdir $CCACHE_COMPILE -c test1.c
+ expect_contains debugdir"$(pwd -P)"/test1.o.*.ccache-log "Result: cache_miss"
+fi
+ # -------------------------------------------------------------------------
TEST "CCACHE_DISABLE"
CCACHE_DISABLE=1 $CCACHE_COMPILE -c test1.c 2>/dev/null
# -------------------------------------------------------------------------
TEST "CCACHE_COMMENTS"
- $REAL_COMPILER -c -o reference_test1.o test1.c
+ $COMPILER -c -o reference_test1.o test1.c
mv test1.c test1-saved.c
echo '// initial comment' >test1.c
cat test1-saved.c >>test1.c
CCACHE_COMMENTS=1 $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo '// different comment' >test1.c
cat test1-saved.c >>test1.c
CCACHE_COMMENTS=1 $CCACHE_COMPILE -c test1.c
mv test1-saved.c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
- $REAL_COMPILER -c -o reference_test1.o test1.c
+ $COMPILER -c -o reference_test1.o test1.c
expect_equal_object_files reference_test1.o test1.o
# -------------------------------------------------------------------------
TEST "CCACHE_NOSTATS"
CCACHE_NOSTATS=1 $CCACHE_COMPILE -c test1.c -O -O
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
# -------------------------------------------------------------------------
TEST "stats file forward compatibility"
echo $i
done > "$stats_file"
- expect_stat 'cache miss' 5
+ expect_stat cache_miss 5
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache miss' 6
+ expect_stat cache_miss 6
expect_contains "$stats_file" 101
expect_newer_than "$stats_file" "$CCACHE_DIR/timestamp_reference"
echo "0 0 0 0 1234567890123456789" >"$stats_file"
- expect_stat 'cache miss' 1234567890123456789
+ expect_stat cache_miss 1234567890123456789
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache miss' 1234567890123456790
+ expect_stat cache_miss 1234567890123456790
# -------------------------------------------------------------------------
TEST "CCACHE_RECACHE"
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat preprocessed_cache_miss 1
+ expect_stat cache_miss 1
+ expect_stat recache 0
CCACHE_RECACHE=1 $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat preprocessed_cache_miss 1
+ expect_stat cache_miss 1
+ expect_stat recache 1
- $REAL_COMPILER -c -o reference_test1.o test1.c
+ $COMPILER -c -o reference_test1.o test1.c
expect_equal_object_files reference_test1.o test1.o
- expect_stat 'files in cache' 1
+ expect_stat files_in_cache 1
# -------------------------------------------------------------------------
TEST "Directory is hashed if using -g"
cd dir1
$CCACHE_COMPILE -c test1.c -g
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test1.c -g
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
cd ../dir2
$CCACHE_COMPILE -c test1.c -g
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
$CCACHE_COMPILE -c test1.c -g
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Directory is not hashed if not using -g"
cd dir1
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
cd ../dir2
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Directory is not hashed if using -g -g0"
cd dir1
$CCACHE_COMPILE -c test1.c -g -g0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test1.c -g -g0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
cd ../dir2
$CCACHE_COMPILE -c test1.c -g -g0
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Directory is not hashed if using -gz"
- $REAL_COMPILER -E test1.c -gz >preprocessed.i 2>/dev/null
+ $COMPILER -E test1.c -gz >preprocessed.i 2>/dev/null
if [ -s preprocessed.i ] && ! fgrep -q $PWD preprocessed.i; then
mkdir dir1 dir2
cp test1.c dir1
cd dir1
$CCACHE_COMPILE -c test1.c -gz
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test1.c -gz
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
cd ../dir2
$CCACHE_COMPILE -c test1.c -gz
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
fi
# -------------------------------------------------------------------------
- TEST "Directory is not hashed if using -gz=zlib"
-
- $REAL_COMPILER -E test1.c -gz=zlib >preprocessed.i 2>/dev/null
- if [ -s preprocessed.i ] && ! fgrep -q $PWD preprocessed.i; then
- mkdir dir1 dir2
- cp test1.c dir1
- cp test1.c dir2
- cd dir1
- $CCACHE_COMPILE -c test1.c -gz=zlib
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- $CCACHE_COMPILE -c test1.c -gz=zlib
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ TEST "Directory is not hashed if using -gz=zlib"
- cd ../dir2
- $CCACHE_COMPILE -c test1.c -gz=zlib
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
+ $COMPILER test1.c -gz=zlib -o /dev/null 2>/dev/null
+ if [ $? -eq 0 ]; then
+ # run test only if -gz=zlib is supported
+ $COMPILER -E test1.c -gz=zlib >preprocessed.i 2>/dev/null
+ if [ "$exit_code" == "0" ] && [ -s preprocessed.i ] && ! fgrep -q $PWD preprocessed.i; then
+ mkdir dir1 dir2
+ cp test1.c dir1
+ cp test1.c dir2
+
+ cd dir1
+ $CCACHE_COMPILE -c test1.c -gz=zlib
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ $CCACHE_COMPILE -c test1.c -gz=zlib
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+
+ cd ../dir2
+ $CCACHE_COMPILE -c test1.c -gz=zlib
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
+ fi
fi
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST "CCACHE_NOHASHDIR"
mkdir dir1 dir2
cd dir1
CCACHE_NOHASHDIR=1 $CCACHE_COMPILE -c test1.c -g
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_NOHASHDIR=1 $CCACHE_COMPILE -c test1.c -g
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
cd ../dir2
CCACHE_NOHASHDIR=1 $CCACHE_COMPILE -c test1.c -g
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
+fi
# -------------------------------------------------------------------------
TEST "CCACHE_EXTRAFILES"
echo "b" >b
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
echo b2 >b
CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 3
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 3
CCACHE_EXTRAFILES="a${PATH_DELIM}b" $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 3
- expect_stat 'cache miss' 3
+ expect_stat preprocessed_cache_hit 3
+ expect_stat cache_miss 3
CCACHE_EXTRAFILES="doesntexist" $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 3
- expect_stat 'cache miss' 3
- expect_stat 'error hashing extra file' 1
+ expect_stat preprocessed_cache_hit 3
+ expect_stat cache_miss 3
+ expect_stat error_hashing_extra_file 1
# -------------------------------------------------------------------------
TEST "CCACHE_PREFIX"
-
- cat <<'EOF' >prefix-a
+ cat <<'EOF' >prefix-a.sh
#!/bin/sh
echo a >prefix.result
exec "$@"
EOF
- cat <<'EOF' >prefix-b
+ cat <<'EOF' >prefix-b.sh
#!/bin/sh
echo b >>prefix.result
exec "$@"
EOF
- chmod +x prefix-a prefix-b
+ chmod +x prefix-a.sh prefix-b.sh
cat <<'EOF' >file.c
int foo;
EOF
- PATH=.:$PATH CCACHE_PREFIX="prefix-a prefix-b" $CCACHE_COMPILE -c file.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ PATH=.:$PATH CCACHE_PREFIX="prefix-a.sh prefix-b.sh" $CCACHE_COMPILE -c file.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_content prefix.result "a
b"
- PATH=.:$PATH CCACHE_PREFIX="prefix-a prefix-b" $CCACHE_COMPILE -c file.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ PATH=.:$PATH CCACHE_PREFIX="prefix-a.sh prefix-b.sh" $CCACHE_COMPILE -c file.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
expect_content prefix.result "a
b"
rm -f prefix.result
- PATH=.:$PATH CCACHE_PREFIX_CPP="prefix-a prefix-b" $CCACHE_COMPILE -c file.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
+ PATH=.:$PATH CCACHE_PREFIX_CPP="prefix-a.sh prefix-b.sh" $CCACHE_COMPILE -c file.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
expect_content prefix.result "a
b"
-
# -------------------------------------------------------------------------
TEST "Files in cache"
generate_code $i test$i.c
$CCACHE_COMPILE -c test$i.c
done
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 32
- expect_stat 'files in cache' 32
-
- # -------------------------------------------------------------------------
- TEST "Called for preprocessing"
-
- $CCACHE_COMPILE -c test1.c -E >test1.i
- expect_stat 'called for preprocessing' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 32
+ expect_stat files_in_cache 32
# -------------------------------------------------------------------------
TEST "Direct .i compile"
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
- $REAL_COMPILER -c test1.c -E >test1.i
+ $COMPILER -c test1.c -E >test1.i
$CCACHE_COMPILE -c test1.i
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "-x c"
ln -f test1.c test1.ccc
$CCACHE_COMPILE -x c -c test1.ccc
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -x c -c test1.ccc
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "-xc"
ln -f test1.c test1.ccc
$CCACHE_COMPILE -xc -c test1.ccc
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -xc -c test1.ccc
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "-x none"
$CCACHE_COMPILE -x assembler -x none -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -x assembler -x none -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "-x unknown"
$CCACHE_COMPILE -x unknown -c test1.c 2>/dev/null
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat 'unsupported source language' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat unsupported_source_language 1
# -------------------------------------------------------------------------
+if ! $HOST_OS_WINDOWS; then
TEST "-x c -c /dev/null"
$CCACHE_COMPILE -x c -c /dev/null -o null.o 2>/dev/null
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -x c -c /dev/null -o null.o 2>/dev/null
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
-
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+fi
# -------------------------------------------------------------------------
TEST "-D not hashed"
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -DNOT_AFFECTING=1 -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "-S"
$CCACHE_COMPILE -S test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -S test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test1.s
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
$CCACHE_COMPILE -c test1.s
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
+if ! $HOST_OS_WINDOWS; then
TEST "-frecord-gcc-switches"
- if $REAL_COMPILER -frecord-gcc-switches -c test1.c >&/dev/null; then
+ if $COMPILER -frecord-gcc-switches -c test1.c >&/dev/null; then
$CCACHE_COMPILE -frecord-gcc-switches -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -frecord-gcc-switches -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
$CCACHE_COMPILE -frecord-gcc-switches -Wall -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
$CCACHE_COMPILE -frecord-gcc-switches -Wall -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
fi
+fi
# -------------------------------------------------------------------------
+ TEST "CCACHE_DISABLE set when executing compiler"
+
+ cat >compiler.sh <<EOF
+#!/bin/sh
+printf "%s" "\${CCACHE_DISABLE}" >>CCACHE_DISABLE.value
+exec $COMPILER "\$@"
+EOF
+ chmod +x compiler.sh
+ backdate compiler.sh
+ $CCACHE ./compiler.sh -c test1.c
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_content CCACHE_DISABLE.value '11' # preprocessor + compiler
+
+ # -------------------------------------------------------------------------
+if ! ( $HOST_OS_WINDOWS && $COMPILER_TYPE_CLANG ) && [ -n "$COMPILER_ARGS" ] ; then
TEST "CCACHE_COMPILER"
- $REAL_COMPILER -c -o reference_test1.o test1.c
+ $COMPILER -c -o reference_test1.o test1.c
+ export CCACHE_DEBUG=1
+ export CCACHE_DEBUGDIR=/tmp/a
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
- CCACHE_COMPILER=$COMPILER_BIN $CCACHE \
+ export CCACHE_DEBUGDIR=/tmp/b
+ CCACHE_COMPILER=$COMPILER $CCACHE \
non_existing_compiler_will_be_overridden_anyway \
$COMPILER_ARGS -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
- CCACHE_COMPILER=$COMPILER_BIN $CCACHE same/for/relative \
+ CCACHE_COMPILER=$COMPILER $CCACHE same/for/relative \
$COMPILER_ARGS -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
- CCACHE_COMPILER=$COMPILER_BIN $CCACHE /and/even/absolute/compilers \
+ CCACHE_COMPILER=$COMPILER $CCACHE /and/even/absolute/compilers \
$COMPILER_ARGS -c test1.c
- expect_stat 'cache hit (preprocessed)' 3
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 3
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
-
+fi
# -------------------------------------------------------------------------
TEST "CCACHE_COMPILERTYPE"
+ if $HOST_OS_WINDOWS; then
+ FAKE_GCC=./gcc.sh
+ else
+ FAKE_GCC=./gcc
+ fi
+
$CCACHE_COMPILE -c test1.c
- cat >gcc <<EOF
+ cat >$FAKE_GCC <<EOF
#!/bin/sh
EOF
- chmod +x gcc
+ chmod +x $FAKE_GCC
- CCACHE_DEBUG=1 $CCACHE ./gcc -c test1.c
- compiler_type=$(sed -En 's/.*Compiler type: (.*)/\1/p' test1.o.ccache-log)
+ CCACHE_DEBUG=1 $CCACHE $FAKE_GCC -c test1.c
+ compiler_type=$(sed -En '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
+ rm test1.o.*.ccache-log
- CCACHE_COMPILERTYPE=clang CCACHE_DEBUG=1 $CCACHE ./gcc -c test1.c
- compiler_type=$(sed -En 's/.*Compiler type: (.*)/\1/p' test1.o.ccache-log)
+ CCACHE_COMPILERTYPE=clang CCACHE_DEBUG=1 $CCACHE $FAKE_GCC -c test1.c
+ compiler_type=$(sed -En '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"
-
+if $RUN_WIN_XFAIL; then
override_path=`pwd`/override_path
mkdir $override_path
cat >$override_path/cc <<EOF
if [ ! -f override_path_compiler_executed ]; then
test_failed "CCACHE_PATH had no effect"
fi
+fi
# -------------------------------------------------------------------------
TEST "CCACHE_COMPILERCHECK=mtime"
cat >compiler.sh <<EOF
#!/bin/sh
-CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
-export CCACHE_DISABLE
exec $COMPILER "\$@"
# A comment
EOF
chmod +x compiler.sh
backdate compiler.sh
CCACHE_COMPILERCHECK=mtime $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
sed_in_place 's/comment/yoghurt/' compiler.sh # Don't change the size
chmod +x compiler.sh
backdate compiler.sh # Don't change the timestamp
CCACHE_COMPILERCHECK=mtime $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
touch compiler.sh
CCACHE_COMPILERCHECK=mtime $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "CCACHE_COMPILERCHECK=content"
cat >compiler.sh <<EOF
#!/bin/sh
-CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
-export CCACHE_DISABLE
exec $COMPILER "\$@"
EOF
chmod +x compiler.sh
CCACHE_COMPILERCHECK=content $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_COMPILERCHECK=content $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
echo "# Compiler upgrade" >>compiler.sh
CCACHE_COMPILERCHECK=content $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "CCACHE_COMPILERCHECK=none"
cat >compiler.sh <<EOF
#!/bin/sh
-CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
-export CCACHE_DISABLE
exec $COMPILER "\$@"
EOF
chmod +x compiler.sh
CCACHE_COMPILERCHECK=none $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_COMPILERCHECK=none $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
echo "# Compiler upgrade" >>compiler.sh
CCACHE_COMPILERCHECK=none $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "CCACHE_COMPILERCHECK=string"
cat >compiler.sh <<EOF
#!/bin/sh
-CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
-export CCACHE_DISABLE
exec $COMPILER "\$@"
EOF
chmod +x compiler.sh
CCACHE_COMPILERCHECK=string:foo $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_COMPILERCHECK=string:foo $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
CCACHE_COMPILERCHECK=string:bar $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
CCACHE_COMPILERCHECK=string:bar $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "CCACHE_COMPILERCHECK=command"
-
+if $RUN_WIN_XFAIL; then
cat >compiler.sh <<EOF
#!/bin/sh
-CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
-export CCACHE_DISABLE
exec $COMPILER "\$@"
EOF
chmod +x compiler.sh
CCACHE_COMPILERCHECK='echo %compiler%' $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo "# Compiler upgrade" >>compiler.sh
CCACHE_COMPILERCHECK="echo ./compiler.sh" $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
cat <<EOF >foobar.sh
#!/bin/sh
EOF
chmod +x foobar.sh
CCACHE_COMPILERCHECK='./foobar.sh' $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
CCACHE_COMPILERCHECK='echo foo; echo bar' $CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
+fi
# -------------------------------------------------------------------------
TEST "CCACHE_COMPILERCHECK=unknown_command"
cat >compiler.sh <<EOF
#!/bin/sh
-CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
-export CCACHE_DISABLE
exec $COMPILER "\$@"
EOF
chmod +x compiler.sh
CCACHE_COMPILERCHECK="unknown_command" $CCACHE ./compiler.sh -c test1.c 2>/dev/null
- expect_stat 'compiler check failed' 1
+ expect_stat compiler_check_failed 1
# -------------------------------------------------------------------------
export CCACHE_UMASK=002
export CCACHE_TEMPDIR=$CCACHE_DIR/tmp
+ $CCACHE -C >/dev/null
+ expect_perm "$CCACHE_DIR" drwxrwxr-x
+ expect_perm "$CCACHE_DIR/0" drwxrwxr-x
+ expect_perm "$CCACHE_DIR/0/stats" -rw-rw-r--
+ rm -rf $CCACHE_DIR
+
+ $CCACHE -c >/dev/null
+ expect_perm "$CCACHE_DIR" drwxrwxr-x
+ expect_perm "$CCACHE_DIR/0" drwxrwxr-x
+ expect_perm "$CCACHE_DIR/0/stats" -rw-rw-r--
+ rm -rf $CCACHE_DIR
+
+ $CCACHE -z >/dev/null
+ expect_perm "$CCACHE_DIR" drwxrwxr-x
+ expect_perm "$CCACHE_DIR/0" drwxrwxr-x
+ expect_perm "$CCACHE_DIR/0/stats" -rw-rw-r--
+ rm -rf $CCACHE_DIR
+
cat <<EOF >test.c
int main() {}
EOF
$CCACHE -M 5 >/dev/null
$CCACHE_COMPILE -MMD -c test.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 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"))
rm test.o test.d
$CCACHE_COMPILE -MMD -c test.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
expect_perm test.o -rw-r--r--
expect_perm test.d -rw-r--r--
$CCACHE_COMPILE -o test test.o
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'called for link' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ 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
+ 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"))
# -------------------------------------------------------------------------
TEST "No object file due to bad prefix"
-
cat <<'EOF' >test_no_obj.c
int test_no_obj;
EOF
- cat <<'EOF' >no-object-prefix
+ cat <<'EOF' >no-object-prefix.sh
#!/bin/sh
# Emulate no object file from the compiler.
EOF
- 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
+ chmod +x no-object-prefix.sh
+ CCACHE_PREFIX=$(pwd)/no-object-prefix.sh $CCACHE_COMPILE -c test_no_obj.c
+ expect_stat compiler_produced_no_output 1
+
+ CCACHE_PREFIX=$(pwd)/no-object-prefix.sh $CCACHE_COMPILE -c test1.c
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat files_in_cache 0
+ expect_stat compiler_produced_no_output 2
+
+ # -------------------------------------------------------------------------
+ TEST "-fsyntax-only"
+
+ echo existing >test1.o
+ $CCACHE_COMPILE -fsyntax-only test1.c
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_content test1.o existing
+
+ rm test1.o
+
+ $CCACHE_COMPILE -fsyntax-only test1.c
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_missing test1.o
+
+ # -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
+ TEST "-fsyntax-only /dev/null"
+
+ echo existing >null.o
+ $CCACHE_COMPILE -fsyntax-only -x c /dev/null
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_content null.o existing
- 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
+ rm null.o
+
+ $CCACHE_COMPILE -fsyntax-only -x c /dev/null
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_missing null.o
# -------------------------------------------------------------------------
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
+ $COMPILER -Wall 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
+ $CCACHE_COMPILE -Wall stderr.c -fsyntax-only 2>stderr.txt
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_text_content reference_stderr.txt stderr.txt
+ # Intentionally compiling with "-c" here but not above.
$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_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_text_content reference_stderr.txt stderr.txt
+fi
# -------------------------------------------------------------------------
TEST "Empty object file"
-
cat <<'EOF' >test_empty_obj.c
int test_empty_obj;
EOF
- cat <<'EOF' >empty-object-prefix
+ cat <<'EOF' >empty-object-prefix.sh
#!/bin/sh
# Emulate empty object file from the compiler.
touch test_empty_obj.o
EOF
- 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
+ chmod +x empty-object-prefix.sh
+ CCACHE_PREFIX=`pwd`/empty-object-prefix.sh $CCACHE_COMPILE -c test_empty_obj.c
+ expect_stat compiler_produced_empty_output 1
# -------------------------------------------------------------------------
TEST "Output to /dev/null"
-
+if ! $HOST_OS_WINDOWS; then
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test1.c -o /dev/null
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+fi
+ # -------------------------------------------------------------------------
+
+ mkdir dir
+ chmod a-w dir
+ if ! touch dir/test 2>/dev/null; then
+ TEST "Failure to write output file"
+
+ mkdir dir
+
+ $CCACHE_COMPILE -c test1.c -o dir/test1.o
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat bad_output_file 0
+
+ rm dir/test1.o
+ chmod a-w dir
+
+if $RUN_WIN_XFAIL; then
+ $CCACHE_COMPILE -c test1.c -o dir/test1.o 2>/dev/null
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat bad_output_file 1
+fi
+ fi
# -------------------------------------------------------------------------
TEST "Caching stderr"
// Trigger warning by having no return statement.
}
EOF
- $REAL_COMPILER -c -Wall -W -c stderr.c 2>reference_stderr.txt
+ $COMPILER -c -Wall -W -c stderr.c 2>reference_stderr.txt
$CCACHE_COMPILE -Wall -W -c stderr.c 2>stderr.txt
- expect_equal_content reference_stderr.txt stderr.txt
+ expect_equal_text_content reference_stderr.txt stderr.txt
# -------------------------------------------------------------------------
TEST "Merging stderr"
unset CCACHE_NOCPP2
stderr=$($CCACHE ./compiler.sh -c test1.c 2>stderr)
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_content stderr "[cc_stderr]"
stderr=$(CCACHE_NOCPP2=1 $CCACHE ./compiler.sh -c test1.c 2>stderr)
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
expect_content stderr "[cpp_stderr][cc_stderr]"
# -------------------------------------------------------------------------
cat <<EOF >test.c
#warning Foo
EOF
- $REAL_COMPILER -c test.c -MMD 2>reference.stderr
+ $COMPILER -c test.c -MMD 2>reference.stderr
mv test.d reference.d
$CCACHE_COMPILE -c test.c -MMD 2>test.stderr
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content reference.stderr test.stderr
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_equal_text_content reference.stderr test.stderr
expect_equal_content reference.d test.d
$CCACHE_COMPILE -c test.c -MMD 2>test.stderr
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_equal_content reference.stderr test.stderr
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_equal_text_content reference.stderr test.stderr
expect_equal_content reference.d test.d
+ # -------------------------------------------------------------------------
+ TEST "Caching stdout and stderr"
+
+ cat >compiler.sh <<EOF
+#!/bin/sh
+if [ \$1 = -E ] || ! echo "\$*" | grep -q '\.i\$'; then
+ printf "cpp_err|" >&2
+fi
+if [ \$1 != -E ]; then
+ printf "cc_err|" >&2
+ printf "cc_out|"
+fi
+exec $COMPILER "\$@"
+EOF
+ chmod +x compiler.sh
+
+ $CCACHE ./compiler.sh -c test1.c >stdout 2>stderr
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_content stdout "cc_out|"
+ expect_content stderr "cpp_err|cc_err|"
+
+ $CCACHE ./compiler.sh -c test1.c >stdout 2>stderr
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_content stdout "cc_out|"
+ expect_content stderr "cpp_err|cc_err|"
+
# -------------------------------------------------------------------------
TEST "--zero-stats"
$CCACHE_COMPILE -c test1.c
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$CCACHE -z >/dev/null
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat files_in_cache 1
# -------------------------------------------------------------------------
TEST "--clear"
$CCACHE_COMPILE -c test1.c
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$CCACHE -C >/dev/null
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 0
# -------------------------------------------------------------------------
TEST "-P -c"
$CCACHE_COMPILE -P -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -P -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "-P -E"
- $CCACHE_COMPILE -P -E test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat 'called for preprocessing' 1
+ $CCACHE_COMPILE -P -E test1.c >/dev/null
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat called_for_preprocessing 1
+
+ # -------------------------------------------------------------------------
+ if $COMPILER -c -Wa,-a test1.c >&/dev/null; then
+ TEST "-Wa,-a"
+
+ $CCACHE_COMPILE -c -Wa,-a test1.c >first.lst
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
+ $CCACHE_COMPILE -c -Wa,-a test1.c >second.lst
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_equal_content first.lst second.lst
+ fi
+
+ # -------------------------------------------------------------------------
+ if $COMPILER -c -Wa,-a=test1.lst,-L test1.c >&/dev/null && [ -e test1.lst ]; then
+ TEST "-Wa,-a=file"
+
+ # Check that -Wa options after -a are handled.
+
+ $CCACHE_COMPILE -c -Wa,-a=test1.lst,-L test1.c
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ rm test1.lst
+
+ $CCACHE_COMPILE -c -Wa,-a=test1.lst,-L test1.c
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ rm test1.lst
+
+ # Check that the filename can be changed without changing the hash.
+
+ $CCACHE_COMPILE -c -Wa,-a=test2.lst,-L test1.c
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
+ rm test2.lst
+
+ # Check that -Wa options before -a are handled.
+
+ $CCACHE_COMPILE -c -Wa,-L,-a=test1.lst test1.c
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
+ rm test1.lst
+
+ $CCACHE_COMPILE -c -Wa,-L,-a=test1.lst test1.c
+ expect_stat preprocessed_cache_hit 3
+ expect_stat cache_miss 2
+ rm test1.lst
+
+ # Check that -Wa,-aOPTIONS=file is different than -Wa,-a=file.
+
+ $CCACHE_COMPILE -c -Wa,-as=test1.lst test1.c
+ expect_stat preprocessed_cache_hit 3
+ expect_stat cache_miss 3
+ rm test1.lst
+
+ $CCACHE_COMPILE -c -Wa,-as=test1.lst test1.c
+ expect_stat preprocessed_cache_hit 4
+ expect_stat cache_miss 3
+ rm test1.lst
+
+ # Multiple -Wa,-a options are not supported.
+
+ $CCACHE_COMPILE -c -Wa,-a=test1.lst -Wa,-as=test1.lst test1.c
+ expect_stat preprocessed_cache_hit 4
+ expect_stat cache_miss 3
+ expect_stat unsupported_compiler_option 1
+ rm test1.lst
+ fi
# -------------------------------------------------------------------------
TEST "-Wp,-P"
$CCACHE_COMPILE -c -Wp,-P test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c -Wp,-P test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "-Wp,-P,-DFOO"
# object file produced when compiling without ccache.)
$CCACHE_COMPILE -c -Wp,-P,-DFOO test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat 'unsupported compiler option' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat unsupported_compiler_option 1
$CCACHE_COMPILE -c -Wp,-DFOO,-P test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat 'unsupported compiler option' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat unsupported_compiler_option 2
# -------------------------------------------------------------------------
TEST "-Wp,-D"
$CCACHE_COMPILE -c -Wp,-DFOO test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c -DFOO test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+
+ # -------------------------------------------------------------------------
+ if touch empty.c && $COMPILER -c -- empty.c 2>/dev/null; then
+ TEST "--"
+
+ $CCACHE_COMPILE -c -- test1.c
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
+ $CCACHE_COMPILE -c -- test1.c
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ fi
# -------------------------------------------------------------------------
TEST "Handling of compiler-only arguments"
cat >compiler.sh <<EOF
#!/bin/sh
-printf "[%s]" "\$*" >>compiler.args
+printf "(%s)" "\$*" >>compiler.args
[ \$1 = -E ] && echo test || echo test >test1.o
EOF
chmod +x compiler.sh
backdate compiler.sh
$CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
if [ -z "$CCACHE_NOCPP2" ]; then
- expect_content compiler.args "[-E test1.c][-c -o test1.o test1.c]"
+ expect_content_pattern compiler.args "(-E -o * test1.c)(-c -o test1.o test1.c)"
fi
rm compiler.args
$CCACHE ./compiler.sh -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
- expect_content compiler.args "[-E test1.c]"
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_content_pattern compiler.args "(-E -o * test1.c)"
rm compiler.args
# Even though -Werror is not passed to the preprocessor, it should be part
# of the hash, so we expect a cache miss:
$CCACHE ./compiler.sh -c -Werror -rdynamic test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
if [ -z "$CCACHE_NOCPP2" ]; then
- expect_content compiler.args "[-E test1.c][-Werror -rdynamic -c -o test1.o test1.c]"
+ expect_content_pattern compiler.args "(-E -o * test1.c)(-Werror -rdynamic -c -o test1.o test1.c)"
fi
rm compiler.args
# -------------------------------------------------------------------------
- TEST "Dependency file content"
-
- mkdir build
- cp test1.c build
-
+if ! $COMPILER_USES_MSVC; then
for src in test1.c build/test1.c; do
for obj in test1.o build/test1.o; do
+ TEST "Dependency file content, $src -o $obj"
+ mkdir build
+ cp test1.c build
$CCACHE_COMPILE -c -MMD $src -o $obj
dep=$(echo $obj | sed 's/\.o$/.d/')
expect_content $dep "$obj: $src"
done
done
-
+fi
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST "Buggy GCC 6 cpp"
-
- cat >buggy-cpp <<EOF
+ cat >buggy-cpp.sh <<EOF
#!/bin/sh
-CCACHE_DISABLE=1 # If $COMPILER happens to be a ccache symlink...
-export CCACHE_DISABLE
if echo "\$*" | grep -- -D >/dev/null; then
$COMPILER "\$@"
else
cat <<'EOF' >file.c
int foo;
EOF
- chmod +x buggy-cpp
+ chmod +x buggy-cpp.sh
- $CCACHE ./buggy-cpp -c file.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
-
- $CCACHE ./buggy-cpp -DNOT_AFFECTING=1 -c file.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ $CCACHE ./buggy-cpp.sh -c file.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ $CCACHE ./buggy-cpp.sh -DNOT_AFFECTING=1 -c file.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+fi
# -------------------------------------------------------------------------
-if ! $HOST_OS_WINDOWS; then
TEST ".incbin"
+ touch empty.bin
+
cat <<EOF >incbin.c
-__asm__(".incbin \"/dev/null\"");
+__asm__(".incbin \"empty.bin\"");
EOF
$CCACHE_COMPILE -c incbin.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat 'unsupported code directive' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat unsupported_code_directive 1
+
+ cat <<EOF >incbin.c
+__asm__(".incbin" " \"empty.bin\"");
+EOF
+
+ $CCACHE_COMPILE -c incbin.c
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat unsupported_code_directive 2
cat <<EOF >incbin.s
-.incbin "/dev/null";
+.incbin "empty.bin";
EOF
$CCACHE_COMPILE -c incbin.s
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat 'unsupported code directive' 2
-fi
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat unsupported_code_directive 3
+
+ cat <<EOF >incbin.cpp
+ struct A {
+ void incbin() const {}
+ };
+ void f()
+ {
+ A a;
+ a.incbin();
+ }
+EOF
+ if $CCACHE_COMPILE -x c++ -c incbin.cpp 2>/dev/null; then
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat unsupported_code_directive 3
+ fi
# -------------------------------------------------------------------------
if ! $HOST_OS_WINDOWS; then
N=1 $CCACHE ./compiler.sh -c test1.c 2>stderr.txt
stderr=$(cat stderr.txt)
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
if [ "$stderr" != "1Pu1Cu1Cc" ]; then
test_failed "Unexpected stderr: $stderr != 1Pu1Cu1Cc"
fi
N=2 $CCACHE ./compiler.sh -c test1.c 2>stderr.txt
stderr=$(cat stderr.txt)
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
if [ "$stderr" != "2Pu1Cc" ]; then
test_failed "Unexpected stderr: $stderr != 2Pu1Cc"
fi
+SUITE_basedir_PROBE() {
+ if ! $RUN_WIN_XFAIL; then
+ echo "CCACHE_BASEDIR is broken on windows."
+ fi
+}
+
SUITE_basedir_SETUP() {
unset CCACHE_NODIRECT
# -------------------------------------------------------------------------
TEST "Enabled CCACHE_BASEDIR"
+ CCACHE_BASEDIR=/ $CCACHE_COMPILE --version >/dev/null
+ expect_stat no_input_file 1
+
cd dir1
CCACHE_BASEDIR="`pwd`" $CCACHE_COMPILE -I`pwd`/include -c src/test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
cd ../dir2
CCACHE_BASEDIR="`pwd`" $CCACHE_COMPILE -I`pwd`/include -c src/test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Disabled (default) CCACHE_BASEDIR"
cd dir1
CCACHE_BASEDIR="`pwd`" $CCACHE_COMPILE -I`pwd`/include -c src/test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# CCACHE_BASEDIR="" is the default:
$CCACHE_COMPILE -I`pwd`/include -c src/test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
if ! $HOST_OS_WINDOWS && ! $HOST_OS_CYGWIN; then
cd dir1
CCACHE_BASEDIR="`pwd`" $CCACHE_COMPILE -I$(pwd)/include -c src/test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
mkdir subdir
# Rewriting triggered by CCACHE_BASEDIR should handle paths with multiple
# slashes, redundant "/." parts and "foo/.." parts correctly.
CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE -I$(pwd)//./subdir/../include -c $(pwd)/src/test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
fi
# -------------------------------------------------------------------------
ln -s d1/d2 d3
CCACHE_BASEDIR=/ $CCACHE_COMPILE -c $PWD/d3/c.c
- $REAL_COMPILER c.o -o c
+ $COMPILER c.o -o c
if [ "$(./c)" != OK ]; then
test_failed "Incorrect header file used"
fi
ln -s d/c.c c.c
CCACHE_BASEDIR=/ $CCACHE_COMPILE -c $PWD/c.c
- $REAL_COMPILER c.o -o c
+ $COMPILER c.o -o c
if [ "$(./c)" != OK ]; then
test_failed "Incorrect header file used"
fi
cd dir1/src
CCACHE_BASEDIR=/ $CCACHE_COMPILE -I$(pwd)/../include -c $(pwd)/test.c -o $(pwd)/build/test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
cd ../../dir2/src
# Apparent CWD:
CCACHE_BASEDIR=/ $CCACHE_COMPILE -I$(pwd)/../include -c $(pwd)/test.c -o $(pwd)/build/test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
# Actual CWD (e.g. from $(CURDIR) in a Makefile):
CCACHE_BASEDIR=/ $CCACHE_COMPILE -I$(pwd -P)/../include -c $(pwd -P)/test.c -o $(pwd -P)/build/test.o
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
fi
# -------------------------------------------------------------------------
cd build1
CCACHE_BASEDIR=/ $CCACHE_COMPILE -I$(pwd)/src/include -c $(pwd)/src/src/test.c -o $(pwd)/test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
cd ../build2
# Apparent CWD:
CCACHE_BASEDIR=/ $CCACHE_COMPILE -I$(pwd)/src/include -c $(pwd)/src/src/test.c -o $(pwd)/test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
# Actual CWD:
CCACHE_BASEDIR=/ $CCACHE_COMPILE -I$(pwd -P)/src/include -c $(pwd -P)/src/src/test.c -o $(pwd -P)/test.o
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
fi
# -------------------------------------------------------------------------
EOF
CCACHE_BASEDIR=`pwd` $CCACHE_COMPILE -Wall -W -I`pwd` -c `pwd`/stderr.c -o `pwd`/stderr.o 2>stderr.txt
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
if grep `pwd` stderr.txt >/dev/null 2>&1; then
test_failed "Base dir (`pwd`) found in stderr:\n`cat stderr.txt`"
fi
CCACHE_BASEDIR=`pwd` $CCACHE_COMPILE -Wall -W -I`pwd` -c `pwd`/stderr.c -o `pwd`/stderr.o 2>stderr.txt
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
if grep `pwd` stderr.txt >/dev/null 2>&1; then
test_failed "Base dir (`pwd`) found in stderr:\n`cat stderr.txt`"
fi
# -------------------------------------------------------------------------
- TEST "-MF/-MQ/-MT with absolute paths"
+ if $HOST_OS_WINDOWS; then
+ additional_options=
+ else
+ additional_options=(MF)
+ fi
+ for option in "MF " $additional_options; do
+ TEST "-${option}/absolute/path"
- for option in MF "MF " MQ "MQ " MT "MT "; do
- clear_cache
cd dir1
- CCACHE_BASEDIR="`pwd`" $CCACHE_COMPILE -I`pwd`/include -MD -${option}`pwd`/test.d -c src/test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE -I"$(pwd)/include" -MMD -${option}"$(pwd)/foo.d" -c src/test.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_content_pattern foo.d "test.o:*"
cd ..
cd dir2
- CCACHE_BASEDIR="`pwd`" $CCACHE_COMPILE -I`pwd`/include -MD -${option}`pwd`/test.d -c src/test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE -I"$(pwd)/include" -MMD -${option}"$(pwd)/foo.d" -c src/test.c
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_content_pattern foo.d "test.o:*"
cd ..
done
+ # -------------------------------------------------------------------------
+ if $HOST_OS_WINDOWS; then
+ additional_options=
+ else
+ additional_options=(MQ MT)
+ fi
+ for option in "MQ " "MT " $additional_options; do
+ TEST "-${option}/absolute/path"
+
+ cd dir1
+
+ CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE -I"$(pwd)/include" -MMD -${option}"$(pwd)/foo.o" -c src/test.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_content_pattern test.d "$(pwd)/foo.o:*"
+ cd ..
+
+ cd dir2
+ CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE -I"$(pwd)/include" -MMD -${option}"$(pwd)/foo.o" -c src/test.c
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_content_pattern test.d "$(pwd)/foo.o:*"
+ cd ..
+ done
# -------------------------------------------------------------------------
# When BASEDIR is set to /, check that -MF, -MQ and -MT arguments with
# absolute paths are rewritten to relative and that the dependency file
# only contains relative paths.
TEST "-MF/-MQ/-MT with absolute paths and BASEDIR set to /"
- for option in MF "MF " MQ "MQ " MT "MT "; do
+ BASEDIR="/"
+ if $HOST_OS_WINDOWS; then
+ # Windows uses drives therefore "/" has no meaning, thus default to drive
+ BASEDIR=`cygpath -m "\\."`
+ fi
+
+ if $HOST_OS_WINDOWS; then
+ additional_options=
+ else
+ additional_options=(MF MQ MT)
+ fi
+ for option in "MF " "MQ " "MT " $additional_options; do
clear_cache
cd dir1
- CCACHE_BASEDIR="/" $CCACHE_COMPILE -I`pwd`/include -MD -${option}`pwd`/test.d -c src/test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+
+ CCACHE_BASEDIR=$BASEDIR $CCACHE_COMPILE -I`pwd`/include -MD -${option}`pwd`/test.d -c src/test.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# Check that there is no absolute path in the dependency file:
while read line; do
- for file in $line; do
- case $file in /*)
- test_failed "Absolute file path '$file' found in dependency file '`pwd`/test.d'"
- esac
+ for token in $line; do
+ if [[ $token == /* && $token != *: ]]; then
+ test_failed "Absolute file path '$token' found in dependency file '$(pwd)/test.d'"
+ fi
done
done <test.d
cd ..
cd dir2
- CCACHE_BASEDIR="/" $CCACHE_COMPILE -I`pwd`/include -MD -${option}`pwd`/test.d -c src/test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ CCACHE_BASEDIR=$BASEDIR $CCACHE_COMPILE -I`pwd`/include -MD -${option}`pwd`/test.d -c src/test.c
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
cd ..
done
-
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST "Absolute paths in stderr"
cat <<EOF >test.c
EOF
backdate test.h
- pwd=$PWD.real
- $REAL_COMPILER -c $pwd/test.c 2>reference.stderr
+ pwd="$(pwd -P)"
+ $COMPILER -c $pwd/test.c 2>reference.stderr
CCACHE_ABSSTDERR=1 CCACHE_BASEDIR="$pwd" $CCACHE_COMPILE -c $pwd/test.c 2>ccache.stderr
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content reference.stderr ccache.stderr
CCACHE_ABSSTDERR=1 CCACHE_BASEDIR="$pwd" $CCACHE_COMPILE -c $pwd/test.c 2>ccache.stderr
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content reference.stderr ccache.stderr
- if $REAL_COMPILER -fdiagnostics-color=always -c test.c 2>/dev/null; then
- $REAL_COMPILER -fdiagnostics-color=always -c $pwd/test.c 2>reference.stderr
+ if $COMPILER_TYPE_GCC && $COMPILER -fdiagnostics-color=always -c test.c 2>/dev/null; then
+ $COMPILER -fdiagnostics-color=always -c $pwd/test.c 2>reference.stderr
CCACHE_ABSSTDERR=1 CCACHE_BASEDIR="$pwd" $CCACHE_COMPILE -fdiagnostics-color=always -c $pwd/test.c 2>ccache.stderr
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content reference.stderr ccache.stderr
CCACHE_ABSSTDERR=1 CCACHE_BASEDIR="$pwd" $CCACHE_COMPILE -fdiagnostics-color=always -c $pwd/test.c 2>ccache.stderr
- expect_stat 'cache hit (direct)' 3
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content reference.stderr ccache.stderr
fi
+fi
+ # -------------------------------------------------------------------------
+ TEST "Relative PWD"
+
+ cd dir1
+ CCACHE_BASEDIR="$(pwd)" PWD=. $CCACHE_COMPILE -I$(pwd)/include -c src/test.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
+ cd ../dir2
+ CCACHE_BASEDIR="$(pwd)" PWD=. $CCACHE_COMPILE -I$(pwd)/include -c src/test.c
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
+ # -------------------------------------------------------------------------
+ TEST "Unset PWD"
+
+ unset PWD
+
+ cd dir1
+ CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE -I$(pwd)/include -c src/test.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
+ cd ../dir2
+ CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE -I$(pwd)/include -c src/test.c
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
+ # -------------------------------------------------------------------------
+ TEST "Object token path in dependency file"
+
+ cd dir1
+
+ CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE -MMD -I$(pwd)/include -c src/test.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_content_pattern test.d "test.o:*"
+
+ CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE -MMD -I$(pwd)/include -c src/test.c -o test.o
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_contains test.d test.o:
+ expect_content_pattern test.d "test.o:*"
+
+ CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE -MMD -I$(pwd)/include -c src/test.c -o $(pwd)/test.o
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_contains test.d test.o:
+ expect_content_pattern test.d "$(pwd)/test.o:*"
+
+ CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE -MMD -I$(pwd)/include -c $(pwd)/src/test.c
+ expect_stat direct_cache_hit 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_contains test.d test.o:
+ expect_content_pattern test.d "test.o:*"
}
TEST "Empty cache"
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_on_level R 2
expect_on_level M 2
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_on_level R 2
expect_on_level M 2
add_fake_files_counters $files
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' $((files + 2))
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache $((files + 2))
expect_on_level R 2
expect_on_level M 2
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' $((files + 2))
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache $((files + 2))
expect_on_level R 2
expect_on_level M 2
add_fake_files_counters $files
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' $((files + 2))
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache $((files + 2))
expect_on_level R 3
expect_on_level M 3
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' $((files + 2))
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache $((files + 2))
expect_on_level R 3
expect_on_level M 3
add_fake_files_counters $files
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' $((files + 2))
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache $((files + 2))
expect_on_level R 4
expect_on_level M 4
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' $((files + 2))
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache $((files + 2))
expect_on_level R 4
expect_on_level M 4
add_fake_files_counters $files
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' $((files + 2))
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache $((files + 2))
expect_on_level R 4
expect_on_level M 4
$CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' $((files + 2))
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache $((files + 2))
expect_on_level R 4
expect_on_level M 4
}
$CCACHE -C >/dev/null
expect_file_count 0 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 0
- expect_stat 'cleanups performed' 1
+ expect_stat files_in_cache 0
+ expect_stat cleanups_performed 1
# -------------------------------------------------------------------------
TEST "Forced cache cleanup, no limits"
$CCACHE -F 0 -M 0 >/dev/null
$CCACHE -c >/dev/null
expect_file_count 10 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 10
- expect_stat 'cleanups performed' 0
+ expect_stat files_in_cache 10
+ expect_stat cleanups_performed 0
# -------------------------------------------------------------------------
TEST "Forced cache cleanup, file limit"
$CCACHE -F 160 -M 0 >/dev/null
$CCACHE -c >/dev/null
expect_file_count 10 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 10
- expect_stat 'cleanups performed' 0
+ expect_stat files_in_cache 10
+ expect_stat cleanups_performed 0
# Reduce file limit
#
$CCACHE -F 112 -M 0 >/dev/null
$CCACHE -c >/dev/null
expect_file_count 7 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 7
- expect_stat 'cleanups performed' 1
+ expect_stat files_in_cache 7
+ expect_stat cleanups_performed 1
for i in 0 1 2; do
file=$CCACHE_DIR/a/result${i}R
expect_missing $CCACHE_DIR/a/result${i}R
$CCACHE -F 0 -M 256K >/dev/null
$CCACHE -c >/dev/null
expect_file_count 3 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 3
- expect_stat 'cleanups performed' 1
+ expect_stat files_in_cache 3
+ expect_stat cleanups_performed 1
for i in 0 1 2 3 4 5 6; do
file=$CCACHE_DIR/a/result${i}R
expect_missing $file
$CCACHE -F 160 -M 0 >/dev/null
expect_file_count 160 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 160
- expect_stat 'cleanups performed' 0
+ expect_stat files_in_cache 160
+ expect_stat cleanups_performed 0
touch empty.c
CCACHE_LIMIT_MULTIPLE=0.9 $CCACHE_COMPILE -c empty.c -o empty.o
expect_file_count 159 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 159
- expect_stat 'cleanups performed' 1
+ expect_stat files_in_cache 159
+ expect_stat cleanups_performed 1
# -------------------------------------------------------------------------
TEST "Automatic cache cleanup, limit_multiple 0.7"
$CCACHE -F 160 -M 0 >/dev/null
expect_file_count 160 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 160
- expect_stat 'cleanups performed' 0
+ expect_stat files_in_cache 160
+ expect_stat cleanups_performed 0
touch empty.c
CCACHE_LIMIT_MULTIPLE=0.7 $CCACHE_COMPILE -c empty.c -o empty.o
expect_file_count 157 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 157
- expect_stat 'cleanups performed' 1
+ expect_stat files_in_cache 157
+ expect_stat cleanups_performed 1
# -------------------------------------------------------------------------
TEST "No cleanup of new unknown file"
touch $CCACHE_DIR/a/abcd.unknown
$CCACHE -F 0 -M 0 -c >/dev/null # update counters
- expect_stat 'files in cache' 11
+ expect_stat files_in_cache 11
$CCACHE -F 160 -M 0 >/dev/null
$CCACHE -c >/dev/null
expect_exists $CCACHE_DIR/a/abcd.unknown
- expect_stat 'files in cache' 10
+ expect_stat files_in_cache 10
# -------------------------------------------------------------------------
TEST "Cleanup of old unknown file"
touch $CCACHE_DIR/a/abcd.unknown
backdate $CCACHE_DIR/a/abcd.unknown
$CCACHE -F 0 -M 0 -c >/dev/null # update counters
- expect_stat 'files in cache' 11
+ expect_stat files_in_cache 11
$CCACHE -F 160 -M 0 -c >/dev/null
expect_missing $CCACHE_DIR/a/abcd.unknown
- expect_stat 'files in cache' 10
+ expect_stat files_in_cache 10
# -------------------------------------------------------------------------
TEST "Cleanup of tmp file"
mkdir -p $CCACHE_DIR/a
touch $CCACHE_DIR/a/abcd.tmp.efgh
$CCACHE -c >/dev/null # update counters
- expect_stat 'files in cache' 1
+ expect_stat files_in_cache 1
backdate $CCACHE_DIR/a/abcd.tmp.efgh
$CCACHE -c >/dev/null
expect_missing $CCACHE_DIR/a/abcd.tmp.efgh
- expect_stat 'files in cache' 0
+ expect_stat files_in_cache 0
# -------------------------------------------------------------------------
TEST "No cleanup of .nfs* files"
$CCACHE -F 0 -M 0 >/dev/null
$CCACHE -c >/dev/null
expect_file_count 1 '.nfs*' $CCACHE_DIR
- expect_stat 'files in cache' 10
+ expect_stat files_in_cache 10
# -------------------------------------------------------------------------
TEST "Cleanup of old files by age"
$CCACHE --evict-older-than 1d >/dev/null
expect_file_count 1 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 1
+ expect_stat files_in_cache 1
$CCACHE --evict-older-than 1d >/dev/null
expect_file_count 1 '*R' $CCACHE_DIR
- expect_stat 'files in cache' 1
+ expect_stat files_in_cache 1
backdate $CCACHE_DIR/a/nowR
$CCACHE --evict-older-than 10s >/dev/null
- expect_stat 'files in cache' 0
+ expect_stat files_in_cache 0
}
return
fi
+ if ! $RUN_WIN_XFAIL; then
+ echo "color_diagnostics tests are broken on Windows."
+ return
+ fi
+
# Probe that real compiler actually supports colored diagnostics.
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 ! $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 ! $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
}
# Check that subsequently running on a TTY generates a cache hit.
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
+ expect_stat cache_miss 1
+ expect_stat preprocessed_cache_hit 1
# -------------------------------------------------------------------------
TEST "Colored diagnostics automatically enabled when stderr is a TTY (run_second_cpp=$run_second_cpp)"
# Check that subsequently running without a TTY generates a cache hit.
$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
+ expect_stat cache_miss 1
+ expect_stat preprocessed_cache_hit 1
if $COMPILER_TYPE_GCC; then
# ---------------------------------------------------------------------
if $CCACHE_COMPILE -fcolor-diagnostics -c test.c >&/dev/null; then
test_failed "-fcolor-diagnostics unexpectedly accepted by GCC"
fi
- expect_stat 'unsupported compiler option' 1
+ expect_stat preprocessor_error 1
# ---------------------------------------------------------------------
TEST "-fcolor-diagnostics not accepted for GCC for cached result"
if ! $CCACHE_COMPILE -c test.c >&/dev/null; then
test_failed "unknown error compiling"
fi
-
if $CCACHE_COMPILE -fcolor-diagnostics -c test.c >&/dev/null; then
test_failed "-fcolor-diagnostics unexpectedly accepted by GCC"
fi
- expect_stat 'unsupported compiler option' 1
+ expect_stat preprocessor_error 1
# ---------------------------------------------------------------------
TEST "-fcolor-diagnostics passed to underlying compiler for unknown compiler type"
generate_code 1 test.c
+ CCACHE_COMPILERTYPE=other $CCACHE_COMPILE -c test.c
+ expect_stat cache_miss 1
+
if CCACHE_COMPILERTYPE=other $CCACHE_COMPILE -fcolor-diagnostics -c test.c >&/dev/null; then
test_failed "-fcolor-diagnostics unexpectedly accepted by GCC"
fi
-
- # Verify that -fcolor-diagnostics was passed to the compiler for the
- # unknown compiler case, i.e. ccache did not exit early with
- # "unsupported compiler option".
- expect_stat 'compile failed' 1
+ expect_stat preprocessor_error 1
fi
if $COMPILER_TYPE_CLANG; then
;;
esac
done
- expect_stat 'cache miss' 1
- expect_stat 'cache hit (preprocessed)' 3
+ expect_stat cache_miss 1
+ expect_stat preprocessed_cache_hit 3
done < <(
A=({color,nocolor},{tty,notty})
color_diagnostics_generate_permutations "${#A[@]}"
--- /dev/null
+SUITE_config() {
+ # -------------------------------------------------------------------------
+ TEST "Environment origin"
+
+ export CCACHE_MAXSIZE="40"
+
+ $CCACHE --max-size "75" >/dev/null
+ $CCACHE --show-config >config.txt
+
+ expect_contains config.txt "(environment) max_size = 40"
+}
SUITE_cpp1_PROBE() {
touch test.c
if $COMPILER_TYPE_GCC; then
- if ! $REAL_COMPILER -E -fdirectives-only test.c >&/dev/null; then
+ if ! $COMPILER -E -fdirectives-only test.c >&/dev/null; then
echo "-fdirectives-only not supported by compiler"
return
fi
elif $COMPILER_TYPE_CLANG; then
- if ! $REAL_COMPILER -E -frewrite-includes test.c >&/dev/null; then
+ if ! $COMPILER -E -frewrite-includes test.c >&/dev/null; then
echo "-frewrite-includes not supported by compiler"
return
fi
+ if $HOST_OS_WINDOWS && ! $COMPILER_USES_MSVC; then
+ echo "This test is broken on msys2 clang: Stores wrong file names like 'tmp.cpp_stdout.2Gq' instead of 'test1.c'."
+ return
+ fi
else
echo "Unknown compiler: $COMPILER"
return
# -------------------------------------------------------------------------
TEST "Base case"
- $REAL_COMPILER $cpp_flag -c -o reference_test1.o test1.c
+ $COMPILER $cpp_flag -c -o reference_test1.o test1.c
$CCACHE_COMPILE $cpp_flag -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
unset CCACHE_NODIRECT
$CCACHE_COMPILE $cpp_flag -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_object_files reference_test1.o test1.o
$CCACHE_COMPILE $cpp_flag -c test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_object_files reference_test1.o test1.o
}
SUITE_debug_prefix_map_PROBE() {
touch test.c
- if ! $REAL_COMPILER -c -fdebug-prefix-map=old=new test.c 2>/dev/null; then
+ if ! $COMPILER -c -fdebug-prefix-map=old=new test.c 2>/dev/null; then
echo "-fdebug-prefix-map not supported by compiler"
fi
+
+ if ! $RUN_WIN_XFAIL; then
+ echo "debug_prefix_map tests are broken on Windows."
+ return
+ fi
}
SUITE_debug_prefix_map_SETUP() {
cd dir1
CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE -I$(pwd)/include -g -fdebug-prefix-map=$(pwd)=some_name_not_likely_to_exist_in_path -c $(pwd)/src/test.c -o $(pwd)/test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_objdump_not_contains test.o "$(pwd)"
expect_objdump_contains test.o some_name_not_likely_to_exist_in_path
cd ../dir2
CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE -I$(pwd)/include -g -fdebug-prefix-map=$(pwd)=some_name_not_likely_to_exist_in_path -c $(pwd)/src/test.c -o $(pwd)/test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_objdump_not_contains test.o "$(pwd)"
# -------------------------------------------------------------------------
cd dir1
CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE -I$(pwd)/include -g -fdebug-prefix-map=$(pwd)=some_name_not_likely_to_exist_in_path -fdebug-prefix-map=foo=bar -c $(pwd)/src/test.c -o $(pwd)/test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_objdump_not_contains test.o "$(pwd)"
expect_objdump_contains test.o some_name_not_likely_to_exist_in_path
cd ../dir2
CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE -I$(pwd)/include -g -fdebug-prefix-map=$(pwd)=some_name_not_likely_to_exist_in_path -fdebug-prefix-map=foo=bar -c $(pwd)/src/test.c -o $(pwd)/test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_objdump_not_contains test.o "$(pwd)"
}
EOF
backdate test1.h test2.h test3.h
- $REAL_COMPILER -c -Wp,-MD,expected.d test.c
- $REAL_COMPILER -c -Wp,-MMD,expected_mmd.d test.c
+ $COMPILER -c -Wp,-MD,expected.d test.c
+ $COMPILER -c -Wp,-MMD,expected_mmd.d test.c
rm test.o
DEPSFLAGS_REAL="-MP -MMD -MF reference_test.d"
BASEDIR2="$BASEDIR/test/dir2"
BASEDIR3="$BASEDIR/test/dir3"
BASEDIR4="$BASEDIR/test/dir4"
+ BASEDIR5="$BASEDIR/test/dir5"
- mkdir -p $BASEDIR1 $BASEDIR2 $BASEDIR3 $BASEDIR4
+ mkdir -p $BASEDIR1 $BASEDIR2 $BASEDIR3 $BASEDIR4 $BASEDIR5
cat <<EOF >$BASEDIR1/test.c
#include "header.h"
cp -f "$BASEDIR1/test.c" $BASEDIR2
cp -f "$BASEDIR1/test.c" $BASEDIR3
cp -f "$BASEDIR1/test.c" $BASEDIR4
+ cp -f "$BASEDIR1/test.c" $BASEDIR5
cat <<EOF >"$BASEDIR1/header.h"
void test();
EOF
cat <<EOF >"$BASEDIR4/header2.h"
static void some_function(){};
+EOF
+
+ cat <<EOF >"$BASEDIR5/header.h"
+void test();
EOF
backdate "$BASEDIR1/header.h" "$BASEDIR1/test.c"
backdate "$BASEDIR2/header.h" "$BASEDIR2/test.c"
backdate "$BASEDIR3/header.h" "$BASEDIR3/test.c"
backdate "$BASEDIR4/header.h" "$BASEDIR4/test.c" "$BASEDIR4/header2.h"
+ backdate "$BASEDIR5/header.h" "$BASEDIR5/test.c"
DEPFLAGS="-MD -MF test.d"
}
generate_reference_compiler_output() {
+ local filename
+ if [[ $# -gt 0 ]]; then
+ filename=$1
+ else
+ filename=test.c
+ fi
rm -f *.o *.d
- $REAL_COMPILER $DEPFLAGS -c -o test.o test.c
+ $COMPILER $DEPFLAGS -c -o test.o $filename
mv test.o reference_test.o
mv test.d reference_test.d
}
# -------------------------------------------------------------------------
TEST "Base case"
- $REAL_COMPILER $DEPSFLAGS_REAL -c -o reference_test.o test.c
+ $COMPILER $DEPSFLAGS_REAL -c -o reference_test.o test.c
CCACHE_DEPEND=1 $CCACHE_COMPILE $DEPSFLAGS_CCACHE -c test.c
expect_equal_object_files reference_test.o test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2 # result + manifest
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat direct_cache_miss 1
+ expect_stat preprocessed_cache_miss 0
+ expect_stat files_in_cache 2 # result + manifest
CCACHE_DEPEND=1 $CCACHE_COMPILE $DEPSFLAGS_CCACHE -c test.c
expect_equal_object_files reference_test.o test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat direct_cache_miss 1
+ expect_stat preprocessed_cache_miss 0
+ expect_stat files_in_cache 2
# -------------------------------------------------------------------------
TEST "No dependency file"
CCACHE_DEPEND=1 $CCACHE_COMPILE -MP -MMD -MF /dev/null -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2 # result + manifest
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # result + manifest
CCACHE_DEPEND=1 $CCACHE_COMPILE -MP -MMD -MF /dev/null -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
# -------------------------------------------------------------------------
TEST "No explicit dependency file"
- $REAL_COMPILER $DEPSFLAGS_REAL -c -o reference_test.o test.c
+ $COMPILER $DEPSFLAGS_REAL -c -o reference_test.o test.c
CCACHE_DEPEND=1 $CCACHE_COMPILE -MD -c test.c
expect_equal_object_files reference_test.o test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2 # result + manifest
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # result + manifest
CCACHE_DEPEND=1 $CCACHE_COMPILE -MD -c test.c
expect_equal_object_files reference_test.o test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
# -------------------------------------------------------------------------
TEST "Dependency file paths converted to relative if CCACHE_BASEDIR specified"
// Trigger compiler warning by having no return statement.
}
EOF
- $REAL_COMPILER -MD -Wall -W -c cpp-warning.c 2>stderr-baseline.txt
+ $COMPILER -MD -Wall -W -c cpp-warning.c 2>stderr-baseline.txt
CCACHE_DEPEND=1 $CCACHE_COMPILE -MD -Wall -W -c cpp-warning.c 2>stderr-orig.txt
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_content stderr-orig.txt "`cat stderr-baseline.txt`"
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_equal_text_content stderr-orig.txt stderr-baseline.txt
CCACHE_DEPEND=1 $CCACHE_COMPILE -MD -Wall -W -c cpp-warning.c 2>stderr-mf.txt
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_content stderr-mf.txt "`cat stderr-baseline.txt`"
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_equal_text_content stderr-mf.txt stderr-baseline.txt
# -------------------------------------------------------------------------
# This test case covers a case in depend mode with unchanged source file
# dir2 has a change in header which affects object file
# dir3 has a change in header which does not affect object file
# dir4 has an additional include header which should change the dependency file
+ # dir5 has no changes, only a different base directory
TEST "Different sets of headers for the same source code"
set_up_different_sets_of_headers_test
CCACHE_DEPEND=1 CCACHE_BASEDIR=$BASEDIR1 $CCACHE_COMPILE $DEPFLAGS -c test.c
expect_equal_object_files reference_test.o test.o
expect_equal_content reference_test.d test.d
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2 # result + manifest
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # result + manifest
# Recompile dir1 first time.
generate_reference_compiler_output
CCACHE_DEPEND=1 CCACHE_BASEDIR=$BASEDIR1 $CCACHE_COMPILE $DEPFLAGS -c test.c
expect_equal_object_files reference_test.o test.o
expect_equal_content reference_test.d test.d
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
# Compile dir2. dir2 header changes the object file compared to dir1.
cd $BASEDIR2
CCACHE_DEPEND=1 CCACHE_BASEDIR=$BASEDIR2 $CCACHE_COMPILE $DEPFLAGS -c test.c
expect_equal_object_files reference_test.o test.o
expect_equal_content reference_test.d test.d
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 3 # 2x result, 1x manifest
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 3 # 2x result, 1x manifest
# Compile dir3. dir3 header change does not change object file compared to
# dir1, but ccache still adds an additional .o/.d file in the cache due to
CCACHE_DEPEND=1 CCACHE_BASEDIR=$BASEDIR3 $CCACHE_COMPILE $DEPFLAGS -c test.c
expect_equal_object_files reference_test.o test.o
expect_equal_content reference_test.d test.d
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 3
- expect_stat 'files in cache' 4 # 3x result, 1x manifest
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 3
+ expect_stat files_in_cache 4 # 3x result, 1x manifest
# Compile dir4. dir4 header adds a new dependency.
cd $BASEDIR4
expect_equal_object_files reference_test.o test.o
expect_equal_content reference_test.d test.d
expect_different_content reference_test.d $BASEDIR1/test.d
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 4
- expect_stat 'files in cache' 5 # 4x result, 1x manifest
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 5 # 4x result, 1x manifest
+
+ # Compile dir5. dir5 is identical to dir1
+ cd $BASEDIR5
+ generate_reference_compiler_output
+ CCACHE_DEPEND=1 CCACHE_BASEDIR=$BASEDIR5 $CCACHE_COMPILE $DEPFLAGS -c test.c
+ expect_equal_object_files reference_test.o test.o
+ expect_equal_content reference_test.d test.d
+ expect_equal_content reference_test.d $BASEDIR1/test.d
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 5 # 4x result, 1x manifest
# Recompile dir1 second time.
cd $BASEDIR1
CCACHE_DEPEND=1 CCACHE_BASEDIR=$BASEDIR1 $CCACHE_COMPILE $DEPFLAGS -c test.c
expect_equal_object_files reference_test.o test.o
expect_equal_content reference_test.d test.d
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 4
- expect_stat 'files in cache' 5
+ expect_stat direct_cache_hit 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 5
# Recompile dir2.
cd $BASEDIR2
generate_reference_compiler_output
CCACHE_DEPEND=1 CCACHE_BASEDIR=$BASEDIR2 $CCACHE_COMPILE $DEPFLAGS -c test.c
- expect_equal_object_files test.o test.o
+ expect_equal_object_files reference_test.o test.o
expect_equal_content reference_test.d test.d
- expect_stat 'cache hit (direct)' 3
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 4
- expect_stat 'files in cache' 5
+ expect_stat direct_cache_hit 4
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 5
# Recompile dir3.
cd $BASEDIR3
CCACHE_DEPEND=1 CCACHE_BASEDIR=$BASEDIR3 $CCACHE_COMPILE $DEPFLAGS -c test.c
expect_equal_object_files reference_test.o test.o
expect_equal_content reference_test.d test.d
- expect_stat 'cache hit (direct)' 4
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 4
- expect_stat 'files in cache' 5
+ expect_stat direct_cache_hit 5
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 5
# Recompile dir4.
cd $BASEDIR4
expect_equal_object_files reference_test.o test.o
expect_equal_content reference_test.d test.d
expect_different_content reference_test.d $BASEDIR1/test.d
- expect_stat 'cache hit (direct)' 5
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 4
- expect_stat 'files in cache' 5
+ expect_stat direct_cache_hit 6
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 5
+
+ # Recompile dir5 from an absolute directory.
+ cd $BASEDIR5
+ generate_reference_compiler_output `pwd`/test.c
+ CCACHE_DEPEND=1 CCACHE_BASEDIR=$BASEDIR5 $CCACHE_COMPILE $DEPFLAGS -c `pwd`/test.c
+ expect_equal_object_files reference_test.o test.o
+ # From the manual: "One known issue is that absolute paths are not
+ # reproduced in dependency files":
+ # expect_equal_content reference_test.d test.d
+ expect_equal_content test.d $BASEDIR1/test.d
+ expect_stat direct_cache_hit 7
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 5
# -------------------------------------------------------------------------
TEST "Source file with special characters"
touch 'file with$special#characters.c'
- $REAL_COMPILER -MMD -c 'file with$special#characters.c'
+ $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_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 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_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content 'file with$special#characters.d' reference.d
}
EOF
backdate test1.h test2.h test3.h
- $REAL_COMPILER -c -Wp,-MD,expected.d test.c
- $REAL_COMPILER -c -Wp,-MMD,expected_mmd.d test.c
+ $COMPILER -c -Wp,-MD,expected.d test.c
+ $COMPILER -c -Wp,-MMD,expected_mmd.d test.c
rm test.o
}
# -------------------------------------------------------------------------
TEST "Base case"
- $REAL_COMPILER -c -o reference_test.o test.c
+ $COMPILER -c -o reference_test.o test.c
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2 # result + manifest
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat direct_cache_miss 1
+ expect_stat preprocessed_cache_miss 1
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 2 # result + manifest
+ expect_stat local_storage_write 2 # result + manifest
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 0
+ expect_stat remote_storage_write 0
+ expect_stat files_in_cache 2 # result + manifest
expect_equal_object_files reference_test.o test.o
manifest_file=$(find $CCACHE_DIR -name '*M')
backdate $manifest_file
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat direct_cache_miss 1
+ expect_stat preprocessed_cache_miss 1
+ expect_stat local_storage_hit 1
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 2 # result + manifest
+ expect_stat local_storage_read_miss 2 # result + manifest
+ expect_stat local_storage_write 2 # result + manifest
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 0
+ expect_stat remote_storage_write 0
+ expect_stat files_in_cache 2
expect_equal_object_files reference_test.o test.o
expect_newer_than $manifest_file test.c
TEST "Corrupt manifest file"
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
manifest_file=`find $CCACHE_DIR -name '*M'`
rm $manifest_file
touch $manifest_file
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "CCACHE_NODIRECT"
CCACHE_NODIRECT=1 $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_NODIRECT=1 $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Modified include file"
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo "int test3_2;" >>test3.h
backdate test3.h
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Removed but previously compiled header file"
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
rm test3.h
cat <<EOF >test1.h
backdate test1.h
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
- TEST "Calculation of dependency file names"
-
- i=0
for ext in .o .obj "" . .foo.bar; do
- rm -rf testdir
- mkdir testdir
+ TEST "Calculation of dependency file names, ext=\"${ext}\""
+ mkdir testdir
dep_file=testdir/`echo test$ext | sed 's/\.[^.]*\$//'`.d
- $CCACHE_COMPILE -MD -c test.c -o testdir/test$ext
- expect_stat 'cache hit (direct)' $((3 * i))
- expect_stat 'cache miss' $((i + 1))
+ # No -MQ/-MT:
+ $CCACHE_COMPILE -MMD -c test.c -o testdir/test$ext
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_content_pattern "${dep_file}" "testdir/test${ext}:*"
rm -f $dep_file
- $CCACHE_COMPILE -MD -c test.c -o testdir/test$ext
- expect_stat 'cache hit (direct)' $((3 * i + 1))
- expect_stat 'cache miss' $((i + 1))
- expect_exists $dep_file
- if ! grep "test$ext:" $dep_file >/dev/null 2>&1; then
- test_failed "$dep_file does not contain \"test$ext:\""
- fi
+ $CCACHE_COMPILE -MMD -c test.c -o testdir/test$ext
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_content_pattern "${dep_file}" "testdir/test${ext}:*"
+ # -MQ:
dep_target=foo.bar
- $CCACHE_COMPILE -MD -MQ $dep_target -c test.c -o testdir/test$ext
- expect_stat 'cache hit (direct)' $((3 * i + 1))
- expect_stat 'cache miss' $((i + 2))
+ $CCACHE_COMPILE -MMD -MQ $dep_target -c test.c -o testdir/test$ext
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_content_pattern "${dep_file}" "${dep_target}:*"
rm -f $dep_target
- $CCACHE_COMPILE -MD -MQ $dep_target -c test.c -o testdir/test$ext
- expect_stat 'cache hit (direct)' $((3 * i + 2))
- expect_stat 'cache miss' $((i + 2))
- expect_exists $dep_file
- if ! grep $dep_target $dep_file >/dev/null 2>&1; then
- test_failed "$dep_file does not contain $dep_target"
- fi
+ $CCACHE_COMPILE -MMD -MQ $dep_target -c test.c -o testdir/test$ext
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+ expect_content_pattern "${dep_file}" "${dep_target}:*"
- i=$((i + 1))
+ dep_target=foo.bar
+ $CCACHE_COMPILE -MMD -MQ $dep_target -c test.c -o testdir/test$ext
+ expect_stat direct_cache_hit 3
+ expect_stat cache_miss 2
+ expect_content_pattern "${dep_file}" "${dep_target}:*"
done
- expect_stat 'files in cache' $((2 * i + 2))
# -------------------------------------------------------------------------
+if ! $COMPILER_USES_MSVC; then
TEST "-MMD for different source files"
mkdir a b
$CCACHE_COMPILE -MMD -c a/source.c -o a/source.o
expect_content a/source.d "a/source.o: a/source.c"
-
+fi
# -------------------------------------------------------------------------
- for dep_args in "-MMD" "-MMD -MF foo.d" "-Wp,-MMD,foo.d"; do
- for obj_args in "" "-o bar.o"; do
- if [ "$dep_args" != "-MMD" ]; then
- dep_file=foo.d
- another_dep_file=foo.d
- elif [ -z "$obj_args" ]; then
- dep_file=test.d
+ dep_args_combinations=(
+ "-MMD"
+ "-MMD -MF a.d -MF mf.d"
+ "-MFmf.d -MMD"
+ "-MMD -Wp,-MMD,wp.d"
+ "-Wp,-MMD,wp.d -MMD"
+ "-MT foo.o -Wp,-MMD,wp.d"
+ "-MQ foo.o -Wp,-MMD,wp.d"
+ )
+ for dep_args in "${dep_args_combinations[@]}"; do
+ for obj_args in "" "-o obj.o"; do
+ if [[ ${dep_args} == *-Wp,* ]]; then
+ dep_file=wp.d
+ another_dep_file=wp.d
+ elif [[ ${dep_args} == *-MF* ]]; then
+ dep_file=mf.d
+ another_dep_file=mf.d
+ elif [[ -n ${obj_args} ]]; then
+ dep_file=obj.d
another_dep_file=another.d
else
- dep_file=bar.d
+ dep_file=test.d
another_dep_file=another.d
fi
TEST "Dependency file content, $dep_args $obj_args"
# -----------------------------------------------------------------
- $REAL_COMPILER -c test.c $dep_args $obj_args
- mv $dep_file $dep_file.real
+ $COMPILER -c test.c $dep_args $obj_args
+ mv $dep_file $dep_file.reference
- $REAL_COMPILER -c test.c $dep_args -o another.o
- mv $another_dep_file another.d.real
+ $COMPILER -c test.c $dep_args -o another.o
+ mv $another_dep_file another.d.reference
# cache miss
$CCACHE_COMPILE -c test.c $dep_args $obj_args
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_equal_text_content $dep_file.real $dep_file
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_equal_content $dep_file.reference $dep_file
# cache hit
$CCACHE_COMPILE -c test.c $dep_args $obj_args
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_equal_text_content $dep_file.real $dep_file
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_equal_content $dep_file.reference $dep_file
# change object file name
$CCACHE_COMPILE -c test.c $dep_args -o another.o
- expect_equal_text_content another.d.real $another_dep_file
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_equal_content another.d.reference $another_dep_file
done
done
+ # -----------------------------------------------------------------
+ TEST "Unsupported -Wp,-MMD with -MF"
+
+ $CCACHE_COMPILE -c test.c -Wp,-MMD,wp.d -MF mf.d -o object.o
+ expect_stat unsupported_compiler_option 1
+
# -------------------------------------------------------------------------
TEST "-MD/-MMD dependency target rewriting"
obj=$dir/$name.o
dep=$(echo $obj | sed 's/\.o$/.d/')
- $REAL_COMPILER $option -c test1.c -o $obj
+ $COMPILER $option -c test1.c -o $obj
mv $dep orig.d
$CCACHE_COMPILE $option -c test1.c -o $obj
diff -u orig.d $dep
expect_equal_content $dep orig.d
- expect_stat 'cache hit (direct)' $i
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit $i
+ expect_stat cache_miss 1
i=$((i + 1))
done
for name in test2 obj1 obj2; do
obj=$dir1/$name.o
dep=$(echo $obj | sed 's/\.o$/.d/')
- $REAL_COMPILER -MMD -c $src -o $obj
+ $COMPILER -MMD -c $src -o $obj
mv $dep $orig_dep
rm $obj
$CCACHE_COMPILE -MMD -c $src -o $obj
dep=$(echo $obj | sed 's/\.o$/.d/')
expect_content $dep "$obj: $src"
- expect_stat 'cache hit (direct)' $hit
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit $hit
+ expect_stat cache_miss 1
hit=$((hit + 1))
rm $orig_dep
done
# -------------------------------------------------------------------------
+if ! $COMPILER_USES_MSVC; then
TEST "Dependency file content"
mkdir build
expect_content $dep "$obj: $src"
done
done
-
+fi
# -------------------------------------------------------------------------
+if ! $COMPILER_USES_MSVC; then
TEST "-MMD for different include file paths"
mkdir a b
$CCACHE_COMPILE -MMD -Ia -c source.c
expect_content source.d "source.o: source.c a/source.h"
-
+fi
# -------------------------------------------------------------------------
TEST "-Wp,-MD"
$CCACHE_COMPILE -c -Wp,-MD,other.d test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content other.d expected.d
- $REAL_COMPILER -c -Wp,-MD,other.d test.c -o reference_test.o
+ $COMPILER -c -Wp,-MD,other.d test.c -o reference_test.o
expect_equal_object_files reference_test.o test.o
rm -f other.d
$CCACHE_COMPILE -c -Wp,-MD,other.d test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content other.d expected.d
expect_equal_object_files reference_test.o test.o
$CCACHE_COMPILE -c -Wp,-MD,different_name.d test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content different_name.d expected.d
expect_equal_object_files reference_test.o test.o
TEST "-Wp,-MMD"
$CCACHE_COMPILE -c -Wp,-MMD,other.d test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content other.d expected_mmd.d
- $REAL_COMPILER -c -Wp,-MMD,other.d test.c -o reference_test.o
+ $COMPILER -c -Wp,-MMD,other.d test.c -o reference_test.o
expect_equal_object_files reference_test.o test.o
rm -f other.d
$CCACHE_COMPILE -c -Wp,-MMD,other.d test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content other.d expected_mmd.d
expect_equal_object_files reference_test.o test.o
$CCACHE_COMPILE -c -Wp,-MMD,different_name.d test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content different_name.d expected_mmd.d
expect_equal_object_files reference_test.o test.o
+ # -------------------------------------------------------------------------
+ TEST "-Wp,-MMD with -o without -MMD/-MT/-MQ"
+
+ $COMPILER -c -Wp,-MMD,expected.d -o out.o "$(pwd)/test.c"
+
+ $CCACHE_COMPILE -c -Wp,-MMD,other.d -o out.o "$(pwd)/test.c"
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_equal_text_content other.d expected.d
+
+ rm -f other.d
+ $CCACHE_COMPILE -c -Wp,-MMD,other.d -o out.o "$(pwd)/test.c"
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_equal_text_content other.d expected.d
+
+ $CCACHE_COMPILE -c -Wp,-MMD,different_name.d -o out.o "$(pwd)/test.c"
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_equal_text_content different_name.d expected.d
+
# -------------------------------------------------------------------------
TEST "-Wp,-D"
$CCACHE_COMPILE -c -Wp,-DFOO test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c -DFOO test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "-Wp, with multiple arguments"
touch source.c
$CCACHE_COMPILE -c -Wp,-MMD,source.d,-MT,source.o source.c 2>/dev/null
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_content source.d "source.o: source.c"
$CCACHE_COMPILE -c -Wp,-MMD,source.d,-MT,source.o source.c 2>/dev/null
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
expect_content source.d "source.o: source.c"
# -------------------------------------------------------------------------
+if ! $COMPILER_USES_MSVC; then
TEST "-MMD for different source files"
mkdir a b
$CCACHE_COMPILE -MMD -c a/source.c
expect_content source.d "source.o: a/source.c"
-
+fi
# -------------------------------------------------------------------------
TEST "Multiple object entries in manifest"
$CCACHE_COMPILE -c test.c
$CCACHE_COMPILE -c test.c
done
- expect_stat 'cache hit (direct)' 5
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 5
+ expect_stat direct_cache_hit 5
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 5
# -------------------------------------------------------------------------
TEST "-MD"
$CCACHE_COMPILE -c -MD test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content test.d expected.d
- $REAL_COMPILER -c -MD test.c -o reference_test.o
+ $COMPILER -c -MD test.c -o reference_test.o
expect_equal_object_files reference_test.o test.o
rm -f test.d
$CCACHE_COMPILE -c -MD test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content test.d expected.d
expect_equal_object_files reference_test.o test.o
+ # -------------------------------------------------------------------------
+ TEST "-MD for assembler file with missing dependency file"
+
+ $COMPILER -S test.c
+
+ $CCACHE_COMPILE -c -MD test.s 2>/dev/null
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
+ $CCACHE_COMPILE -c -MD test.s 2>/dev/null
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
+ # -------------------------------------------------------------------------
+ TEST "-MD for assembler file with existing dependency file"
+
+ $COMPILER -S test.c
+ echo foo >test.d
+
+ $CCACHE_COMPILE -c -MD test.s 2>/dev/null
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ rm test.d
+
+ $CCACHE_COMPILE -c -MD test.s 2>/dev/null
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_missing test.d
+
# -------------------------------------------------------------------------
TEST "-ftest-coverage"
EOF
$CCACHE_COMPILE -c -fprofile-arcs -ftest-coverage code.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists code.gcno
rm code.gcno
$CCACHE_COMPILE -c -fprofile-arcs -ftest-coverage code.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists code.gcno
# -------------------------------------------------------------------------
int test() { return 0; }
EOF
- if $REAL_COMPILER -c -fstack-usage code.c >/dev/null 2>&1; then
+ if $COMPILER -c -fstack-usage code.c >/dev/null 2>&1; then
$CCACHE_COMPILE -c -fstack-usage code.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists code.su
rm code.su
$CCACHE_COMPILE -c -fstack-usage code.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists code.su
fi
TEST "Direct mode on cache created by ccache without direct mode support"
CCACHE_NODIRECT=1 $CCACHE_COMPILE -c -MD test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content test.d expected.d
- $REAL_COMPILER -c -MD test.c -o reference_test.o
+ $COMPILER -c -MD test.c -o reference_test.o
expect_equal_object_files reference_test.o test.o
rm -f test.d
CCACHE_NODIRECT=1 $CCACHE_COMPILE -c -MD test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
expect_equal_content test.d expected.d
expect_equal_object_files reference_test.o test.o
rm -f test.d
$CCACHE_COMPILE -c -MD test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
expect_equal_content test.d expected.d
expect_equal_object_files reference_test.o test.o
rm -f test.d
$CCACHE_COMPILE -c -MD test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
expect_equal_content test.d expected.d
expect_equal_object_files reference_test.o test.o
TEST "-MF"
$CCACHE_COMPILE -c -MD -MF other.d test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content other.d expected.d
- $REAL_COMPILER -c -MD -MF other.d test.c -o reference_test.o
+ $COMPILER -c -MD -MF other.d test.c -o reference_test.o
expect_equal_object_files reference_test.o test.o
rm -f other.d
$CCACHE_COMPILE -c -MD -MF other.d test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content other.d expected.d
expect_equal_object_files reference_test.o test.o
$CCACHE_COMPILE -c -MD -MF different_name.d test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content different_name.d expected.d
expect_equal_object_files reference_test.o test.o
rm -f different_name.d
$CCACHE_COMPILE -c -MD -MFthird_name.d test.c
- expect_stat 'cache hit (direct)' 3
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_equal_content third_name.d expected.d
expect_equal_object_files reference_test.o test.o
rm -f third_name.d
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST "MF /dev/null"
$CCACHE_COMPILE -c -MD -MF /dev/null test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2 # result + manifest
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # result + manifest
$CCACHE_COMPILE -c -MD -MF /dev/null test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
$CCACHE_COMPILE -c -MD -MF test.d test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
expect_equal_content test.d expected.d
rm -f test.d
$CCACHE_COMPILE -c -MD -MF test.d test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
expect_equal_content test.d expected.d
+fi
+ # -------------------------------------------------------------------------
+ TEST "Handling of -MT/-MQ"
+
+ echo '#include "test3.h"' >test.c
+
+ $CCACHE_COMPILE -MMD -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_content test.d "test.o: test.c test3.h"
+
+ $CCACHE_COMPILE -MMD -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_content test.d "test.o: test.c test3.h"
+
+ # -MQ
+
+ $CCACHE_COMPILE -MMD -MQ '$mq1' -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_content test.d '$$mq1: test.c test3.h'
+
+ $CCACHE_COMPILE -MMD -MQ '$mq2' -c test.c
+ expect_stat direct_cache_hit 2 # New dependency target but still a cache hit
+ expect_stat cache_miss 2
+ expect_content test.d '$$mq2: test.c test3.h'
+
+ # -MT
+
+ $CCACHE_COMPILE -MMD -MT '$mt1' -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 3
+ expect_content test.d '$mt1: test.c test3.h'
+
+ $CCACHE_COMPILE -MMD -MT '$mt2' -c test.c
+ expect_stat direct_cache_hit 3 # New dependency target but still a cache hit
+ expect_stat cache_miss 3
+ expect_content test.d '$mt2: test.c test3.h'
+
+ # -MQ -MT
+
+ $CCACHE_COMPILE -MMD -MQ '$mq1' -MT '$mt1' -c test.c
+ expect_stat direct_cache_hit 3
+ expect_stat cache_miss 4
+ content=$(<test.d)
+ expected_1='$$mq1 $mt1: test.c test3.h'
+ expected_2='$mt1 $$mq1: test.c test3.h'
+ if [[ $content != "${expected_1}" && $content != "${expected_2}" ]]; then
+ test_failed "Bad content of test.d\nExpected: ${expected_1} or ${expected_2}\nActual: ${content}"
+ fi
+
+ $CCACHE_COMPILE -MMD -MQ '$mq2' -MT '$mt2' -c test.c
+ expect_stat direct_cache_hit 4 # New dependency targets but still a cache hit
+ expect_stat cache_miss 4
+ content=$(<test.d)
+ expected_1='$$mq2 $mt2: test.c test3.h'
+ expected_2='$mt2 $$mq2: test.c test3.h'
+ if [[ $content != "${expected_1}" && $content != "${expected_2}" ]]; then
+ test_failed "Bad content of test.d\nExpected: ${expected_1} or ${expected_2}\nActual: ${content}"
+ fi
+
+ # -MQ -MT -Wp,-MMD,
+
+ $CCACHE_COMPILE -MMD -MQ '$mq1' -MT '$mt1' -Wp,-MMD,foo.d -c test.c
+ expect_stat direct_cache_hit 4
+ expect_stat cache_miss 5
+ content=$(<foo.d)
+ expected_1='$$mq1 $mt1: test.c test3.h'
+ expected_2='$mt1 $$mq1: test.c test3.h'
+ if [[ $content != "${expected_1}" && $content != "${expected_2}" ]]; then
+ test_failed "Bad content of foo.d\nExpected: ${expected_1} or ${expected_2}\nActual: ${content}"
+ fi
+
+ $CCACHE_COMPILE -MMD -MQ '$mq2' -MT '$mt2' -Wp,-MMD,foo.d -c test.c
+ expect_stat direct_cache_hit 5 # New dependency targets but still a cache hit
+ expect_stat cache_miss 5
+ content=$(<foo.d)
+ expected_1='$$mq2 $mt2: test.c test3.h'
+ expected_2='$mt2 $$mq2: test.c test3.h'
+ if [[ $content != "${expected_1}" && $content != "${expected_2}" ]]; then
+ test_failed "Bad content of test.d\nExpected: ${expected_1} or ${expected_2}\nActual: ${content}"
+ fi
# -------------------------------------------------------------------------
TEST "stderr from both preprocessor and compiler"
}
EOF
$CCACHE_COMPILE -Wall -W -c cpp-warning.c 2>stderr-orig.txt
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_NODIRECT=1 $CCACHE_COMPILE -Wall -W -c cpp-warning.c 2>stderr-cpp.txt
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
expect_content stderr-cpp.txt "`cat stderr-orig.txt`"
$CCACHE_COMPILE -Wall -W -c cpp-warning.c 2>stderr-mf.txt
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
expect_content stderr-mf.txt "`cat stderr-orig.txt`"
# -------------------------------------------------------------------------
touch empty.c
$CCACHE_COMPILE -c empty.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c empty.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Empty include file"
EOF
backdate empty.h
$CCACHE_COMPILE -c include_empty.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c include_empty.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "The source file path is included in the hash"
cp file.c file2.c
$CCACHE_COMPILE -c file.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c file.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c file2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -c file2.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -c $(pwd)/file.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 3
$CCACHE_COMPILE -c $(pwd)/file.c
- expect_stat 'cache hit (direct)' 3
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 3
# -------------------------------------------------------------------------
TEST "The source file path is included even if sloppiness = file_macro"
cp file.c file2.c
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE_COMPILE -c file.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE_COMPILE -c file.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE_COMPILE -c file2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE_COMPILE -c file2.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE_COMPILE -c $(pwd)/file.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 3
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS file_macro" $CCACHE_COMPILE -c $(pwd)/file.c
- expect_stat 'cache hit (direct)' 3
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 3
# -------------------------------------------------------------------------
TEST "Relative includes for identical source code in different directories"
backdate b/file.h
$CCACHE_COMPILE -c a/file.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c a/file.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c b/file.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -c b/file.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "__TIME__ in source file disables direct mode"
EOF
$CCACHE_COMPILE -c time.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c time.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "__TIME__ in include file disables direct mode"
EOF
$CCACHE_COMPILE -c time_h.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
+ $CCACHE_COMPILE -c time_h.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c time_h.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "__TIME__ in source file ignored if sloppy"
EOF
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE -c time.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE -c time.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "__TIME__ in include file ignored if sloppy"
#include "time.h"
EOF
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE -c time_h.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE -c time_h.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Too new include file disables direct mode"
touch -t 203801010000 new.h
$CCACHE_COMPILE -c new.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c new.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "__DATE__ in header file results in direct cache hit as the date remains the same"
backdate test_date2.c test_date2.h
$CCACHE_COMPILE -MP -MMD -MF test_date2.d -c test_date2.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -MP -MMD -MF test_date2.d -c test_date2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "New include file ignored if sloppy"
touch -t 203801010000 new.h
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS include_file_mtime" $CCACHE_COMPILE -c new.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS include_file_mtime" $CCACHE_COMPILE -c new.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Sloppy Clang index store"
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS clang_index_store" $CCACHE_COMPILE -index-store-path foo -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS clang_index_store" $CCACHE_COMPILE -index-store-path bar -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
# Check that environment variables that affect the preprocessor are taken
backdate subdir1/foo.h subdir2/foo.h
CPATH=subdir1 $CCACHE_COMPILE -c foo.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CPATH=subdir1 $CCACHE_COMPILE -c foo.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CPATH=subdir2 $CCACHE_COMPILE -c foo.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2 # subdir2 is part of the preprocessor output
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2 # subdir2 is part of the preprocessor output
CPATH=subdir2 $CCACHE_COMPILE -c foo.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Comment in strings"
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
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c comment.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo 'const char *comment = " /* \\\\u" "goo" " */";' >comment.c
$CCACHE_COMPILE -c comment.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "#line directives with troublesome files"
manifest=`find $CCACHE_DIR -name '*M'`
if [ -n "$manifest" ]; then
- data="`$CCACHE --dump-manifest $manifest | egrep '/dev/(stdout|tty|sda|hda'`"
+ data="`$CCACHE --inspect $manifest | egrep '/dev/(stdout|tty|sda|hda'`"
if [ -n "$data" ]; then
test_failed "$manifest contained troublesome file(s): $data"
fi
fi
# -------------------------------------------------------------------------
- TEST "--dump-manifest"
+ TEST "--inspect"
$CCACHE_COMPILE test.c -c -o test.o
manifest=`find $CCACHE_DIR -name '*M'`
- $CCACHE --dump-manifest $manifest >manifest.dump
+ $CCACHE --inspect $manifest >manifest.dump
checksum_test1_h='b7273h0ksdehi0o4pitg5jeehal3i54ns'
checksum_test2_h='24f1315jch5tcndjbm6uejtu8q3lf9100'
grep "Hash: $checksum_test3_h" manifest.dump >/dev/null 2>&1; then
: OK
else
- test_failed "Unexpected output of --dump-manifest"
+ test_failed "Unexpected output of --inspect"
fi
# -------------------------------------------------------------------------
EOF
$CCACHE_COMPILE -B -L -DFOO -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -B -L -DBAR -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "CCACHE_IGNOREHEADERS with filename"
CCACHE_IGNOREHEADERS="subdir/ignore.h" $CCACHE_COMPILE -c ignore.c
manifest=`find $CCACHE_DIR -name '*M'`
- data="`$CCACHE --dump-manifest $manifest | grep subdir/ignore.h`"
+ data="`$CCACHE --inspect $manifest | grep subdir/ignore.h`"
if [ -n "$data" ]; then
test_failed "$manifest contained ignored header: $data"
fi
CCACHE_IGNOREHEADERS="subdir" $CCACHE_COMPILE -c ignore.c
manifest=`find $CCACHE_DIR -name '*M'`
- data="`$CCACHE --dump-manifest $manifest | grep subdir/ignore.h`"
+ data="`$CCACHE --inspect $manifest | grep subdir/ignore.h`"
if [ -n "$data" ]; then
test_failed "$manifest contained ignored header: $data"
fi
TEST "CCACHE_IGNOREOPTIONS"
CCACHE_IGNOREOPTIONS="-DTEST=1" $CCACHE_COMPILE -DTEST=1 -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_IGNOREOPTIONS="-DTEST=1*" $CCACHE_COMPILE -DTEST=1 -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_IGNOREOPTIONS="-DTEST=1*" $CCACHE_COMPILE -DTEST=12 -c test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_IGNOREOPTIONS="-DTEST=2*" $CCACHE_COMPILE -DTEST=12 -c test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 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
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat recache 0
+ 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_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat recache 1
+ expect_stat files_in_cache 2
expect_equal_content $manifest_file saved.manifest
}
+++ /dev/null
-SUITE_direct_gcc_PROBE() {
- if [[ $REAL_COMPILER != *"gcc"* ]]; then
- echo "Skipping GCC only test cases"
- fi
-}
-
-SUITE_direct_gcc_SETUP() {
- unset CCACHE_NODIRECT
-
- cat <<EOF >test.c
-// test.c
-#include "test1.h"
-#include "test2.h"
-EOF
- cat <<EOF >test1.h
-#include "test3.h"
-int test1;
-EOF
- cat <<EOF >test2.h
-int test2;
-EOF
- cat <<EOF >test3.h
-int test3;
-EOF
- backdate test1.h test2.h test3.h
-
- DEPENDENCIES_OUTPUT="expected_dependencies_output.d" $REAL_COMPILER -c test.c
- DEPENDENCIES_OUTPUT="expected_dependencies_output_target.d target.o" $REAL_COMPILER -c test.c
- SUNPRO_DEPENDENCIES="expected_sunpro_dependencies.d" $REAL_COMPILER -c test.c
- SUNPRO_DEPENDENCIES="expected_sunpro_dependencies_target.d target.o" $REAL_COMPILER -c test.c
- rm test.o
-}
-
-SUITE_direct_gcc() {
- # -------------------------------------------------------------------------
- TEST "DEPENDENCIES_OUTPUT environment variable"
-
- DEPENDENCIES_OUTPUT="other.d" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content other.d expected_dependencies_output.d
-
- DEPENDENCIES_OUTPUT="other.d" $REAL_COMPILER -c test.c -o reference_test.o
- expect_equal_object_files reference_test.o test.o
-
- rm -f other.d
- DEPENDENCIES_OUTPUT="other.d" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content other.d expected_dependencies_output.d
- expect_equal_object_files reference_test.o test.o
-
- DEPENDENCIES_OUTPUT="different_name.d" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content different_name.d expected_dependencies_output.d
- expect_equal_object_files reference_test.o test.o
-
- # -------------------------------------------------------------------------
- TEST "DEPENDENCIES_OUTPUT environment variable with target"
-
- DEPENDENCIES_OUTPUT="other.d target.o" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content other.d expected_dependencies_output_target.d
-
- DEPENDENCIES_OUTPUT="other.d target.o" $REAL_COMPILER -c test.c -o reference_test.o
- expect_equal_object_files reference_test.o test.o
-
- rm -f other.d
- DEPENDENCIES_OUTPUT="other.d target.o" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content other.d expected_dependencies_output_target.d
- expect_equal_object_files reference_test.o test.o
-
- DEPENDENCIES_OUTPUT="different_name.d target.o" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content different_name.d expected_dependencies_output_target.d
- expect_equal_object_files reference_test.o test.o
-
- # -------------------------------------------------------------------------
- TEST "SUNPRO_DEPENDENCIES environment variable"
-
- SUNPRO_DEPENDENCIES="other.d" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content other.d expected_sunpro_dependencies.d
-
- SUNPRO_DEPENDENCIES="other.d" $REAL_COMPILER -c test.c -o reference_test.o
- expect_equal_object_files reference_test.o test.o
-
- rm -f other.d
- SUNPRO_DEPENDENCIES="other.d" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content other.d expected_sunpro_dependencies.d
- expect_equal_object_files reference_test.o test.o
-
- SUNPRO_DEPENDENCIES="different_name.d" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content different_name.d expected_sunpro_dependencies.d
- expect_equal_object_files reference_test.o test.o
-
- # -------------------------------------------------------------------------
- TEST "SUNPRO_DEPENDENCIES environment variable with target"
-
- SUNPRO_DEPENDENCIES="other.d target.o" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content other.d expected_sunpro_dependencies_target.d
-
- SUNPRO_DEPENDENCIES="other.d target.o" $REAL_COMPILER -c test.c -o reference_test.o
- expect_equal_object_files reference_test.o test.o
-
- rm -f other.d
- SUNPRO_DEPENDENCIES="other.d target.o" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content other.d expected_sunpro_dependencies_target.d
- expect_equal_object_files reference_test.o test.o
-
- SUNPRO_DEPENDENCIES="different_name.d target.o" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_equal_content different_name.d expected_sunpro_dependencies_target.d
- expect_equal_object_files reference_test.o test.o
-
- # -------------------------------------------------------------------------
- TEST "DEPENDENCIES_OUTPUT environment variable set to /dev/null"
-
- DEPENDENCIES_OUTPUT="/dev/null" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
-
- DEPENDENCIES_OUTPUT="other.d" $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
-}
generate_code 1 test.c
- $REAL_COMPILER -c -o reference_test.o test.c
+ $COMPILER -c -o reference_test.o test.c
CCACHE_FILECLONE=1 $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_object_files reference_test.o test.o
# Note: CCACHE_DEBUG=1 below is needed for the test case.
CCACHE_FILECLONE=1 CCACHE_DEBUG=1 $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_object_files reference_test.o test.o
- if ! grep -q 'Cloning.*to test.o' test.o.ccache-log; then
+ if ! grep -q 'Cloning.*to test.o' test.o.*.ccache-log; then
test_failed "Did not try to clone file"
fi
- if grep -q 'Failed to clone' test.o.ccache-log; then
+ if grep -q 'Failed to clone' test.o.*.ccache-log; then
test_failed "Failed to clone"
fi
generate_code 1 test.c
- $REAL_COMPILER -c -o reference_test.o test.c
+ $COMPILER -c -o reference_test.o test.c
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test.o test.o
# Note: CCACHE_DEBUG=1 below is needed for the test case.
CCACHE_FILECLONE=1 CCACHE_DEBUG=1 $CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test.o test.o
- if grep -q 'Cloning' test.o.ccache-log; then
+ if grep -q 'Cloning' test.o.*.ccache-log; then
test_failed "Tried to clone"
fi
}
generate_code 1 test1.c
- $REAL_COMPILER -c -o reference_test1.o test1.c
+ $COMPILER -c -o reference_test1.o test1.c
CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_object_files reference_test1.o test1.o
mv test1.o test1.o.saved
CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
if [ ! test1.o -ef test1.o.saved ]; then
test_failed "Object files not hard linked"
fi
+ $CCACHE_COMPILE -c test1.c
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+
+ if [ test1.o -ef test1.o.saved ]; then
+ test_failed "Object files are hard linked"
+ fi
+
# -------------------------------------------------------------------------
TEST "Corrupted file size is detected"
generate_code 1 test1.c
CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
mv test1.o test1.o.saved
CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST "Overwrite assembler"
generate_code 1 test1.c
- $REAL_COMPILER -S -o test1.s test1.c
+ $COMPILER -S -o test1.s test1.c
- $REAL_COMPILER -c -o reference_test1.o test1.s
+ $COMPILER -c -o reference_test1.o test1.s
CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.s
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
generate_code 2 test1.c
- $REAL_COMPILER -S -o test1.s test1.c
+ $COMPILER -S -o test1.s test1.c
CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.s
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
generate_code 1 test1.c
- $REAL_COMPILER -S -o test1.s test1.c
+ $COMPILER -S -o test1.s test1.c
CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test1.s
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
expect_equal_object_files reference_test1.o test1.o
-
+fi
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST "Automake depend move"
unset CCACHE_NODIRECT
generate_code 1 test1.c
CCACHE_HARDLINK=1 CCACHE_DEPEND=1 $CCACHE_COMPILE -c -MMD -MF test1.d.tmp test1.c
- expect_stat 'cache hit (direct)' 0
+ expect_stat direct_cache_hit 0
mv test1.d.tmp test1.d || test_failed "first mv failed"
CCACHE_HARDLINK=1 CCACHE_DEPEND=1 $CCACHE_COMPILE -c -MMD -MF test1.d.tmp test1.c
- expect_stat 'cache hit (direct)' 1
+ expect_stat direct_cache_hit 1
mv test1.d.tmp test1.d || test_failed "second mv failed"
-
+fi
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST ".d file corrupted by compiler"
unset CCACHE_NODIRECT
echo "int x;" >test1.c
$CCACHE_COMPILE -c -MMD test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
expect_content test1.d "test1.o: test1.c"
touch test1.h
echo '#include "test1.h"' >>test1.c
$CCACHE_COMPILE -c -MMD test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
expect_content test1.d "test1.o: test1.c test1.h"
echo "int x;" >test1.c
$CCACHE_COMPILE -c -MMD test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
expect_content test1.d "test1.o: test1.c"
+fi
}
return
fi
- temp_dir=$(dirname $($CCACHE -k temporary_dir))
- fs=$(stat -fLc %T $temp_dir)
- if [ "$fs" = "nfs" ]; then
- echo "ccache temporary directory is on NFS"
+ unset CCACHE_NODIRECT
+ export CCACHE_TEMPDIR="${CCACHE_DIR}/tmp"
+
+ touch test.c
+ $CCACHE $COMPILER -c test.c
+ if [[ ! -f "${CCACHE_TEMPDIR}/inode-cache-32.v1" && ! -f "${CCACHE_TEMPDIR}/inode-cache-64.v1" ]]; then
+ local fs_type=$(stat -fLc %T "${CCACHE_DIR}")
+ echo "inode cache not supported on ${fs_type}"
fi
}
SUITE_inode_cache_SETUP() {
- export CCACHE_INODECACHE=1
export CCACHE_DEBUG=1
unset CCACHE_NODIRECT
+ export CCACHE_TEMPDIR="${CCACHE_DIR}/tmp" # isolate inode cache file
+
+ # Disable safety guard against race condition in InodeCache. This is OK
+ # since files used in the tests have different sizes and thus will have
+ # different cache keys even if ctime/mtime are not updated quickly enough.
+ export CCACHE_DISABLE_INODE_CACHE_MIN_AGE=1
}
SUITE_inode_cache() {
local source_file=$2
local type=$3
- local log_file=$(echo $source_file | sed 's/\.c$/.o.ccache-log/')
- local actual=$(grep -c "inode cache $type: $source_file" "$log_file")
+ local actual=$(grep -c "Inode cache $type: $source_file" ${source_file/%.c/.o}.*.ccache-log)
if [ $actual -ne $expected ]; then
- test_failed "Found $actual (expected $expected) $type for $source_file"
+ test_failed_internal "Found $actual (expected $expected) $type for $source_file"
fi
}
echo "// recompile" > test1.c
$CCACHE_COMPILE -c test1.c
expect_inode_cache 0 1 1 test1.c
+ rm *.ccache-*
+
$CCACHE_COMPILE -c test1.c
expect_inode_cache 1 0 0 test1.c
echo "// backdate" > test1.c
$CCACHE_COMPILE -c test1.c
expect_inode_cache 0 1 1 test1.c
+ rm *.ccache-*
backdate test1.c
$CCACHE_COMPILE -c test1.c
expect_inode_cache 1 0 0 test2.c
# -------------------------------------------------------------------------
- TEST "Soft link"
+ TEST "Symbolic link"
- echo "// soft linked" > test1.c
+ echo "// symbolically linked" > test1.c
ln -fs test1.c test2.c
$CCACHE_COMPILE -c test1.c
$CCACHE_COMPILE -c test2.c
echo "// replace" > test1.c
$CCACHE_COMPILE -c test1.c
expect_inode_cache 0 1 1 test1.c
+ rm *.ccache-*
rm test1.c
echo "// replace" > test1.c
SUITE_input_charset_PROBE() {
touch test.c
- if ! $REAL_COMPILER -c -finput-charset=latin1 test.c >/dev/null 2>&1; then
+ if ! $COMPILER -c -finput-charset=latin1 test.c >/dev/null 2>&1; then
echo "compiler doesn't support -finput-charset"
fi
}
printf '#include <wchar.h>\nwchar_t foo[] = L"\xbf";\n' >latin1.c
$CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
CCACHE_NOCPP2=1 $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
CCACHE_NOCPP2=1 $CCACHE_COMPILE -c -finput-charset=latin1 latin1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
}
TEST "without sloppy ivfsoverlay"
$CCACHE_COMPILE -ivfsoverlay test.yaml -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 0
- expect_stat 'unsupported compiler option' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat unsupported_compiler_option 1
# -------------------------------------------------------------------------
TEST "with sloppy ivfsoverlay"
- CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS ivfsoverlay" $CCACHE_COMPILE -ivfsoverlay test.yaml -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS include_file_mtime ivfsoverlay" $CCACHE_COMPILE -ivfsoverlay test.yaml -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
- CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS ivfsoverlay" $CCACHE_COMPILE -ivfsoverlay test.yaml -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
+ CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS include_file_mtime ivfsoverlay" $CCACHE_COMPILE -ivfsoverlay test.yaml -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
}
}
SUITE_masquerading_SETUP() {
- ln -s "$CCACHE" $COMPILER_BIN
generate_code 1 test1.c
}
SUITE_masquerading() {
+if ! $HOST_OS_WINDOWS && ! $HOST_OS_CYGWIN; then
# -------------------------------------------------------------------------
TEST "Masquerading via symlink, relative path"
- $REAL_COMPILER -c -o reference_test1.o test1.c
+ ln -s "$CCACHE" $COMPILER_BIN
+ $COMPILER -c -o reference_test1.o test1.c
- ./$COMPILER_BIN $COMPILER_ARGS -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ PATH="${PWD}:${PATH}" ./$COMPILER_BIN $COMPILER_ARGS -c test1.c
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
- ./$COMPILER_BIN $COMPILER_ARGS -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ PATH="${PWD}:${PATH}" ./$COMPILER_BIN $COMPILER_ARGS -c test1.c
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
# -------------------------------------------------------------------------
TEST "Masquerading via symlink, absolute path"
- $REAL_COMPILER -c -o reference_test1.o test1.c
+ ln -s "$CCACHE" $COMPILER_BIN
+ $COMPILER -c -o reference_test1.o test1.c
+
+ PATH="${PWD}:${PATH}" $PWD/$COMPILER_BIN $COMPILER_ARGS -c test1.c
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_equal_object_files reference_test1.o test1.o
+
+ PATH="${PWD}:${PATH}" $PWD/$COMPILER_BIN $COMPILER_ARGS -c test1.c
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_equal_object_files reference_test1.o test1.o
+fi
+
+ # -------------------------------------------------------------------------
+ TEST "Masquerading via copy or hard link"
+
+ cp "$CCACHE" $COMPILER_BIN
+ $COMPILER -c -o reference_test1.o test1.c
- $PWD/$COMPILER_BIN $COMPILER_ARGS -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ PATH="${PWD}:${PATH}" ./$COMPILER_BIN $COMPILER_ARGS -c test1.c
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
- $PWD/$COMPILER_BIN $COMPILER_ARGS -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ PATH="${PWD}:${PATH}" ./$COMPILER_BIN $COMPILER_ARGS -c test1.c
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_object_files reference_test1.o test1.o
}
SUITE_modules_PROBE() {
- if ! $COMPILER_TYPE_CLANG; then
+ if ! $COMPILER_TYPE_CLANG || $COMPILER_USES_MSVC; then
echo "-fmodules/-fcxx-modules not supported by compiler"
else
- touch test.c
- $COMPILER -fmodules test.c -S || echo "compiler does not support modules"
+ echo '#include <string>' >testmodules.cpp
+ $COMPILER -x c++ -fmodules testmodules.cpp -S || echo "compiler does not support modules"
fi
}
# -------------------------------------------------------------------------
TEST "fall back to real compiler, no sloppiness"
- $CCACHE_COMPILE -fmodules -fcxx-modules -c test1.cpp -MD
- expect_stat "can't use modules" 1
+ $CCACHE_COMPILE -x c++ -fmodules -fcxx-modules -c test1.cpp -MD
+ expect_stat could_not_use_modules 1
- $CCACHE_COMPILE -fmodules -fcxx-modules -c test1.cpp -MD
- expect_stat "can't use modules" 2
+ $CCACHE_COMPILE -x c++ -fmodules -fcxx-modules -c test1.cpp -MD
+ expect_stat could_not_use_modules 2
# -------------------------------------------------------------------------
TEST "fall back to real compiler, no depend mode"
unset CCACHE_DEPEND
- CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -fmodules -fcxx-modules -c test1.cpp -MD
- expect_stat "can't use modules" 1
+ CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -x c++ -fmodules -fcxx-modules -c test1.cpp -MD
+ expect_stat could_not_use_modules 1
- CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -fmodules -fcxx-modules -c test1.cpp -MD
- expect_stat "can't use modules" 2
+ CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -x c++ -fmodules -fcxx-modules -c test1.cpp -MD
+ expect_stat could_not_use_modules 2
# -------------------------------------------------------------------------
TEST "cache hit"
- CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -fmodules -fcxx-modules -c test1.cpp -MD
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -x c++ -fmodules -fcxx-modules -c test1.cpp -MD
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
- CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -fmodules -fcxx-modules -c test1.cpp -MD
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
+ CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -x c++ -fmodules -fcxx-modules -c test1.cpp -MD
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "cache miss"
- CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -MD -fmodules -fcxx-modules -c test1.cpp -MD
- expect_stat 'cache miss' 1
+ CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -MD -x c++ -fmodules -fcxx-modules -c test1.cpp -MD
+ expect_stat cache_miss 1
cat <<EOF >test1.h
#include <string>
EOF
backdate test1.h
- CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -MD -fmodules -fcxx-modules -c test1.cpp -MD
- expect_stat 'cache miss' 2
+ CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -MD -x c++ -fmodules -fcxx-modules -c test1.cpp -MD
+ expect_stat cache_miss 2
echo >>module.modulemap
backdate test1.h
- CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -MD -fmodules -fcxx-modules -c test1.cpp -MD
- expect_stat 'cache miss' 3
+ CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS modules" $CCACHE_COMPILE -MD -x c++ -fmodules -fcxx-modules -c test1.cpp -MD
+ expect_stat cache_miss 3
}
# Different arches shouldn't affect each other
$CCACHE_COMPILE -arch i386 -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -arch x86_64 -c test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -arch i386 -c test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
# Multiple arches should be cached too
$CCACHE_COMPILE -arch i386 -arch x86_64 -c test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
$CCACHE_COMPILE -arch i386 -arch x86_64 -c test1.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 3
+
+ # A single -Xarch_* matching -arch is supported.
+ CCACHE_DEBUG=1 $CCACHE_COMPILE -arch x86_64 -Xarch_x86_64 -I. -c test1.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 4
+ expect_contains test1.o.*.ccache-log "clang -Xarch_x86_64 -I. -arch x86_64 -E"
+ expect_not_contains test1.o.*.ccache-log "clang -Xarch_x86_64 -I. -I. -arch x86_64 -E"
+
+ $CCACHE_COMPILE -arch x86_64 -Xarch_x86_64 -I. -c test1.c
+ expect_stat direct_cache_hit 3
+ expect_stat cache_miss 4
+
+ # The parameter following -Xarch should be processed.
+ $CCACHE_COMPILE -arch x86_64 -Xarch_x86_64 -analyze -I. -c test1.c
+ expect_stat unsupported_compiler_option 1
# -------------------------------------------------------------------------
TEST "cache hit, preprocessor mode"
export CCACHE_NODIRECT=1
$CCACHE_COMPILE -arch i386 -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -arch x86_64 -c test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -arch i386 -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
# Multiple arches should be cached too
$CCACHE_COMPILE -arch i386 -arch x86_64 -c test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 3
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 3
$CCACHE_COMPILE -arch i386 -arch x86_64 -c test1.c
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 3
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 3
}
--- /dev/null
+SUITE_namespace_SETUP() {
+ unset CCACHE_NODIRECT
+ echo 'int x;' >test1.c
+ echo 'int y;' >test2.c
+}
+
+SUITE_namespace() {
+ # -------------------------------------------------------------------------
+ TEST "Namespace makes entries isolated"
+
+ $CCACHE_COMPILE -c test1.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+
+ $CCACHE_COMPILE -c test1.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+
+ CCACHE_NAMESPACE=a $CCACHE_COMPILE -c test1.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+
+ CCACHE_NAMESPACE=a $CCACHE_COMPILE -c test1.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+
+ CCACHE_NAMESPACE=b $CCACHE_COMPILE -c test1.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 3
+
+ CCACHE_NAMESPACE=b $CCACHE_COMPILE -c test1.c
+ expect_stat direct_cache_hit 3
+ expect_stat cache_miss 3
+
+ # -------------------------------------------------------------------------
+ TEST "--evict-namespace + --evict-older-than"
+
+ CCACHE_NAMESPACE="a" $CCACHE_COMPILE -c test1.c
+ result_file="$(find $CCACHE_DIR -name '*R')"
+ backdate "$result_file"
+ for ns in a b c; do
+ CCACHE_NAMESPACE="$ns" $CCACHE_COMPILE -c test2.c
+ done
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 8
+
+ $CCACHE --evict-namespace d >/dev/null
+ expect_stat files_in_cache 8
+
+ $CCACHE --evict-namespace c >/dev/null
+ expect_stat files_in_cache 6
+
+ $CCACHE --evict-namespace a --evict-older-than 1d >/dev/null
+ expect_stat files_in_cache 5
+
+ $CCACHE --evict-namespace a >/dev/null
+ expect_stat files_in_cache 2
+}
# -------------------------------------------------------------------------
TEST "Base case"
- $REAL_COMPILER -c -o reference_test.o test.c
+ $COMPILER -c -o reference_test.o test.c
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_object_files reference_test.o test.o
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_object_files reference_test.o test.o
# -------------------------------------------------------------------------
$CCACHE_COMPILE -c test.c
result_file=$(find $CCACHE_DIR -name '*R')
- if ! $CCACHE --dump-result $result_file | grep 'Compression type: none' >/dev/null 2>&1; then
+ if ! $CCACHE --inspect $result_file | grep 'Compression type: none' >/dev/null 2>&1; then
test_failed "Result file not uncompressed according to metadata"
fi
if [ $(file_size $result_file) -le $(file_size test.o) ]; then
TEST "Hash sum equal for compressed and uncompressed files"
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
unset CCACHE_NOCOMPRESS
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Corrupt result file"
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
result_file=$(find $CCACHE_DIR -name '*R')
- printf foo | dd of=$result_file bs=3 count=1 seek=20 conv=notrunc >&/dev/null
+ # Write BAD at byte 300.
+ printf BAD | dd of=$result_file bs=3 count=1 seek=100 conv=notrunc >&/dev/null
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
}
+SUITE_nocpp2_PROBE() {
+ if $HOST_OS_WINDOWS; then
+ echo "CCACHE_NOCPP2 does not work correct on Windows"
+ return
+ fi
+}
+
SUITE_nocpp2_SETUP() {
export CCACHE_NOCPP2=1
generate_code 1 test1.c
nvcc_PROBE() {
if [ -z "$REAL_NVCC" ]; then
echo "nvcc is not available"
- elif [ -z "$REAL_CUOBJDUMP" ]; then
+ elif ! command -v cuobjdump >/dev/null; then
echo "cuobjdump is not available"
fi
}
nvcc_opts_gpu2="--generate-code arch=compute_52,code=sm_52"
ccache_nvcc_cpp="$CCACHE $REAL_NVCC $nvcc_opts_cpp"
ccache_nvcc_cuda="$CCACHE $REAL_NVCC $nvcc_opts_cuda"
- cuobjdump="$REAL_CUOBJDUMP -all -elf -symbols -ptx -sass"
+ cuobjdump="cuobjdump -all -elf -symbols -ptx -sass"
# -------------------------------------------------------------------------
TEST "Simple mode"
# First compile.
$ccache_nvcc_cpp test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
$ccache_nvcc_cpp test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
# -------------------------------------------------------------------------
expect_different_content reference_test2.dump reference_test3.dump
$ccache_nvcc_cuda test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test1.dump test1.dump
# Other GPU.
$ccache_nvcc_cuda $nvcc_opts_gpu1 test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test2.dump test1.dump
$ccache_nvcc_cuda $nvcc_opts_gpu1 test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test2.dump test1.dump
# Another GPU.
$ccache_nvcc_cuda $nvcc_opts_gpu2 test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 3
- expect_stat 'files in cache' 3
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 3
+ expect_stat files_in_cache 3
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test3.dump test1.dump
$ccache_nvcc_cuda $nvcc_opts_gpu2 test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 3
- expect_stat 'files in cache' 3
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 3
+ expect_stat files_in_cache 3
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test3.dump test1.dump
$cuobjdump reference_test4.o > reference_test4.dump
$ccache_nvcc_cuda -dc -o test_cuda.o test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$cuobjdump test_cuda.o > test4.dump
expect_equal_content test4.dump reference_test4.dump
$ccache_nvcc_cuda -dc -o test_cuda.o test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$cuobjdump test_cuda.o > test4.dump
expect_equal_content test4.dump reference_test4.dump
expect_different_content reference_test1.o reference_test2.o
$ccache_nvcc_cpp test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
# Specified define, but unused. Can only be found by preprocessed mode.
$ccache_nvcc_cpp -DDUMMYENV=1 test_cpp.cu
- expect_stat "cache hit (preprocessed)" 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
# Specified used define.
$ccache_nvcc_cpp -DNUM=10 test_cpp.cu
- expect_stat "cache hit (preprocessed)" 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
expect_equal_content reference_test2.o test_cpp.o
$ccache_nvcc_cpp -DNUM=10 test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
expect_equal_content reference_test2.o test_cpp.o
# -------------------------------------------------------------------------
expect_different_content reference_test1.o reference_test2.o
$ccache_nvcc_cpp -optf test1.optf test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
$ccache_nvcc_cpp -optf test1.optf test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
$ccache_nvcc_cpp -optf test2.optf test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
expect_equal_content reference_test2.o test_cpp.o
$ccache_nvcc_cpp -optf test2.optf test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 2
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
expect_equal_content reference_test2.o test_cpp.o
# -------------------------------------------------------------------------
TEST "Option --compiler-bindir"
- $REAL_NVCC $nvcc_opts_cpp --compiler-bindir $REAL_COMPILER_BIN \
+ $REAL_NVCC $nvcc_opts_cpp --compiler-bindir $COMPILER_BIN \
-o reference_test1.o test_cpp.cu
# First compile.
- $ccache_nvcc_cpp --compiler-bindir $REAL_COMPILER_BIN test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ $ccache_nvcc_cpp --compiler-bindir $COMPILER_BIN test_cpp.cu
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
- $ccache_nvcc_cpp --compiler-bindir $REAL_COMPILER_BIN test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ $ccache_nvcc_cpp --compiler-bindir $COMPILER_BIN test_cpp.cu
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
# -------------------------------------------------------------------------
TEST "Option -ccbin"
- $REAL_NVCC $nvcc_opts_cpp -ccbin $REAL_COMPILER_BIN \
+ $REAL_NVCC $nvcc_opts_cpp -ccbin $COMPILER_BIN \
-o reference_test1.o test_cpp.cu
# First compile.
- $ccache_nvcc_cpp -ccbin $REAL_COMPILER_BIN test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ $ccache_nvcc_cpp -ccbin $COMPILER_BIN test_cpp.cu
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
- $ccache_nvcc_cpp -ccbin $REAL_COMPILER_BIN test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ $ccache_nvcc_cpp -ccbin $COMPILER_BIN test_cpp.cu
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
# -------------------------------------------------------------------------
# First compile.
$ccache_nvcc_cpp --output-directory . test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
$ccache_nvcc_cpp --output-directory . test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
# -------------------------------------------------------------------------
# First compile.
$ccache_nvcc_cpp -odir . test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
$ccache_nvcc_cpp -odir . test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content reference_test1.o test_cpp.o
# -------------------------------------------------------------------------
TEST "Option -Werror"
$ccache_nvcc_cpp -Werror cross-execution-space-call test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$ccache_nvcc_cpp -Werror cross-execution-space-call test_cpp.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
}
SUITE_nvcc_PROBE() {
nvcc_opts_gpu2="--generate-code arch=compute_52,code=sm_52"
ccache_nvcc_cpp="$CCACHE $REAL_NVCC $nvcc_opts_cpp"
ccache_nvcc_cuda="$CCACHE $REAL_NVCC $nvcc_opts_cuda"
- cuobjdump="$REAL_CUOBJDUMP -all -elf -symbols -ptx -sass"
+ cuobjdump="cuobjdump -all -elf -symbols -ptx -sass"
# -------------------------------------------------------------------------
TEST "Simple mode"
# First compile.
$ccache_nvcc_cpp test_cpp.cu
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_content reference_test1.o test_cpp.o
$ccache_nvcc_cpp test_cpp.cu
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_content reference_test1.o test_cpp.o
# -------------------------------------------------------------------------
expect_different_content reference_test2.dump reference_test3.dump
$ccache_nvcc_cuda test_cuda.cu
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test1.dump test1.dump
# Other GPU.
$ccache_nvcc_cuda $nvcc_opts_gpu1 test_cuda.cu
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test2.dump test1.dump
$ccache_nvcc_cuda $nvcc_opts_gpu1 test_cuda.cu
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test2.dump test1.dump
# Another GPU.
$ccache_nvcc_cuda $nvcc_opts_gpu2 test_cuda.cu
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
- expect_stat 'files in cache' 6
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
+ expect_stat files_in_cache 6
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test3.dump test1.dump
$ccache_nvcc_cuda $nvcc_opts_gpu2 test_cuda.cu
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 3
- expect_stat 'files in cache' 6
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 3
+ expect_stat files_in_cache 6
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test3.dump test1.dump
expect_different_content reference_test1.o reference_test2.o
$ccache_nvcc_cpp test_cpp.cu
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_content reference_test1.o test_cpp.o
# Specified define, but unused. Can only be found by preprocessed mode.
$ccache_nvcc_cpp -DDUMMYENV=1 test_cpp.cu
- expect_stat "cache hit (preprocessed)" 1
- expect_stat "cache hit (direct)" 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 3
+ expect_stat preprocessed_cache_hit 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 3
expect_equal_content reference_test1.o test_cpp.o
# Specified used define.
$ccache_nvcc_cpp -DNUM=10 test_cpp.cu
- expect_stat "cache hit (direct)" 0
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 5
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 5
expect_equal_content reference_test2.o test_cpp.o
$ccache_nvcc_cpp -DNUM=10 test_cpp.cu
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 5
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 5
expect_equal_content reference_test2.o test_cpp.o
# -------------------------------------------------------------------------
expect_different_content reference_test1.o reference_test2.o
$ccache_nvcc_cpp -optf test1.optf test_cpp.cu
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_content reference_test1.o test_cpp.o
$ccache_nvcc_cpp -optf test1.optf test_cpp.cu
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
expect_equal_content reference_test1.o test_cpp.o
$ccache_nvcc_cpp -optf test2.optf test_cpp.cu
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
expect_equal_content reference_test2.o test_cpp.o
$ccache_nvcc_cpp -optf test2.optf test_cpp.cu
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
expect_equal_content reference_test2.o test_cpp.o
}
if [ -z "$REAL_NVCC" ]; then
echo "nvcc is not available"
return
- elif [ -z "$REAL_CUOBJDUMP" ]; then
+ elif ! command -v cuobjdump >/dev/null; then
echo "cuobjdump is not available"
return
fi
elif [ ! -d $nvcc_idir ]; then
echo "include directory $nvcc_idir not found"
fi
+
+ echo "int main() { return 0; }" | $REAL_NVCC -Wno-deprecated-gpu-targets -ccbin $COMPILER_BIN -c -x cu -
+ if [ $? -ne 0 ]; then
+ echo "nvcc of a canary failed; Is CUDA compatible with the host compiler ($COMPILER_BIN)?"
+ fi
}
SUITE_nvcc_ldir_SETUP() {
}
SUITE_nvcc_ldir() {
- nvcc_opts_cuda="-Wno-deprecated-gpu-targets -c -ccbin $REAL_COMPILER_BIN"
+ nvcc_opts_cuda="-Wno-deprecated-gpu-targets -c -ccbin $COMPILER_BIN"
ccache_nvcc_cuda="$CCACHE $REAL_NVCC $nvcc_opts_cuda"
- cuobjdump="$REAL_CUOBJDUMP -all -elf -symbols -ptx -sass"
+ cuobjdump="cuobjdump -all -elf -symbols -ptx -sass"
nvcc_dir=$(dirname $REAL_NVCC)
nvcc_ldir=$nvcc_dir/../nvvm/libdevice
cicc_path=$nvcc_dir/../nvvm/bin
# First compile.
$ccache_nvcc_cuda $TEST_OPTS test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test1.dump test1.dump
$ccache_nvcc_cuda $TEST_OPTS test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test1.dump test1.dump
# First compile.
$ccache_nvcc_cuda $TEST_OPTS test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test1.dump test1.dump
$ccache_nvcc_cuda $TEST_OPTS test_cuda.cu
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
$cuobjdump test_cuda.o > test1.dump
expect_equal_content reference_test1.dump test1.dump
touch pch.h empty.c
mkdir dir
- if ! $REAL_COMPILER $SYSROOT -fpch-preprocess pch.h 2>/dev/null \
+ if ! $COMPILER $SYSROOT -fpch-preprocess pch.h 2>/dev/null \
|| [ ! -f pch.h.gch ]; then
echo "compiler ($($COMPILER --version | head -n 1)) doesn't support precompiled headers"
fi
- $REAL_COMPILER $SYSROOT -c pch.h -o dir/pch.h.gch 2>/dev/null
- if ! $REAL_COMPILER $SYSROOT -c -include dir/pch.h empty.c 2>/dev/null; then
+ $COMPILER $SYSROOT -c pch.h -o dir/pch.h.gch 2>/dev/null
+ if ! $COMPILER $SYSROOT -c -include dir/pch.h empty.c 2>/dev/null; then
echo "compiler ($($COMPILER --version | head -n 1)) seems to have broken support for precompiled headers"
fi
}
pch_suite_common
if $COMPILER_TYPE_CLANG; then
- pch_suite_clang
+ if ! [ $COMPILER_BIN = "gcc" ] || $RUN_WIN_XFAIL; then
+ pch_suite_clang
+ fi
else
pch_suite_gcc
fi
TEST "Create .gch, -c, no -o, without opt-in"
$CCACHE_COMPILE $SYSROOT -c pch.h
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat "can't use precompiled header" 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat could_not_use_precompiled_header 1
# -------------------------------------------------------------------------
TEST "Create .gch, no -c, -o, without opt-in"
$CCACHE_COMPILE pch.h -o pch.gch
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat "can't use precompiled header" 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat could_not_use_precompiled_header 1
# -------------------------------------------------------------------------
TEST "Create .gch, -c, no -o, with opt-in"
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -c pch.h
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
rm pch.h.gch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -c pch.h
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists pch.h.gch
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
rm pch.h.gch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -c pch.h
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
expect_exists pch.h.gch
# -------------------------------------------------------------------------
TEST "Create .gch, no -c, -o, with opt-in"
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT pch.h -o pch.gch
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT pch.h -o pch.gch
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists pch.gch
# -------------------------------------------------------------------------
TEST "Use .gch, #include, remove pch.h"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
rm pch.h
$CCACHE_COMPILE $SYSROOT -c pch.c 2>/dev/null
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
# Preprocessor error because GCC can't find the real include file when
# trying to preprocess (gcc -E will be called by ccache):
- expect_stat 'preprocessor error' 1
+ expect_stat preprocessor_error 1
# -------------------------------------------------------------------------
- TEST "Use .gch, -include, no sloppiness"
-
- $REAL_COMPILER $SYSROOT -c pch.h
- backdate pch.h.gch
-
- $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- # Must enable sloppy time macros:
- expect_stat "can't use precompiled header" 1
+ include_pch_variants=(
+ "-include pch.h"
+ "-includepch.h"
+ )
+ for args in "${include_pch_variants[@]}"; do
+ TEST "Use .gch, $args, no sloppiness"
+
+ $COMPILER $SYSROOT -c pch.h
+ backdate pch.h.gch
+
+ $CCACHE_COMPILE $SYSROOT -c $args pch2.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ # Must enable sloppy time macros:
+ expect_stat could_not_use_precompiled_header 1
+ done
# -------------------------------------------------------------------------
TEST "Use .gch, -include"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
+if $RUN_WIN_XFAIL; then
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+fi
# -------------------------------------------------------------------------
TEST "Use .gch, preprocessor mode, -include"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Create .gch, -c, -o"
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -c pch.h -o pch.h.gch
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
rm -f pch.h.gch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -c pch.h -o pch.h.gch
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists pch.h.gch
echo '#include <string.h> /*change pch*/' >>pch.h
rm pch.h.gch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -c pch.h -o pch.h.gch
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
expect_exists pch.h.gch
# -------------------------------------------------------------------------
TEST "Use .gch, -include, PCH_EXTSUM=1"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
echo "original checksum" > pch.h.gch.sum
CCACHE_PCH_EXTSUM=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_PCH_EXTSUM=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo "other checksum" > pch.h.gch.sum
CCACHE_PCH_EXTSUM=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
echo "original checksum" > pch.h.gch.sum
CCACHE_PCH_EXTSUM=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# With GCC, a newly generated PCH is always different, even if the contents
# should be exactly the same. And Clang stores file timestamps, so in this
sleep 1
touch pch.h
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
CCACHE_PCH_EXTSUM=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 3
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Use .gch, -include, no PCH_EXTSUM"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
echo "original checksum" > pch.h.gch.sum
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# external checksum not used, so no cache miss when changed
echo "other checksum" > pch.h.gch.sum
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Use .gch, -include, other dir for .gch"
mkdir -p dir
- $REAL_COMPILER $SYSROOT -c pch.h -o dir/pch.h.gch
+ $COMPILER $SYSROOT -c pch.h -o dir/pch.h.gch
backdate dir/pch.h.gch
rm -f pch.h.gch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include dir/pch.h pch2.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include dir/pch.h pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h -o dir/pch.h.gch
+ $COMPILER $SYSROOT -c pch.h -o dir/pch.h.gch
backdate dir/pch.h.gch
+if $RUN_WIN_XFAIL; then
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include dir/pch.h pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include dir/pch.h pch2.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+fi
rm -rf dir
# -------------------------------------------------------------------------
TEST "Use .gch, preprocessor mode, -include, other dir for .gch"
mkdir -p dir
- $REAL_COMPILER $SYSROOT -c pch.h -o dir/pch.h.gch
+ $COMPILER $SYSROOT -c pch.h -o dir/pch.h.gch
backdate dir/pch.h.gch
rm -f pch.h.gch
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include dir/pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include dir/pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h -o dir/pch.h.gch
+ $COMPILER $SYSROOT -c pch.h -o dir/pch.h.gch
backdate dir/pch.h.gch
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include dir/pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include dir/pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
rm -rf dir
# -------------------------------------------------------------------------
TEST "Use .gch, depend mode, -include"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
CCACHE_DEPEND=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c -MD -MF pch.d
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_DEPEND=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c -MD -MF pch.d
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
CCACHE_DEPEND=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c -MD -MF pch.d
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
CCACHE_DEPEND=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c -MD -MF pch.d
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
}
pch_suite_gcc() {
# -------------------------------------------------------------------------
TEST "Use .gch, -include, remove pch.h"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
rm pch.h
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Use .gch, #include, no sloppiness"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
rm pch.h
$CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
# Must enable sloppy time macros:
- expect_stat "can't use precompiled header" 1
+ expect_stat could_not_use_precompiled_header 1
# -------------------------------------------------------------------------
TEST "Use .gch, #include"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
rm pch.h
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
+if $RUN_WIN_XFAIL; then
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+fi
# -------------------------------------------------------------------------
TEST "Use .gch, preprocessor mode, #include"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
rm pch.h
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
+if $RUN_WIN_XFAIL; then
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
+fi
# -------------------------------------------------------------------------
TEST "Create and use .gch directory"
mkdir pch.h.gch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -x c-header -c pch.h -o pch.h.gch/foo
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
rm pch.h.gch/foo
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -x c-header -c pch.h -o pch.h.gch/foo
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists pch.h.gch/foo
backdate pch.h.gch/foo
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
echo "updated" >>pch.h.gch/foo # GCC seems to cope with this...
backdate pch.h.gch/foo
+if $RUN_WIN_XFAIL; then
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 3
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 3
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 3
+fi
# -------------------------------------------------------------------------
TEST "Use .gch, #include, PCH_EXTSUM=1"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
echo "original checksum" > pch.h.gch.sum
CCACHE_PCH_EXTSUM=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_PCH_EXTSUM=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo "other checksum" > pch.h.gch.sum
CCACHE_PCH_EXTSUM=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
echo "original checksum" > pch.h.gch.sum
CCACHE_PCH_EXTSUM=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# With GCC, a newly generated PCH is always different, even if the contents
# should be exactly the same. And Clang stores file timestamps, so in this
sleep 1
touch pch.h
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
CCACHE_PCH_EXTSUM=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 3
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Use .gch, #include, no PCH_EXTSUM"
- $REAL_COMPILER $SYSROOT -c pch.h
+ $COMPILER $SYSROOT -c pch.h
backdate pch.h.gch
echo "original checksum" > pch.h.gch.sum
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# external checksum not used, so no cache miss when changed
echo "other checksum" > pch.h.gch.sum
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -fpch-preprocess pch.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "Too new PCH file"
touch lib.h
touch main.c
- $REAL_COMPILER $SYSROOT -c lib.h
+ $COMPILER $SYSROOT -c lib.h
touch -d "@$(($(date +%s) + 60))" lib.h.gch # 1 minute in the future
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines,time_macros" $CCACHE_COMPILE -include lib.h -c main.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat "can't use precompiled header" 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat could_not_use_precompiled_header 1
}
pch_suite_clang() {
sleep 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -c pch2.h
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
touch test.h
sleep 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -c pch2.h
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
- $REAL_COMPILER $SYSROOT -c -include pch2.h pch2.c
+ $COMPILER $SYSROOT -c -include pch2.h pch2.c
expect_exists pch2.o
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -c pch2.h
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Use .pch, -include, no sloppiness"
- $REAL_COMPILER $SYSROOT -c pch.h -o pch.h.pch
+ $COMPILER $SYSROOT -c pch.h -o pch.h.pch
backdate pch.h.pch
$CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
# Must enable sloppy time macros:
- expect_stat "can't use precompiled header" 1
+ expect_stat could_not_use_precompiled_header 1
# -------------------------------------------------------------------------
TEST "Use .pch, -include"
- $REAL_COMPILER $SYSROOT -c pch.h -o pch.h.pch
+ $COMPILER $SYSROOT -c pch.h -o pch.h.pch
backdate pch.h.pch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h -o pch.h.pch
+ $COMPILER $SYSROOT -c pch.h -o pch.h.pch
backdate pch.h.pch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Use .pch, preprocessor mode, -include"
- $REAL_COMPILER $SYSROOT -c pch.h -o pch.h.pch
+ $COMPILER $SYSROOT -c pch.h -o pch.h.pch
backdate pch.h.pch
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h -o pch.h.pch
+ $COMPILER $SYSROOT -c pch.h -o pch.h.pch
backdate pch.h.pch
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include pch.h pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Use .pch, -include-pch"
- $REAL_COMPILER $SYSROOT -c pch.h -o pch.h.pch
+ $COMPILER $SYSROOT -c pch.h -o pch.h.pch
backdate pch.h.pch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include-pch pch.h.pch pch2.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include-pch pch.h.pch pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h -o pch.h.pch
+ $COMPILER $SYSROOT -c pch.h -o pch.h.pch
backdate pch.h.pch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include-pch pch.h.pch pch2.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Use .pch, preprocessor mode, -include-pch"
- $REAL_COMPILER $SYSROOT -c pch.h -o pch.h.pch
+ $COMPILER $SYSROOT -c pch.h -o pch.h.pch
backdate pch.h.pch
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include-pch pch.h.pch pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include-pch pch.h.pch pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -c pch.h -o pch.h.pch
+ $COMPILER $SYSROOT -c pch.h -o pch.h.pch
backdate pch.h.pch
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include-pch pch.h.pch pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -c -include-pch pch.h.pch pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 2
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Create .pch with -Xclang options"
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -Xclang -emit-pch -o pch.h.pch -c pch.h
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
rm pch.h.pch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -Xclang -emit-pch -o pch.h.pch -c pch.h
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists pch.h.pch
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
rm pch.h.pch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS pch_defines" $CCACHE_COMPILE $SYSROOT -Xclang -emit-pch -o pch.h.pch -c pch.h
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
expect_exists pch.h.pch
# -------------------------------------------------------------------------
TEST "Use .pch the with -Xclang options"
- $REAL_COMPILER $SYSROOT -Xclang -emit-pch -o pch.h.pch -c pch.h
+ $COMPILER $SYSROOT -Xclang -emit-pch -o pch.h.pch -c pch.h
backdate pch.h.pch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -Xclang -include-pch -Xclang pch.h.pch -Xclang -include -Xclang pch.h -c pch.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -Xclang -include-pch -Xclang pch.h.pch -Xclang -include -Xclang pch.h -c pch.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
echo '#include <string.h> /*change pch*/' >>pch.h
backdate pch.h
- $REAL_COMPILER $SYSROOT -Xclang -emit-pch -o pch.h.pch -c pch.h
+ $COMPILER $SYSROOT -Xclang -emit-pch -o pch.h.pch -c pch.h
backdate pch.h.pch
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -Xclang -include-pch -Xclang pch.h.pch -Xclang -include -Xclang pch.h -c pch.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
CCACHE_SLOPPINESS="$DEFAULT_SLOPPINESS time_macros" $CCACHE_COMPILE $SYSROOT -Xclang -include-pch -Xclang pch.h.pch -Xclang -include -Xclang pch.h -c pch.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
}
+# Remove header, including a volatile timestamp, from a .gcno file.
+normalize_gcno_file() {
+ local from="$1"
+ local to="$2"
+ tail -c +13 "${from}" >"${to}"
+}
+
+
SUITE_profiling_PROBE() {
- touch test.c
+ echo 'int main(void) { return 0; }' >test.c
if ! $COMPILER -fprofile-generate -c test.c 2>/dev/null; then
echo "compiler does not support profiling"
fi
+ if ! $COMPILER -fprofile-generate test.o -o test 2>/dev/null; then
+ echo "compiler cannot link with profiling"
+ fi
if ! $COMPILER -fprofile-generate=data -c test.c 2>/dev/null; then
echo "compiler does not support -fprofile-generate=path"
fi
TEST "-fprofile-use, missing file"
$CCACHE_COMPILE -fprofile-use -c test.c 2>/dev/null
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 0
- expect_stat 'no input file' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat no_input_file 1
# -------------------------------------------------------------------------
TEST "-fbranch-probabilities, missing file"
$CCACHE_COMPILE -fbranch-probabilities -c test.c 2>/dev/null
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 0
- expect_stat 'no input file' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat no_input_file 1
# -------------------------------------------------------------------------
TEST "-fprofile-use=file, missing file"
$CCACHE_COMPILE -fprofile-use=data.gcda -c test.c 2>/dev/null
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 0
- expect_stat 'no input file' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat no_input_file 1
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST "-fprofile-use"
$CCACHE_COMPILE -fprofile-generate -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$COMPILER -fprofile-generate test.o -o test
merge_profiling_data .
$CCACHE_COMPILE -fprofile-use -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-use -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
./test
merge_profiling_data .
$CCACHE_COMPILE -fprofile-use -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
+fi
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST "-fprofile-use=dir"
mkdir data
$CCACHE_COMPILE -fprofile-generate=data -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$COMPILER -fprofile-generate test.o -o test
merge_profiling_data data
$CCACHE_COMPILE -fprofile-use=data -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-use=data -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
./test
merge_profiling_data data
$CCACHE_COMPILE -fprofile-use=data -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
+fi
+ # -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
+ TEST "-fprofile-generate=dir in different directories"
+
+ mkdir -p dir1/data dir2/data
+
+ cd dir1
+
+ $CCACHE_COMPILE -Werror -fprofile-generate=data -c ../test.c \
+ || test_failed "compilation error"
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+
+ $CCACHE_COMPILE -Werror -fprofile-generate=data -c ../test.c \
+ || test_failed "compilation error"
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+
+ $COMPILER -Werror -fprofile-generate test.o -o test \
+ || test_failed "compilation error"
+
+ ./test || test_failed "execution error"
+ merge_profiling_data data
+
+ $CCACHE_COMPILE -Werror -fprofile-use=data -c ../test.c \
+ || test_failed "compilation error"
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+
+ cd ../dir2
+
+ $CCACHE_COMPILE -Werror -fprofile-generate=data -c ../test.c \
+ || test_failed "compilation error"
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
+
+ $CCACHE_COMPILE -Werror -fprofile-generate=data -c ../test.c \
+ || test_failed "compilation error"
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 3
+
+ $COMPILER -Werror -fprofile-generate test.o -o test \
+ || test_failed "compilation error"
+ ./test || test_failed "execution error"
+ merge_profiling_data data
+
+ $CCACHE_COMPILE -Werror -fprofile-use=data -c ../test.c \
+ || test_failed "compilation error"
+ # Note: No expect_stat here since GCC and Clang behave differently – just
+ # check that the compiler doesn't warn about not finding the profile data.
+fi
# -------------------------------------------------------------------------
- TEST "-ftest-coverage with -fprofile-dir"
-
- # GCC 9 and newer creates a mangled .gcno filename (still in the current
- # working directory) if -fprofile-dir is given.
-
- for flag in "" -fprofile-dir=dir; do
- for dir in . subdir; do
- $CCACHE -z >/dev/null
-
- mkdir -p "$dir"
- touch "$dir/test.c"
- find -name '*.gcno' -delete
-
- $REAL_COMPILER $flag -ftest-coverage -c $dir/test.c -o $dir/test.o
- gcno_name=$(find -name '*.gcno')
- rm "$gcno_name"
-
- $CCACHE_COMPILE $flag -ftest-coverage -c $dir/test.c -o $dir/test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_exists "$gcno_name"
- rm "$gcno_name"
-
- $CCACHE_COMPILE $flag -ftest-coverage -c $dir/test.c -o $dir/test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_exists "$gcno_name"
- rm "$gcno_name"
+ if $COMPILER_TYPE_GCC; then
+ # GCC 9 and newer creates a mangled .gcno filename (still in the current
+ # working directory) if -fprofile-dir is given.
+ for flag in "" -fprofile-dir=dir; do
+ for dir in . subdir; do
+ TEST "-ftest-coverage with -fprofile-dir=$flag, dir=$dir"
+ $CCACHE -z >/dev/null
+
+ mkdir -p "$dir"
+ touch "$dir/test.c"
+ find -name '*.gcno' -delete
+
+ $COMPILER $flag -ftest-coverage -c $dir/test.c -o $dir/test.o
+ gcno_name=$(find -name '*.gcno')
+ rm "$gcno_name"
+
+ $CCACHE_COMPILE $flag -ftest-coverage -c $dir/test.c -o $dir/test.o
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_exists "$gcno_name"
+ rm "$gcno_name"
+
+ $CCACHE_COMPILE $flag -ftest-coverage -c $dir/test.c -o $dir/test.o
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_exists "$gcno_name"
+ rm "$gcno_name"
+ done
done
- done
+ fi
# -------------------------------------------------------------------------
TEST "-fprofile-arcs for different object file paths"
mkdir obj1 obj2
$CCACHE_COMPILE -fprofile-arcs -c test.c -o obj1/test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -fprofile-arcs -c test.c -o obj1/test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
$CCACHE_COMPILE -fprofile-arcs -c test.c -o obj2/test.o
expect_different_content obj1/test.o obj2/test.o # different paths to .gcda file
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-arcs -c test.c -o obj2/test.o
expect_different_content obj1/test.o obj2/test.o # different paths to .gcda file
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+
+ # -------------------------------------------------------------------------
+ TEST "-ftest-coverage, different directories"
+
+ mkdir obj1 obj2
+
+ cd obj1
+ $COMPILER -ftest-coverage -c "$(pwd)/../test.c"
+ normalize_gcno_file test.gcno test.gcno.reference
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ normalize_gcno_file test.gcno test.gcno.ccache-miss
+ expect_equal_content test.gcno.reference test.gcno.ccache-miss
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ normalize_gcno_file test.gcno test.gcno.ccache-hit
+ expect_equal_content test.gcno.reference test.gcno.ccache-hit
+
+ cd ../obj2
+ $COMPILER -ftest-coverage -c "$(pwd)/../test.c"
+ normalize_gcno_file test.gcno test.gcno.reference
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ normalize_gcno_file test.gcno test.gcno.ccache-miss
+ expect_equal_content test.gcno.reference test.gcno.ccache-miss
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+ normalize_gcno_file test.gcno test.gcno.ccache-hit
+ expect_equal_content test.gcno.reference test.gcno.ccache-hit
+
+ # -------------------------------------------------------------------------
+ TEST "-ftest-coverage, different directories, basedir, sloppy gcno_cwd"
+
+ export CCACHE_SLOPPINESS="$CCACHE_SLOPPINESS gcno_cwd"
+ export CCACHE_BASEDIR="$(pwd)"
+
+ mkdir obj1 obj2
+
+ cd obj1
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+
+ cd ../obj2
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
}
merge_profiling_data() {
mkdir data
$CCACHE_COMPILE -fprofile-generate=data -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$COMPILER -fprofile-generate=data test.o -o test
llvm-profdata$CLANG_VERSION_SUFFIX merge -output foo.profdata data/default_*.profraw
$CCACHE_COMPILE -fprofile-use=foo.profdata -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-use=foo.profdata -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
./test
llvm-profdata$CLANG_VERSION_SUFFIX merge -output foo.profdata data/default_*.profraw
$CCACHE_COMPILE -fprofile-use=foo.profdata -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
# -------------------------------------------------------------------------
TEST "-fprofile-instr-use"
mkdir data
$CCACHE_COMPILE -fprofile-instr-generate -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$COMPILER -fprofile-instr-generate test.o -o test
llvm-profdata$CLANG_VERSION_SUFFIX merge -output default.profdata default.profraw
$CCACHE_COMPILE -fprofile-instr-use -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-instr-use -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
echo >>default.profdata # Dummy change to trigger modification
$CCACHE_COMPILE -fprofile-instr-use -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
# -------------------------------------------------------------------------
TEST "-fprofile-instr-use=file"
$CCACHE_COMPILE -fprofile-instr-generate=foo.profraw -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$COMPILER -fprofile-instr-generate=data=foo.profraw test.o -o test
llvm-profdata$CLANG_VERSION_SUFFIX merge -output foo.profdata foo.profraw
$CCACHE_COMPILE -fprofile-instr-use=foo.profdata -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-instr-use=foo.profdata -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
echo >>foo.profdata # Dummy change to trigger modification
$CCACHE_COMPILE -fprofile-instr-use=foo.profdata -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
# -------------------------------------------------------------------------
TEST "-fprofile-sample-use"
echo 'main:1:1' > sample.prof
$CCACHE_COMPILE -fprofile-sample-use=sample.prof -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -fprofile-sample-use=sample.prof -fprofile-sample-accurate -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-sample-use=sample.prof -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-sample-use=sample.prof -fprofile-sample-accurate -c test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
echo 'main:2:2' > sample.prof
$CCACHE_COMPILE -fprofile-sample-use=sample.prof -c test.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 3
echo 'main:1:1' > sample.prof
$CCACHE_COMPILE -fprofile-sample-use=sample.prof -c test.c
- expect_stat 'cache hit (direct)' 3
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 3
+ expect_stat cache_miss 3
}
if ! $COMPILER_TYPE_GCC; then
echo "compiler is not GCC"
fi
+ if ! $RUN_WIN_XFAIL; then
+ echo "this suite does not work on Windows"
+ fi
}
SUITE_profiling_gcc_SETUP() {
TEST "-fbranch-probabilities"
$CCACHE_COMPILE -fprofile-generate -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$COMPILER -fprofile-generate test.o -o test
./test
$CCACHE_COMPILE -fbranch-probabilities -c test.c 2>/dev/null
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fbranch-probabilities -c test.c 2>/dev/null
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
./test
$CCACHE_COMPILE -fbranch-probabilities -c test.c 2>/dev/null
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
# -------------------------------------------------------------------------
TEST "-fprofile-dir=dir + -fprofile-use"
mkdir data
$CCACHE_COMPILE -fprofile-dir=data -fprofile-generate -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$COMPILER -fprofile-generate test.o -o test
./test
$CCACHE_COMPILE -fprofile-dir=data -fprofile-use -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-dir=data -fprofile-use -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
./test
$CCACHE_COMPILE -fprofile-dir=data -fprofile-use -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
# -------------------------------------------------------------------------
TEST "-fprofile-use + -fprofile-dir=dir"
mkdir data
$CCACHE_COMPILE -fprofile-generate -fprofile-dir=data -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$COMPILER -fprofile-generate test.o -o test
./test
$CCACHE_COMPILE -fprofile-use -fprofile-dir=data -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-use -fprofile-dir=data -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
./test
$CCACHE_COMPILE -fprofile-use -fprofile-dir=data -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
# -------------------------------------------------------------------------
TEST "-fprofile-dir=path1 + -fprofile-use=path2"
mkdir data
$CCACHE_COMPILE -fprofile-dir=data2 -fprofile-generate=data -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$COMPILER -fprofile-generate test.o -o test
./test
$CCACHE_COMPILE -fprofile-dir=data2 -fprofile-use=data -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
$CCACHE_COMPILE -fprofile-dir=data2 -fprofile-use=data -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
./test
$CCACHE_COMPILE -fprofile-dir=data2 -fprofile-use=data -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
+
+ # -------------------------------------------------------------------------
+ TEST "-fprofile-abs-path"
+
+ if $COMPILER -fprofile-abs-path -c test.c 2>/dev/null; then
+ mkdir a b
+
+ cd a
+
+ $CCACHE_COMPILE -fprofile-abs-path -ftest-coverage -c ../test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+
+ $CCACHE_COMPILE -fprofile-abs-path -ftest-coverage -c ../test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+
+ cd ../b
+
+ $CCACHE_COMPILE -fprofile-abs-path -ftest-coverage -c ../test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+
+ $CCACHE_COMPILE -fprofile-abs-path -ftest-coverage -c ../test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+
+ export CCACHE_SLOPPINESS="$CCACHE_SLOPPINESS gcno_cwd"
+
+ cd ../a
+
+ $CCACHE_COMPILE -fprofile-abs-path -ftest-coverage -c ../test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 3
+
+ $CCACHE_COMPILE -fprofile-abs-path -ftest-coverage -c ../test.c
+ expect_stat direct_cache_hit 3
+ expect_stat cache_miss 3
+
+ cd ../b
+
+ $CCACHE_COMPILE -fprofile-abs-path -ftest-coverage -c ../test.c
+ expect_stat direct_cache_hit 4
+ expect_stat cache_miss 3
+ fi
}
hip_opts="-x hip --cuda-gpu-arch=gfx900 -nogpulib"
$CCACHE_COMPILE $hip_opts -c test1.hip
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE $hip_opts -c test1.hip
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
$CCACHE_COMPILE $hip_opts --cuda-gpu-arch=gfx906 -c test1.hip
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
$CCACHE_COMPILE $hip_opts -c test2.hip
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
$CCACHE_COMPILE $hip_opts -c test2.hip
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 3
+ expect_stat preprocessed_cache_hit 0
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 3
$CCACHE_COMPILE $hip_opts -Dx=x -c test2.hip
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 3
+ expect_stat preprocessed_cache_hit 1
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 3
$CCACHE_COMPILE $hip_opts -Dx=y -c test2.hip
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 4
+ expect_stat preprocessed_cache_hit 1
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 4
}
# Cache a compilation.
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
rm test.o
# Make the cache read-only.
if [ $? -ne 0 ]; then
test_failed "Failure when compiling test2.c read-only"
fi
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 0
# -------------------------------------------------------------------------
# Check that read-only mode and direct mode work together.
# Cache a compilation.
$CCACHE_COMPILE -c test.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
rm test.o
# Make the cache read-only.
TEST "Direct hit"
$CCACHE_COMPILE -c test.c -o test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat direct_cache_miss 1
+ expect_stat preprocessed_cache_miss 1
CCACHE_READONLY_DIRECT=1 $CCACHE_COMPILE -c test.c -o test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat direct_cache_miss 1
+ expect_stat preprocessed_cache_miss 1
# -------------------------------------------------------------------------
TEST "Direct miss doesn't lead to preprocessed hit"
$CCACHE_COMPILE -c test.c -o test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat direct_cache_miss 1
+ expect_stat preprocessed_cache_miss 1
CCACHE_READONLY_DIRECT=1 $CCACHE_COMPILE -DFOO -c test.c -o test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat direct_cache_miss 2
+ expect_stat preprocessed_cache_miss 1
}
--- /dev/null
+# This test suite verified both the file storage backend and the remote
+# storage framework itself.
+
+SUITE_remote_file_PROBE() {
+ if ! $RUN_WIN_XFAIL; then
+ echo "remote file is broken on windows."
+ fi
+}
+
+SUITE_remote_file_SETUP() {
+ unset CCACHE_NODIRECT
+ export CCACHE_REMOTE_STORAGE="file:$PWD/remote"
+
+ generate_code 1 test.c
+}
+
+SUITE_remote_file() {
+ # -------------------------------------------------------------------------
+ TEST "Base case"
+
+ # Compile and send result to local and remote storage.
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 2 # result + manifest
+ expect_stat local_storage_write 2 # result + manifest
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 2 # result + manifest
+ expect_stat remote_storage_write 2 # result + manifest
+ expect_exists remote/CACHEDIR.TAG
+ subdirs=$(find remote -type d | wc -l)
+ if [ "${subdirs}" -lt 2 ]; then # "remote" itself counts as one
+ test_failed "Expected subdirectories in remote"
+ fi
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ # Get result from local storage.
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat local_storage_hit 1
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 2 # result + manifest
+ expect_stat local_storage_read_miss 2 # result + manifest
+ expect_stat local_storage_write 2 # result + manifest
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 2
+ expect_stat remote_storage_write 2
+ expect_stat files_in_cache 2
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ # Clear local storage.
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ # Get result from remote storage, copying it to local storage.
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat local_storage_hit 1
+ expect_stat local_storage_miss 2
+ expect_stat local_storage_read_hit 2 # result + manifest
+ expect_stat local_storage_read_miss 4 # 2 * (result + manifest)
+ expect_stat local_storage_write 4 # 2 * (result + manifest)
+ expect_stat remote_storage_hit 1
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 2 # result + manifest
+ expect_stat remote_storage_read_miss 2 # result + manifest
+ expect_stat remote_storage_write 2 # result + manifest
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ # Get result from local storage again.
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 3
+ expect_stat cache_miss 1
+ expect_stat local_storage_hit 2
+ expect_stat local_storage_miss 2
+ expect_stat local_storage_read_hit 4 # 2 * (result + manifest)
+ expect_stat local_storage_read_miss 4 # 2 * (result + manifest)
+ expect_stat local_storage_write 4 # 2 * (result + manifest)
+ expect_stat remote_storage_hit 1
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 2 # result + manifest
+ expect_stat remote_storage_read_miss 2 # result + manifest
+ expect_stat remote_storage_write 2 # result + manifest
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Flat layout"
+
+ CCACHE_REMOTE_STORAGE+="|layout=flat"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_exists remote/CACHEDIR.TAG
+ subdirs=$(find remote -type d | wc -l)
+ if [ "${subdirs}" -ne 1 ]; then # "remote" itself counts as one
+ test_failed "Expected no subdirectories in remote"
+ fi
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Two directories"
+
+ CCACHE_REMOTE_STORAGE+=" file://$PWD/remote_2"
+ mkdir remote_2
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+ expect_file_count 3 '*' remote_2 # CACHEDIR.TAG + result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+ expect_file_count 3 '*' remote_2 # CACHEDIR.TAG + result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+ expect_file_count 3 '*' remote_2 # CACHEDIR.TAG + result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+
+ rm -r remote/??
+ expect_file_count 1 '*' remote # CACHEDIR.TAG
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote_2
+ expect_file_count 1 '*' remote # CACHEDIR.TAG
+ expect_file_count 3 '*' remote_2 # CACHEDIR.TAG + result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Read-only"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ CCACHE_REMOTE_STORAGE+="|read-only"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ echo 'int x;' >> test.c
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "umask"
+
+ export CCACHE_UMASK=042
+ CCACHE_REMOTE_STORAGE="file://$PWD/remote|umask=024"
+ rm -rf remote
+ $CCACHE_COMPILE -c test.c
+ expect_perm remote drwxr-x-wx # 777 & 024
+ expect_perm remote/CACHEDIR.TAG -rw-r---w- # 666 & 024
+ result_file=$(find $CCACHE_DIR -name '*R')
+ expect_perm "$(dirname "${result_file}")" drwx-wxr-x # 777 & 042
+ expect_perm "${result_file}" -rw--w-r-- # 666 & 042
+
+ CCACHE_REMOTE_STORAGE="file://$PWD/remote|umask=026"
+ $CCACHE -C >/dev/null
+ rm -rf remote
+ $CCACHE_COMPILE -c test.c
+ expect_perm remote drwxr-x--x # 777 & 026
+ expect_perm remote/CACHEDIR.TAG -rw-r----- # 666 & 026
+
+ # -------------------------------------------------------------------------
+ TEST "Sharding"
+
+ CCACHE_REMOTE_STORAGE="file://$PWD/remote/*|shards=a,b(2)"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ if [ ! -d remote/a ] && [ ! -d remote/b ]; then
+ test_failed "Expected remote/a or remote/b to exist"
+ fi
+
+ # -------------------------------------------------------------------------
+ TEST "Reshare"
+
+ CCACHE_REMOTE_STORAGE="" $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 2
+ expect_stat local_storage_write 2
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 0
+ expect_stat remote_storage_write 0
+ expect_missing remote
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat local_storage_hit 1
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 2
+ expect_stat local_storage_read_miss 2
+ expect_stat local_storage_write 2
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 0
+ expect_stat remote_storage_write 0
+ expect_missing remote
+
+ CCACHE_RESHARE=1 $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat local_storage_hit 2
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 4
+ expect_stat local_storage_read_miss 2
+ expect_stat local_storage_write 2
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 0
+ expect_stat remote_storage_write 2
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ $CCACHE -C >/dev/null
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 3
+ expect_stat cache_miss 1
+ expect_stat local_storage_hit 2
+ expect_stat local_storage_miss 2
+ expect_stat local_storage_read_hit 4
+ expect_stat local_storage_read_miss 4
+ expect_stat local_storage_write 4
+ expect_stat remote_storage_hit 1
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 2
+ expect_stat remote_storage_read_miss 0
+ expect_stat remote_storage_write 2
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Recache"
+
+ CCACHE_RECACHE=1 $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat direct_cache_miss 0
+ expect_stat preprocessed_cache_miss 0
+ expect_stat cache_miss 0
+ expect_stat recache 1
+ expect_stat files_in_cache 2
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 0
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 1 # Try to read manifest for updating
+ expect_stat local_storage_write 2
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 1 # Try to read manifest for updating
+ expect_stat remote_storage_write 2
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ CCACHE_RECACHE=1 $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+
+ expect_stat direct_cache_miss 0
+ expect_stat preprocessed_cache_miss 0
+ expect_stat cache_miss 0
+ expect_stat recache 2
+ expect_stat files_in_cache 2
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 0
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 2 # Try to read manifest for updating
+ expect_stat local_storage_write 4
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 1 # Read manifest for updating
+ expect_stat remote_storage_read_miss 1
+ expect_stat remote_storage_write 3 # Not 4 since result key already present
+
+ # -------------------------------------------------------------------------
+ if touch test.c && ln test.c test-if-fs-supports-hard-links.c 2>/dev/null; then
+ TEST "Don't reshare results with raw files"
+
+ CCACHE_REMOTE_STORAGE= CCACHE_HARDLINK=1 $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 3
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 2
+ expect_stat local_storage_write 2
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 0
+ expect_stat remote_storage_write 0
+
+ CCACHE_RESHARE=1 $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 3
+ expect_stat local_storage_hit 1
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 2
+ expect_stat local_storage_read_miss 2
+ expect_stat local_storage_write 2
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 0
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 0
+ expect_stat remote_storage_write 1 # result not saved since not self-contained
+ expect_file_count 2 '*' remote # CACHEDIR.TAG + manifest, not result
+ fi
+
+ # -------------------------------------------------------------------------
+ TEST "Manifest handling"
+
+ echo 'int x;' >test.h
+ backdate test.h
+ echo '#include "test.h"' >test.c
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 2 # miss: manifest + result
+ expect_stat local_storage_write 2 # manifest + result
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 2
+ expect_stat remote_storage_write 2 # miss: manifest + result
+
+ # Both local and remote now have an "int x;" key in the manifest.
+
+ echo 'int y;' >test.h
+ backdate test.h
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 2
+ expect_stat local_storage_read_hit 1 # hit: manifest without key
+ expect_stat local_storage_read_miss 3 # miss: result
+ expect_stat local_storage_write 5 # miss: merged manifest + new manifest entry + result
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 2
+ expect_stat remote_storage_read_hit 1 # # hit: manifest without key
+ expect_stat remote_storage_read_miss 3 # miss: result
+ expect_stat remote_storage_write 4 # miss: manifest + result
+
+ # Both local and remote now have "int x;" and "int y;" keys in the manifest.
+
+ $CCACHE -C >/dev/null
+
+ # Now only remote has "int x;" and "int y;" keys in the manifest. We
+ # should now be able to get remote hit without involving local.
+
+ echo 'int x;' >test.h
+ backdate test.h
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 3
+ expect_stat local_storage_read_hit 1
+ expect_stat local_storage_read_miss 5 # miss: manifest + result
+ expect_stat local_storage_write 7 # miss: manifest + result
+ expect_stat remote_storage_hit 1
+ expect_stat remote_storage_miss 2
+ expect_stat remote_storage_read_hit 3
+ expect_stat remote_storage_read_miss 3
+ expect_stat remote_storage_write 4
+
+ # Should be able to get remote hit without involving local.
+
+ echo 'int y;' >test.h
+ backdate test.h
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 4
+ expect_stat local_storage_read_hit 2 # hit: manifest with key (downloaded from previous step)
+ expect_stat local_storage_read_miss 6 # miss: manifest + result
+ expect_stat local_storage_write 8 # miss: result
+ expect_stat remote_storage_hit 2
+ expect_stat remote_storage_miss 2
+ expect_stat remote_storage_read_hit 4 # hit: result
+ expect_stat remote_storage_read_miss 3
+ expect_stat remote_storage_write 4
+
+ # -------------------------------------------------------------------------
+ TEST "Manifest merging"
+
+ echo 'int x;' >test.h
+ backdate test.h
+ echo '#include "test.h"' >test.c
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 1
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 2 # miss: manifest + result
+ expect_stat local_storage_write 2
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 2 # miss: manifest + result 3
+ expect_stat remote_storage_write 2
+
+ $CCACHE -C >/dev/null
+
+ # Now remote has an "int x;" key in the manifest and local has none.
+
+ echo 'int y;' >test.h
+ backdate test.h
+
+ CCACHE_REMOTE_STORAGE= $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 2
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 4 # miss: manifest + result
+ expect_stat local_storage_write 4
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 2
+ expect_stat remote_storage_write 2
+
+ # Now local has "int y;" while remote still has "int x;".
+
+ echo 'int x;' >test.h
+ backdate test.h
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 3
+ expect_stat local_storage_read_hit 1 # hit: manifest without key
+ expect_stat local_storage_read_miss 5 # miss: result
+ expect_stat local_storage_write 6
+ expect_stat remote_storage_hit 1
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 2 # hit: manifest + result
+ expect_stat remote_storage_read_miss 2
+ expect_stat remote_storage_write 2
+
+ # Local manifest with "int y;" was merged with remote's "int x;" above, so
+ # we should now be able to get "int x;" and "int y;" hits locally.
+
+ echo 'int y;' >test.h
+ backdate test.h
+
+ CCACHE_REMOTE_STORAGE= $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat local_storage_hit 1
+ expect_stat local_storage_miss 3
+ expect_stat local_storage_read_hit 3 # hit: manifest + result
+ expect_stat local_storage_read_miss 5 # miss: result
+ expect_stat local_storage_write 6
+ expect_stat remote_storage_hit 1
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 2
+ expect_stat remote_storage_read_miss 2
+ expect_stat remote_storage_write 2
+
+ echo 'int x;' >test.h
+ backdate test.h
+
+ CCACHE_REMOTE_STORAGE= $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 3
+ expect_stat cache_miss 2
+ expect_stat local_storage_hit 2
+ expect_stat local_storage_miss 3
+ expect_stat local_storage_read_hit 5 # hit: manifest + result
+ expect_stat local_storage_read_miss 5 # miss: result
+ expect_stat local_storage_write 6
+ expect_stat remote_storage_hit 1
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 2
+ expect_stat remote_storage_read_miss 2
+ expect_stat remote_storage_write 2
+}
--- /dev/null
+start_http_server() {
+ local port="$1"
+ local cache_dir="$2"
+ local credentials="$3" # optional parameter
+
+ mkdir -p "${cache_dir}"
+ "${HTTP_SERVER}" --bind localhost --directory "${cache_dir}" "${port}" \
+ ${credentials:+--basic-auth ${credentials}} \
+ &>http-server.log &
+ "${HTTP_CLIENT}" "http://localhost:${port}" &>http-client.log \
+ ${credentials:+--basic-auth ${credentials}} \
+ || test_failed_internal "Cannot connect to server"
+}
+
+maybe_start_ipv6_http_server() {
+ local port="$1"
+ local cache_dir="$2"
+ local credentials="$3" # optional parameter
+
+ mkdir -p "${cache_dir}"
+ "${HTTP_SERVER}" --bind "::1" --directory "${cache_dir}" "${port}" \
+ ${credentials:+--basic-auth ${credentials}} \
+ &>http-server.log &
+ "${HTTP_CLIENT}" "http://[::1]:${port}" &>http-client.log \
+ ${credentials:+--basic-auth ${credentials}} \
+ || return 1
+}
+
+SUITE_remote_http_PROBE() {
+ if ! "${HTTP_SERVER}" --help >/dev/null 2>&1; then
+ echo "cannot execute ${HTTP_SERVER} - Python 3 might be missing"
+ fi
+}
+
+SUITE_remote_http_SETUP() {
+ unset CCACHE_NODIRECT
+
+ generate_code 1 test.c
+}
+
+SUITE_remote_http() {
+ # -------------------------------------------------------------------------
+ TEST "Subdirs layout"
+
+ start_http_server 12780 remote
+ export CCACHE_REMOTE_STORAGE="http://localhost:12780"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 2 '*' remote # result + manifest
+ subdirs=$(find remote -type d | wc -l)
+ if [ "${subdirs}" -lt 2 ]; then # "remote" itself counts as one
+ test_failed "Expected subdirectories in remote"
+ fi
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 2 '*' remote # result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_file_count 2 '*' remote # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_file_count 2 '*' remote # result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Flat layout"
+
+ start_http_server 12780 remote
+ export CCACHE_REMOTE_STORAGE="http://localhost:12780|layout=flat"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 2 '*' remote # result + manifest
+ subdirs=$(find remote -type d | wc -l)
+ if [ "${subdirs}" -ne 1 ]; then # "remote" itself counts as one
+ test_failed "Expected no subdirectories in remote"
+ fi
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 2 '*' remote # result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_file_count 2 '*' remote # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_file_count 2 '*' remote # result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Bazel layout"
+
+ start_http_server 12780 remote
+ mkdir remote/ac
+ export CCACHE_REMOTE_STORAGE="http://localhost:12780|layout=bazel"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 2 '*' remote/ac # result + manifest
+ if [ "$(ls remote/ac | grep -Ec '^[0-9a-f]{64}$')" -ne 2 ]; then
+ test_failed "Bazel layout filenames not as expected"
+ fi
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 2 '*' remote/ac # result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_file_count 2 '*' remote/ac # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_file_count 2 '*' remote/ac # result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Basic auth"
+
+ start_http_server 12780 remote "somebody:secret123"
+ export CCACHE_REMOTE_STORAGE="http://somebody:secret123@localhost:12780"
+
+ CCACHE_DEBUG=1 $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 2 '*' remote # result + manifest
+ expect_not_contains test.o.*.ccache-log secret123
+
+ # -------------------------------------------------------------------------
+ # This test fails sporadically.
+ # Mostly with MSVC 32 bit, but from time to time also with all other
+ # Windows test runs.
+ # Probably the http-server is doing something wrong here.
+if $RUN_WIN_XFAIL; then
+ TEST "Basic auth required"
+
+ start_http_server 12780 remote "somebody:secret123"
+ # no authentication configured on client
+ export CCACHE_REMOTE_STORAGE="http://localhost:12780"
+
+ CCACHE_DEBUG=1 $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 0 '*' remote # result + manifest
+ expect_contains test.o.*.ccache-log "status code: 401"
+fi
+
+ # -------------------------------------------------------------------------
+ # This test fails sporadically.
+ # Mostly with MSVC 32 bit, but from time to time also with all other
+ # Windows test runs.
+ # Probably the http-server is doing something wrong here.
+if $RUN_WIN_XFAIL; then
+ TEST "Basic auth failed"
+
+ start_http_server 12780 remote "somebody:secret123"
+ export CCACHE_REMOTE_STORAGE="http://somebody:wrong@localhost:12780"
+
+ CCACHE_DEBUG=1 $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 0 '*' remote # result + manifest
+ expect_not_contains test.o.*.ccache-log secret123
+ expect_contains test.o.*.ccache-log "status code: 401"
+fi
+ # -------------------------------------------------------------------------
+ TEST "IPv6 address"
+
+ if maybe_start_ipv6_http_server 12780 remote; then
+ export CCACHE_REMOTE_STORAGE="http://[::1]:12780"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 2 '*' remote # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_file_count 2 '*' remote # result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_file_count 2 '*' remote # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_file_count 2 '*' remote # result + manifest
+ fi
+}
--- /dev/null
+SUITE_remote_only_PROBE() {
+ if ! $RUN_WIN_XFAIL; then
+ echo "remote file is broken on windows."
+ fi
+}
+
+SUITE_remote_only_SETUP() {
+ unset CCACHE_NODIRECT
+ export CCACHE_REMOTE_STORAGE="file:$PWD/remote"
+ export CCACHE_REMOTE_ONLY=1
+
+ generate_code 1 test.c
+}
+
+SUITE_remote_only() {
+ # -------------------------------------------------------------------------
+ TEST "Base case"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 0
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 0
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 0
+ expect_stat local_storage_write 0
+ expect_stat remote_storage_hit 0
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 0
+ expect_stat remote_storage_read_miss 2
+ expect_stat remote_storage_write 2
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 0
+ expect_stat local_storage_hit 0
+ expect_stat local_storage_miss 0
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 0
+ expect_stat local_storage_write 0
+ expect_stat remote_storage_hit 1
+ expect_stat remote_storage_miss 1
+ expect_stat remote_storage_read_hit 2
+ expect_stat remote_storage_read_miss 2
+ expect_stat remote_storage_write 2
+ expect_stat files_in_cache 0
+ expect_file_count 3 '*' remote # CACHEDIR.TAG + result + manifest
+}
--- /dev/null
+SUITE_remote_redis_PROBE() {
+ if ! $CCACHE --version | fgrep -q -- redis-storage &> /dev/null; then
+ echo "redis-storage not available"
+ return
+ fi
+ if ! command -v redis-server &> /dev/null; then
+ echo "redis-server not found"
+ return
+ fi
+ if ! command -v redis-cli &> /dev/null; then
+ echo "redis-cli not found"
+ return
+ fi
+}
+
+start_redis_server() {
+ local port="$1"
+ local password="${2:-}"
+
+ redis-server --bind localhost --port "${port}" >/dev/null &
+ # Wait for server start.
+ i=0
+ while [ $i -lt 100 ] && ! redis-cli -p "${port}" ping &>/dev/null; do
+ sleep 0.1
+ i=$((i + 1))
+ done
+
+ if [ -n "${password}" ]; then
+ redis-cli -p "${port}" config set requirepass "${password}" &>/dev/null
+ fi
+}
+
+SUITE_remote_redis_SETUP() {
+ unset CCACHE_NODIRECT
+
+ generate_code 1 test.c
+}
+
+expect_number_of_redis_cache_entries() {
+ local expected=$1
+ local url=$2
+ local actual
+
+ actual=$(redis-cli -u "$url" keys "ccache:*" 2>/dev/null | wc -l)
+ if [ "$actual" -ne "$expected" ]; then
+ test_failed_internal "Found $actual (expected $expected) entries in $url"
+ fi
+}
+
+SUITE_remote_redis() {
+ # -------------------------------------------------------------------------
+ TEST "Base case"
+
+ port=7777
+ redis_url="redis://localhost:${port}"
+ export CCACHE_REMOTE_STORAGE="${redis_url}"
+
+ start_redis_server "${port}"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_number_of_redis_cache_entries 2 "$redis_url" # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_number_of_redis_cache_entries 2 "$redis_url" # result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_number_of_redis_cache_entries 2 "$redis_url" # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_number_of_redis_cache_entries 2 "$redis_url" # result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Password"
+
+ port=7777
+ password=secret123
+ redis_url="redis://${password}@localhost:${port}"
+ export CCACHE_REMOTE_STORAGE="${redis_url}"
+
+ start_redis_server "${port}" "${password}"
+
+ CCACHE_DEBUG=1 $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_number_of_redis_cache_entries 2 "$redis_url" # result + manifest
+ expect_not_contains test.o.*.ccache-log "${password}"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_number_of_redis_cache_entries 2 "$redis_url" # result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_number_of_redis_cache_entries 2 "$redis_url" # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_number_of_redis_cache_entries 2 "$redis_url" # result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Unreachable server"
+
+ export CCACHE_REMOTE_STORAGE="redis://localhost:1"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_stat remote_storage_error 1
+}
--- /dev/null
+SUITE_remote_redis_unix_PROBE() {
+ if ! $CCACHE --version | fgrep -q -- redis-storage &> /dev/null; then
+ echo "redis-storage not available"
+ return
+ fi
+ if ! command -v redis-server &> /dev/null; then
+ echo "redis-server not found"
+ return
+ fi
+ if redis-server --unixsocket /foo/redis.sock 2>&1 | grep -q "FATAL CONFIG FILE ERROR"; then
+ # "Bad directive or wrong number of arguments"
+ echo "redis-server without unixsocket"
+ return
+ fi
+ if ! command -v redis-cli &> /dev/null; then
+ echo "redis-cli not found"
+ return
+ fi
+ if ! redis-cli -s /foo/redis.sock --version &> /dev/null; then
+ # "Unrecognized option or bad number of args"
+ echo "redis-cli without socket"
+ return
+ fi
+}
+
+start_redis_unix_server() {
+ local socket="$1"
+ local password="${2:-}"
+
+ redis-server --bind localhost --unixsocket "${socket}" --port 0 >/dev/null &
+ # Wait for server start.
+ i=0
+ while [ $i -lt 100 ] && ! redis-cli -s "${socket}" ping &>/dev/null; do
+ sleep 0.1
+ i=$((i + 1))
+ done
+
+ if [ -n "${password}" ]; then
+ redis-cli -s "${socket}" config set requirepass "${password}" &>/dev/null
+ fi
+}
+
+SUITE_remote_redis_unix_SETUP() {
+ unset CCACHE_NODIRECT
+
+ generate_code 1 test.c
+}
+
+expect_number_of_redis_unix_cache_entries() {
+ local expected=$1
+ local socket=$2
+ local actual
+
+ actual=$(redis-cli -s "$socket" keys "ccache:*" 2>/dev/null | wc -l)
+ if [ "$actual" -ne "$expected" ]; then
+ test_failed_internal "Found $actual (expected $expected) entries in $socket"
+ fi
+}
+
+SUITE_remote_redis_unix() {
+ # -------------------------------------------------------------------------
+ TEST "Base case"
+
+ socket=$(mktemp)
+ redis_url="redis+unix:${socket}"
+ export CCACHE_REMOTE_STORAGE="${redis_url}"
+
+ start_redis_unix_server "${socket}"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_number_of_redis_unix_cache_entries 2 "${socket}" # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_number_of_redis_unix_cache_entries 2 "${socket}" # result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_number_of_redis_unix_cache_entries 2 "${socket}" # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_number_of_redis_unix_cache_entries 2 "${socket}" # result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Password"
+
+ socket=$(mktemp)
+ password=secret123
+ redis_url="redis+unix://${password}@localhost${socket}"
+ export CCACHE_REMOTE_STORAGE="${redis_url}"
+
+ start_redis_unix_server "${socket}" "${password}"
+
+ CCACHE_DEBUG=1 $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_number_of_redis_unix_cache_entries 2 "${socket}" # result + manifest
+ expect_not_contains test.o.*.ccache-log "${password}"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_number_of_redis_unix_cache_entries 2 "${socket}" # result + manifest
+
+ $CCACHE -C >/dev/null
+ expect_stat files_in_cache 0
+ expect_number_of_redis_unix_cache_entries 2 "${socket}" # result + manifest
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2 # fetched from remote
+ expect_number_of_redis_unix_cache_entries 2 "${socket}" # result + manifest
+
+ # -------------------------------------------------------------------------
+ TEST "Unreachable server"
+
+ export CCACHE_REMOTE_STORAGE="redis+unix:///foo"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_stat remote_storage_error 1
+}
--- /dev/null
+SUITE_remote_url_SETUP() {
+ generate_code 1 test.c
+}
+
+SUITE_remote_url() {
+ # -------------------------------------------------------------------------
+ TEST "Reject empty url (without config attributes)"
+
+ export CCACHE_REMOTE_STORAGE="|"
+ $CCACHE_COMPILE -c test.c 2>stderr.log
+ expect_contains stderr.log "must provide a URL"
+
+ # -------------------------------------------------------------------------
+ TEST "Reject empty url (but with config attributes)"
+
+ export CCACHE_REMOTE_STORAGE="|key=value"
+ $CCACHE_COMPILE -c test.c 2>stderr.log
+ expect_contains stderr.log "must provide a URL"
+
+ # -------------------------------------------------------------------------
+ TEST "Reject invalid url"
+
+ export CCACHE_REMOTE_STORAGE="://qwerty"
+ $CCACHE_COMPILE -c test.c 2>stderr.log
+ expect_contains stderr.log "Cannot parse URL"
+
+ # -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
+ TEST "Reject missing scheme"
+
+ export CCACHE_REMOTE_STORAGE="/qwerty"
+ $CCACHE_COMPILE -c test.c 2>stderr.log
+ expect_contains stderr.log "URL scheme must not be empty"
+
+ # -------------------------------------------------------------------------
+ TEST "Reject user info defined but no host"
+
+ export CCACHE_REMOTE_STORAGE="http://foo@"
+ $CCACHE_COMPILE -c test.c 2>stderr.log
+ expect_contains stderr.log "User info defined, but host is empty"
+
+ # -------------------------------------------------------------------------
+ TEST "Reject relative path with colon in first part"
+
+ export CCACHE_REMOTE_STORAGE="file:foo:bar"
+ $CCACHE_COMPILE -c test.c 2>stderr.log
+ expect_contains stderr.log "The first segment of the relative path can't contain ':'"
+fi
+}
SUITE_sanitize_blacklist_PROBE() {
touch test.c blacklist.txt
- if ! $REAL_COMPILER -c -fsanitize-blacklist=blacklist.txt \
+ if ! $COMPILER -c -fsanitize-blacklist=blacklist.txt \
test.c 2>/dev/null; then
echo "-fsanitize-blacklist not supported by compiler"
fi
# -------------------------------------------------------------------------
TEST "Compile OK"
- $REAL_COMPILER -c -fsanitize-blacklist=blacklist.txt test1.c
+ $COMPILER -c -fsanitize-blacklist=blacklist.txt test1.c
$CCACHE_COMPILE -c -fsanitize-blacklist=blacklist.txt test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
$CCACHE_COMPILE -c -fsanitize-blacklist=blacklist.txt test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
echo "fun:bar" >blacklist.txt
$CCACHE_COMPILE -c -fsanitize-blacklist=blacklist.txt test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
$CCACHE_COMPILE -c -fsanitize-blacklist=blacklist.txt test1.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
# -------------------------------------------------------------------------
TEST "Unsuccessful compilation"
- if $REAL_COMPILER -c -fsanitize-blacklist=nosuchfile.txt test1.c 2>expected.stderr; then
+ if $COMPILER -c -fsanitize-blacklist=nosuchfile.txt test1.c 2>expected.stderr; then
test_failed "Expected an error compiling test1.c"
fi
test_failed "Expected an error compiling test1.c"
fi
- expect_stat 'error hashing extra file' 1
+ expect_stat error_hashing_extra_file 1
# -------------------------------------------------------------------------
TEST "Multiple -fsanitize-blacklist"
- $REAL_COMPILER -c -fsanitize-blacklist=blacklist2.txt -fsanitize-blacklist=blacklist.txt test1.c
+ $COMPILER -c -fsanitize-blacklist=blacklist2.txt -fsanitize-blacklist=blacklist.txt test1.c
$CCACHE_COMPILE -c -fsanitize-blacklist=blacklist2.txt -fsanitize-blacklist=blacklist.txt test1.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
$CCACHE_COMPILE -c -fsanitize-blacklist=blacklist2.txt -fsanitize-blacklist=blacklist.txt test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
echo "fun_2:foo" >blacklist2.txt
$CCACHE_COMPILE -c -fsanitize-blacklist=blacklist2.txt -fsanitize-blacklist=blacklist.txt test1.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
$CCACHE_COMPILE -c -fsanitize-blacklist=blacklist2.txt -fsanitize-blacklist=blacklist.txt test1.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
}
SUITE_serialize_diagnostics_PROBE() {
touch test.c
- if ! $REAL_COMPILER -c --serialize-diagnostics \
+ if ! $COMPILER -c --serialize-diagnostics \
test1.dia test.c 2>/dev/null; then
echo "--serialize-diagnostics not supported by compiler"
fi
# -------------------------------------------------------------------------
TEST "Compile OK"
- $REAL_COMPILER -c --serialize-diagnostics expected.dia test1.c
+ $COMPILER -c --serialize-diagnostics expected.dia test1.c
$CCACHE_COMPILE -c --serialize-diagnostics test.dia test1.c
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content expected.dia test.dia
rm test.dia
$CCACHE_COMPILE -c --serialize-diagnostics test.dia test1.c
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
expect_equal_content expected.dia test.dia
# -------------------------------------------------------------------------
TEST "Unsuccessful compilation"
echo "bad source" >error.c
- if $REAL_COMPILER -c --serialize-diagnostics expected.dia error.c 2>expected.stderr; then
+ if $COMPILER -c --serialize-diagnostics expected.dia error.c 2>expected.stderr; then
test_failed "Expected an error compiling error.c"
fi
$CCACHE_COMPILE -c --serialize-diagnostics test.dia error.c 2>test.stderr
- expect_stat 'compile failed' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 0
- expect_stat 'files in cache' 0
+ expect_stat compile_failed 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat files_in_cache 0
expect_equal_content expected.dia test.dia
- expect_equal_content expected.stderr test.stderr
+ expect_equal_text_content expected.stderr test.stderr
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST "--serialize-diagnostics + CCACHE_BASEDIR"
mkdir -p dir1/src dir1/include
cd dir1
CCACHE_BASEDIR=`pwd` $CCACHE_COMPILE -w -MD -MF `pwd`/test.d -I`pwd`/include --serialize-diagnostics `pwd`/test.dia -c src/test.c -o `pwd`/test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
cd ../dir2
CCACHE_BASEDIR=`pwd` $CCACHE_COMPILE -w -MD -MF `pwd`/test.d -I`pwd`/include --serialize-diagnostics `pwd`/test.dia -c src/test.c -o `pwd`/test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+fi
}
SUITE_source_date_epoch_PROBE() {
echo 'char x[] = __DATE__;' >test.c
- if ! SOURCE_DATE_EPOCH=0 $REAL_COMPILER -E test.c | grep -q 1970; then
+ if ! SOURCE_DATE_EPOCH=0 $COMPILER -E test.c | grep -q 1970; then
echo "SOURCE_DATE_EPOCH not supported by compiler"
fi
}
unset CCACHE_NODIRECT
SOURCE_DATE_EPOCH=1 $CCACHE_COMPILE -c without_temporal_macros.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
SOURCE_DATE_EPOCH=1 $CCACHE_COMPILE -c without_temporal_macros.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
SOURCE_DATE_EPOCH=2 $CCACHE_COMPILE -c without_temporal_macros.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "With __DATE__ macro"
unset CCACHE_NODIRECT
SOURCE_DATE_EPOCH=1 $CCACHE_COMPILE -c with_date_macro.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
SOURCE_DATE_EPOCH=1 $CCACHE_COMPILE -c with_date_macro.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
SOURCE_DATE_EPOCH=2 $CCACHE_COMPILE -c with_date_macro.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
# -------------------------------------------------------------------------
TEST "With __TIME__ macro"
unset CCACHE_NODIRECT
SOURCE_DATE_EPOCH=1 $CCACHE_COMPILE -c with_time_macro.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
SOURCE_DATE_EPOCH=1 $CCACHE_COMPILE -c with_time_macro.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
SOURCE_DATE_EPOCH=2 $CCACHE_COMPILE -c with_time_macro.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "With __TIME__ and time_macros sloppiness"
unset CCACHE_NODIRECT
CCACHE_SLOPPINESS=time_macros SOURCE_DATE_EPOCH=1 $CCACHE_COMPILE -c with_time_macro.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS=time_macros SOURCE_DATE_EPOCH=1 $CCACHE_COMPILE -c with_time_macro.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
CCACHE_SLOPPINESS=time_macros SOURCE_DATE_EPOCH=2 $CCACHE_COMPILE -c with_time_macro.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
SOURCE_DATE_EPOCH=1 $CCACHE_COMPILE -c with_time_macro.c
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
}
SUITE_split_dwarf_PROBE() {
touch test.c
- if ! $REAL_COMPILER -c -gsplit-dwarf test.c 2>/dev/null || [ ! -e test.dwo ]; then
+ if ! $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"
cd dir1
$CCACHE_COMPILE -I$(pwd)/include -c src/test.c -gsplit-dwarf
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
$CCACHE_COMPILE -I$(pwd)/include -c src/test.c -gsplit-dwarf
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
cd ../dir2
$CCACHE_COMPILE -I$(pwd)/include -c src/test.c -gsplit-dwarf
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
$CCACHE_COMPILE -I$(pwd)/include -c src/test.c -gsplit-dwarf
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache miss' 2
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
# -------------------------------------------------------------------------
TEST "Output filename is hashed if using -gsplit-dwarf"
cd dir1
- $REAL_COMPILER -I$(pwd)/include -c src/test.c -o test.o -gsplit-dwarf
+ $COMPILER -I$(pwd)/include -c src/test.c -o test.o -gsplit-dwarf
mv test.o reference.o
mv test.dwo reference.dwo
- $REAL_COMPILER -I$(pwd)/include -c src/test.c -o test.o -gsplit-dwarf
+ $COMPILER -I$(pwd)/include -c src/test.c -o test.o -gsplit-dwarf
mv test.o reference2.o
mv test.dwo reference2.dwo
$CCACHE_COMPILE -I$(pwd)/include -c src/test.c -o test.o -gsplit-dwarf
expect_equal_object_files reference.o test.o
expect_equal_object_files reference.dwo test.dwo
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
$CCACHE_COMPILE -I$(pwd)/include -c src/test.c -o test.o -gsplit-dwarf
expect_equal_object_files reference.o test.o
expect_equal_object_files reference.dwo test.dwo
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
- $REAL_COMPILER -I$(pwd)/include -c src/test.c -o test2.o -gsplit-dwarf
+ $COMPILER -I$(pwd)/include -c src/test.c -o test2.o -gsplit-dwarf
mv test2.o reference2.o
mv test2.dwo reference2.dwo
$CCACHE_COMPILE -I$(pwd)/include -c src/test.c -o test2.o -gsplit-dwarf
expect_equal_object_files reference2.o test2.o
expect_equal_object_files reference2.dwo test2.dwo
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
$CCACHE_COMPILE -I$(pwd)/include -c src/test.c -o test2.o -gsplit-dwarf
expect_equal_object_files reference2.o test2.o
expect_equal_object_files reference2.dwo test2.dwo
- expect_stat 'cache hit (direct)' 2
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 2
- expect_stat 'files in cache' 4
+ expect_stat direct_cache_hit 2
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
fi
# Else: Compiler does not produce stable object file output when compiling
# the same source to the same output filename twice (DW_AT_GNU_dwo_id
# differs), so we can't verify filename hashing.
# -------------------------------------------------------------------------
+if $RUN_WIN_XFAIL; then
TEST "-fdebug-prefix-map and -gsplit-dwarf"
cd dir1
CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE -I$(pwd)/include -gsplit-dwarf -fdebug-prefix-map=$(pwd)=. -c $(pwd)/src/test.c -o $(pwd)/test.o
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
- expect_objdump_not_contains test.o "$(pwd)"
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_objdump_not_contains test.o "$(pwd)" 2>/dev/null
cd ../dir2
CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE -I$(pwd)/include -gsplit-dwarf -fdebug-prefix-map=$(pwd)=. -c $(pwd)/src/test.c -o $(pwd)/test.o
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
- expect_stat 'files in cache' 2
- expect_objdump_not_contains test.o "$(pwd)"
-
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_objdump_not_contains test.o "$(pwd)" 2>/dev/null
+fi
# -------------------------------------------------------------------------
TEST "-gsplit-dwarf -g1"
# "gcc -gsplit-dwarf -g1" produces a .dwo file, but "clang -gsplit-dwarf
# -g1" doesn't, so test that ccache handles it gracefully either way.
- $REAL_COMPILER -gsplit-dwarf -g1 -c test.c -o reference.o
+ $COMPILER -gsplit-dwarf -g1 -c test.c -o reference.o
$CCACHE_COMPILE -gsplit-dwarf -g1 -c test.c
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
rm -f test.dwo
$CCACHE_COMPILE -gsplit-dwarf -g1 -c test.c
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
if [ -f reference.dwo ] && [ ! -f test.dwo ]; then
test_failed ".dwo missing"
TEST "Object file without dot"
$CCACHE_COMPILE -gsplit-dwarf -c test.c -o test
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists test.dwo
rm test.dwo
$CCACHE_COMPILE -gsplit-dwarf -c test.c -o test
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists test.dwo
# -------------------------------------------------------------------------
TEST "Object file with two dots"
$CCACHE_COMPILE -gsplit-dwarf -c test.c -o test.x.y
- expect_stat 'cache hit (direct)' 0
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists test.x.dwo
rm test.x.dwo
$CCACHE_COMPILE -gsplit-dwarf -c test.c -o test.x.y
- expect_stat 'cache hit (direct)' 1
- expect_stat 'cache hit (preprocessed)' 0
- expect_stat 'cache miss' 1
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
expect_exists test.x.dwo
}
--- /dev/null
+SUITE_stats_log_SETUP() {
+ generate_code 1 test.c
+ unset CCACHE_NODIRECT
+ export CCACHE_STATSLOG=stats.log
+}
+
+SUITE_stats_log() {
+ # -------------------------------------------------------------------------
+ TEST "CCACHE_STATSLOG"
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_miss 1
+ expect_stat cache_miss 1
+ expect_stat local_storage_read_hit 0
+ expect_stat local_storage_read_miss 2
+
+ $CCACHE_COMPILE -c test.c
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_miss 1
+ expect_stat cache_miss 1
+ expect_stat local_storage_read_hit 2
+ expect_stat local_storage_read_miss 2
+
+ expect_content stats.log "# test.c
+cache_miss
+direct_cache_miss
+local_storage_miss
+local_storage_read_miss
+local_storage_read_miss
+local_storage_write
+local_storage_write
+preprocessed_cache_miss
+# test.c
+direct_cache_hit
+local_storage_hit
+local_storage_read_hit
+local_storage_read_hit"
+}
--- /dev/null
+SUITE_trim_dir_PROBE() {
+ if [ -z "$ENABLE_CACHE_CLEANUP_TESTS" ]; then
+ echo "cleanup tests disabled"
+ fi
+}
+
+SUITE_trim_dir() {
+ # -------------------------------------------------------------------------
+ TEST "Trim remote cache directory"
+
+ if $HOST_OS_APPLE; then
+ one_mb=1m
+ else
+ one_mb=1M
+ fi
+ for subdir in aa bb cc; do
+ mkdir -p remote/$subdir
+ dd if=/dev/zero of=remote/$subdir/1 count=1 bs=$one_mb 2>/dev/null
+ dd if=/dev/zero of=remote/$subdir/2 count=1 bs=$one_mb 2>/dev/null
+ done
+
+ backdate remote/bb/2 remote/cc/1
+ $CCACHE --trim-dir remote --trim-max-size 4.5M --trim-method mtime \
+ >/dev/null
+
+ expect_exists remote/aa/1
+ expect_exists remote/aa/2
+ expect_exists remote/bb/1
+ expect_missing remote/bb/2
+ expect_missing remote/cc/1
+ expect_exists remote/cc/2
+
+ # -------------------------------------------------------------------------
+ TEST "Trim local cache directory"
+
+ mkdir -p local/0
+ touch local/0/stats
+ if $CCACHE --trim-dir local --trim-max-size 0 &>/dev/null; then
+ test_failed "Expected failure"
+ fi
+
+ rm -rf local
+ mkdir local
+ touch local/ccache.conf
+ if $CCACHE --trim-dir local --trim-max-size 0 &>/dev/null; then
+ test_failed "Expected failure"
+ fi
+}
+SUITE_upgrade_PROBE() {
+ if ! $RUN_WIN_XFAIL; then
+ echo "upgrade tests are broken on Windows. (mix between windows and posix path)"
+ return
+ fi
+}
+
SUITE_upgrade() {
# -------------------------------------------------------------------------
TEST "Default cache config/directory without XDG variables"
else
expected=$HOME/.cache/ccache
fi
- actual=$($CCACHE -s | sed -n 's/^cache directory *//p')
+ actual=$($CCACHE -k cache_dir)
if [ "$actual" != "$expected" ]; then
test_failed "expected cache directory $expected, actual $actual"
fi
else
expected=$HOME/.config/ccache/ccache.conf
fi
- actual=$($CCACHE -s | sed -n 's/^primary config *//p')
+ actual=$($CCACHE -sv | sed -n 's/^Config file: *//p')
if [ "$actual" != "$expected" ]; then
- test_failed "expected primary config $expected actual $actual"
+ test_failed "expected config $expected, actual $actual"
fi
# -------------------------------------------------------------------------
export XDG_CONFIG_HOME=/elsewhere/config
expected=$XDG_CACHE_HOME/ccache
- actual=$($CCACHE -s | sed -n 's/^cache directory *//p')
+ actual=$($CCACHE -k cache_dir)
if [ "$actual" != "$expected" ]; then
test_failed "expected cache directory $expected, actual $actual"
fi
expected=$XDG_CONFIG_HOME/ccache/ccache.conf
- actual=$($CCACHE -s | sed -n 's/^primary config *//p')
+ actual=$($CCACHE -sv | sed -n 's/^Config file: *//p')
if [ "$actual" != "$expected" ]; then
- test_failed "expected primary config $expected actual $actual"
+ test_failed "expected config $expected, actual $actual"
fi
# -------------------------------------------------------------------------
mkdir $HOME/.ccache
expected=$HOME/.ccache
- actual=$($CCACHE -s | sed -n 's/^cache directory *//p')
+ actual=$($CCACHE -k cache_dir)
if [ "$actual" != "$expected" ]; then
test_failed "expected cache directory $expected, actual $actual"
fi
expected=$HOME/.ccache/ccache.conf
- actual=$($CCACHE -s | sed -n 's/^primary config *//p')
+ actual=$($CCACHE -sv | sed -n 's/^Config file: *//p')
if [ "$actual" != "$expected" ]; then
- test_failed "expected primary config $expected actual $actual"
+ test_failed "expected config $expected, actual $actual"
fi
# -------------------------------------------------------------------------
export XDG_CONFIG_HOME=/elsewhere/config
expected=$CCACHE_DIR
- actual=$($CCACHE -s | sed -n 's/^cache directory *//p')
+ actual=$($CCACHE -k cache_dir)
if [ "$actual" != "$expected" ]; then
test_failed "expected cache directory $expected, actual $actual"
fi
expected=$CCACHE_DIR/ccache.conf
- actual=$($CCACHE -s | sed -n 's/^primary config *//p')
+ actual=$($CCACHE -sv | sed -n 's/^Config file: *//p')
if [ "$actual" != "$expected" ]; then
- test_failed "expected primary config $expected actual $actual"
+ test_failed "expected config $expected, actual $actual"
fi
# -------------------------------------------------------------------------
TEST "Cache config/directory with empty CCACHE_DIR"
# Empty (but set) CCACHE_DIR means "use defaults" and should thus override
- # cache_dir set in the secondary config.
+ # cache_dir set in the system config.
unset CCACHE_CONFIGPATH
export CCACHE_CONFIGPATH2=$PWD/ccache.conf2
echo 'cache_dir = /nowhere' > $CCACHE_CONFIGPATH2
expected=$XDG_CACHE_HOME/ccache
- actual=$($CCACHE -s | sed -n 's/^cache directory *//p')
+ actual=$($CCACHE -k cache_dir)
if [ "$actual" != "$expected" ]; then
test_failed "expected cache directory $expected, actual $actual"
fi
expected=$XDG_CONFIG_HOME/ccache/ccache.conf
- actual=$($CCACHE -s | sed -n 's/^primary config *//p')
+ actual=$($CCACHE -sv | sed -n 's/^Config file: *//p')
if [ "$actual" != "$expected" ]; then
- test_failed "expected primary config $expected actual $actual"
+ test_failed "expected config $expected, actual $actual"
fi
}
---
-Checks: '-*,readability-function-size'
+Checks: '-*,
+ readability-*,
+ -readability-implicit-bool-conversion,
+ -readability-function-cognitive-complexity,
+ -readability-magic-numbers,
+ -readability-else-after-return,
+ -readability-named-parameter,
+ -readability-qualified-auto,
+ -readability-redundant-declaration'
WarningsAsErrors: '*'
# Only include headers directly in unittest.
HeaderFilterRegex: 'unittest/[^/]*$'
- key: readability-braces-around-statements.ShortStatementLines
value: 0
- # If you hit the limits, please change the code and not the limits!!
- # Note: some limits "disabled" due to TEST_SUITE macro.
- # The macro generates hundreds of statements, branches and variables.
+ # If you hit a limit, please consider changing the code instead of the limit.
- key: readability-function-size.LineThreshold
- value: 130
+ value: 999999
- key: readability-function-size.StatementThreshold
value: 999999
- key: readability-function-size.ParameterThreshold
main.cpp
test_Args.cpp
test_AtomicFile.cpp
- test_Checksum.cpp
- test_Compression.cpp
test_Config.cpp
- test_Counters.cpp
test_Depfile.cpp
- test_FormatNonstdStringView.cpp
test_Hash.cpp
- test_Lockfile.cpp
- test_NullCompression.cpp
test_Stat.cpp
- test_Statistics.cpp
test_Util.cpp
- test_ZstdCompression.cpp
test_argprocessing.cpp
test_ccache.cpp
test_compopt.cpp
- test_hashutil.cpp)
+ test_compression_types.cpp
+ test_core_MsvcShowIncludesOutput.cpp
+ test_core_Statistics.cpp
+ test_core_StatisticsCounters.cpp
+ test_core_StatsLog.cpp
+ test_hashutil.cpp
+ test_storage_local_StatsFile.cpp
+ test_storage_local_util.cpp
+ test_util_Bytes.cpp
+ test_util_Duration.cpp
+ test_util_LockFile.cpp
+ test_util_TextTable.cpp
+ test_util_TimePoint.cpp
+ test_util_Tokenizer.cpp
+ test_util_XXH3_128.cpp
+ test_util_XXH3_64.cpp
+ test_util_expected.cpp
+ test_util_file.cpp
+ test_util_path.cpp
+ test_util_string.cpp
+ test_util_zstd.cpp
+)
if(INODE_CACHE_SUPPORTED)
list(APPEND source_files test_InodeCache.cpp)
list(APPEND source_files test_bsdmkstemp.cpp test_Win32Util.cpp)
endif()
+file(GLOB headers *.hpp)
+list(APPEND source_files ${headers})
+
add_executable(unittest ${source_files})
+if(MSVC)
+ # Turn off /Zc:preprocessor for this test because it triggers a bug in some older Windows 10 SDK headers.
+ set_source_files_properties(test_Stat.cpp PROPERTIES COMPILE_FLAGS /Zc:preprocessor-)
+endif()
+
target_link_libraries(
unittest
- PRIVATE standard_settings standard_warnings ccache_lib third_party_lib)
+ PRIVATE standard_settings standard_warnings ccache_framework third_party)
target_include_directories(unittest PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${ccache_SOURCE_DIR}/src)
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "TestUtil.hpp"
#include "../src/Util.hpp"
-#include "../src/exceptions.hpp"
-#include "../src/fmtmacros.hpp"
+
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <fmtmacros.hpp>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
namespace TestUtil {
TestContext::TestContext() : m_test_dir(Util::get_actual_cwd())
{
if (Util::base_name(Util::dir_name(m_test_dir)) != "testdir") {
- throw Error("TestContext instantiated outside test directory");
+ throw core::Error("TestContext instantiated outside test directory");
}
++m_subdir_counter;
std::string subtest_dir = FMT("{}/test_{}", m_test_dir, m_subdir_counter);
check_chdir(const std::string& dir)
{
if (chdir(dir.c_str()) != 0) {
- throw Error("failed to change directory to {}: {}", dir, strerror(errno));
+ throw core::Error(
+ FMT("failed to change directory to {}: {}", dir, strerror(errno)));
}
}
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#pragma once
-#include "system.hpp"
-
+#include <cstddef>
#include <string>
#ifdef _MSC_VER
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "../src/Args.hpp"
#include "TestUtil.hpp"
+#include <util/file.hpp>
+
#include "third_party/doctest.h"
TEST_SUITE_BEGIN("Args");
CHECK(args[3] == "f");
}
-TEST_CASE("Args::from_gcc_atfile")
+TEST_CASE("Args::from_atfile")
{
TestContext test_context;
SUBCASE("Nonexistent file")
{
- CHECK(Args::from_gcc_atfile("at_file") == nonstd::nullopt);
+ CHECK(Args::from_atfile("at_file") == std::nullopt);
}
SUBCASE("Empty")
{
- Util::write_file("at_file", "");
- args = *Args::from_gcc_atfile("at_file");
+ util::write_file("at_file", "");
+ args = *Args::from_atfile("at_file");
CHECK(args.size() == 0);
}
SUBCASE("One argument without newline")
{
- Util::write_file("at_file", "foo");
- args = *Args::from_gcc_atfile("at_file");
+ util::write_file("at_file", "foo");
+ args = *Args::from_atfile("at_file");
CHECK(args.size() == 1);
CHECK(args[0] == "foo");
}
SUBCASE("One argument with newline")
{
- Util::write_file("at_file", "foo\n");
- args = *Args::from_gcc_atfile("at_file");
+ util::write_file("at_file", "foo\n");
+ args = *Args::from_atfile("at_file");
CHECK(args.size() == 1);
CHECK(args[0] == "foo");
}
SUBCASE("Multiple simple arguments")
{
- Util::write_file("at_file", "x y z\n");
- args = *Args::from_gcc_atfile("at_file");
+ util::write_file("at_file", "x y z\n");
+ args = *Args::from_atfile("at_file");
CHECK(args.size() == 3);
CHECK(args[0] == "x");
CHECK(args[1] == "y");
SUBCASE("Tricky quoting")
{
- Util::write_file(
+ util::write_file(
"at_file",
"first\rsec\\\tond\tthi\\\\rd\nfourth \tfif\\ th \"si'x\\\" th\""
" 'seve\nth'\\");
- args = *Args::from_gcc_atfile("at_file");
+ args = *Args::from_atfile("at_file");
CHECK(args.size() == 7);
CHECK(args[0] == "first");
CHECK(args[1] == "sec\tond");
CHECK(args[5] == "si'x\" th");
CHECK(args[6] == "seve\nth");
}
+
+ SUBCASE("Only escape double quote and backslash in alternate format")
+ {
+ util::write_file("at_file", "\"\\\"\\a\\ \\\\\\ \\b\\\"\"\\");
+ args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
+ CHECK(args.size() == 1);
+ CHECK(args[0] == "\"\\a\\ \\\\ \\b\"\\");
+ }
+
+ SUBCASE("Ignore single quote in alternate format")
+ {
+ util::write_file("at_file", "'a b'");
+ args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
+ CHECK(args.size() == 2);
+ CHECK(args[0] == "'a");
+ CHECK(args[1] == "b'");
+ }
}
TEST_CASE("Args copy assignment operator")
-// Copyright (C) 2011-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2011-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "../src/AtomicFile.hpp"
-#include "../src/Util.hpp"
#include "TestUtil.hpp"
+#include <Stat.hpp>
+#include <util/file.hpp>
+
#include "third_party/doctest.h"
using TestUtil::TestContext;
atomic_file.write(std::vector<uint8_t>{0x65, 0x6c});
fputs("lo", atomic_file.stream());
atomic_file.commit();
- CHECK(Util::read_file("test") == "hello");
+ CHECK(*util::read_file<std::string>("test") == "hello");
}
TEST_CASE("Not committing")
AtomicFile atomic_file("test", AtomicFile::Mode::text);
atomic_file.write("hello");
}
- CHECK_THROWS_WITH(Util::read_file("test"), "No such file or directory");
+ CHECK(!Stat::stat("test"));
}
TEST_SUITE_END();
+++ /dev/null
-// Copyright (C) 2011-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/Checksum.hpp"
-
-#include "third_party/doctest.h"
-
-TEST_SUITE_BEGIN("Checksum");
-
-TEST_CASE("Checksums")
-{
- Checksum checksum;
- CHECK(checksum.digest() == 0x2d06800538d394c2);
-
- checksum.update("foo", 3);
- CHECK(checksum.digest() == 0xab6e5f64077e7d8a);
-
- checksum.update("t", 1);
- CHECK(checksum.digest() == 0x3fd918aed1a9e7e4);
-
- checksum.reset();
- CHECK(checksum.digest() == 0x2d06800538d394c2);
-}
-
-TEST_SUITE_END();
+++ /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
-
-#include "../src/Compression.hpp"
-#include "../src/Config.hpp"
-
-#include "third_party/doctest.h"
-
-TEST_SUITE_BEGIN("Compression");
-
-TEST_CASE("Compression::level_from_config")
-{
- Config config;
- CHECK(Compression::level_from_config(config) == 0);
-}
-
-TEST_CASE("Compression::type_from_config")
-{
- Config config;
- CHECK(Compression::type_from_config(config) == Compression::Type::zstd);
-}
-
-TEST_CASE("Compression::type_from_int")
-{
- CHECK(Compression::type_from_int(0) == Compression::Type::none);
- CHECK(Compression::type_from_int(1) == Compression::Type::zstd);
- CHECK_THROWS_WITH(Compression::type_from_int(2), "Unknown type: 2");
-}
-
-TEST_CASE("Compression::type_to_string")
-{
- CHECK(Compression::type_to_string(Compression::Type::none) == "none");
- CHECK(Compression::type_to_string(Compression::Type::zstd) == "zstd");
-}
-
-TEST_SUITE_END();
-// Copyright (C) 2011-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2011-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "../src/Config.hpp"
-#include "../src/Sloppiness.hpp"
#include "../src/Util.hpp"
-#include "../src/exceptions.hpp"
#include "../src/fmtmacros.hpp"
#include "TestUtil.hpp"
+#include <core/exceptions.hpp>
+#include <util/file.hpp>
+
#include "third_party/doctest.h"
#include "third_party/fmt/core.h"
CHECK(config.log_file().empty());
CHECK(config.max_files() == 0);
CHECK(config.max_size() == static_cast<uint64_t>(5) * 1000 * 1000 * 1000);
+ CHECK(config.msvc_dep_prefix() == "Note: including file:");
CHECK(config.path().empty());
CHECK_FALSE(config.pch_external_checksum());
CHECK(config.prefix_command().empty());
CHECK_FALSE(config.read_only());
CHECK_FALSE(config.read_only_direct());
CHECK_FALSE(config.recache());
+ CHECK_FALSE(config.remote_only());
+ CHECK(config.remote_storage().empty());
+ CHECK_FALSE(config.reshare());
CHECK(config.run_second_cpp());
- CHECK(config.sloppiness() == 0);
+ CHECK(config.sloppiness().to_bitmask() == 0);
CHECK(config.stats());
CHECK(config.temporary_dir().empty()); // Set later
- CHECK(config.umask() == std::numeric_limits<uint32_t>::max());
+ CHECK(config.umask() == std::nullopt);
}
TEST_CASE("Config::update_from_file")
std::string base_dir = FMT("C:/{0}/foo/{0}", user);
#endif
- Util::write_file(
+ util::write_file(
"ccache.conf",
"base_dir = " + base_dir + "\n"
"cache_dir=\n"
" #A comment\n"
"\t compiler = foo\n"
"compiler_check = none\n"
- "compiler_type = pump\n"
+ "compiler_type = nvcc\n"
"compression=false\n"
"compression_level= 2\n"
"cpp_extension = .foo\n"
"log_file = $USER${USER} \n"
"max_files = 17\n"
"max_size = 123M\n"
+ "msvc_dep_prefix = Some other prefix:\n"
"path = $USER.x\n"
"pch_external_checksum = true\n"
"prefix_command = x$USER\n"
"read_only = true\n"
"read_only_direct = true\n"
"recache = true\n"
+ "reshare = true\n"
"run_second_cpp = false\n"
"sloppiness = time_macros ,include_file_mtime"
" include_file_ctime,file_stat_matches,file_stat_matches_ctime,pch_defines"
- " , no_system_headers,system_headers,clang_index_store,ivfsoverlay\n"
+ " , no_system_headers,system_headers,clang_index_store,ivfsoverlay,"
+ " gcno_cwd,\n"
"stats = false\n"
"temporary_dir = ${USER}_foo\n"
"umask = 777"); // Note: no newline.
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(config.compiler_type() == CompilerType::nvcc);
CHECK_FALSE(config.compression());
CHECK(config.compression_level() == 2);
CHECK(config.cpp_extension() == ".foo");
CHECK(config.log_file() == FMT("{0}{0}", user));
CHECK(config.max_files() == 17);
CHECK(config.max_size() == 123 * 1000 * 1000);
+ CHECK(config.msvc_dep_prefix() == "Some other prefix:");
CHECK(config.path() == FMT("{}.x", user));
CHECK(config.pch_external_checksum());
CHECK(config.prefix_command() == FMT("x{}", user));
CHECK(config.read_only());
CHECK(config.read_only_direct());
CHECK(config.recache());
+ CHECK(config.reshare());
CHECK_FALSE(config.run_second_cpp());
- CHECK(config.sloppiness()
- == (SLOPPY_INCLUDE_FILE_MTIME | SLOPPY_INCLUDE_FILE_CTIME
- | SLOPPY_TIME_MACROS | SLOPPY_FILE_STAT_MATCHES
- | SLOPPY_FILE_STAT_MATCHES_CTIME | SLOPPY_SYSTEM_HEADERS
- | SLOPPY_PCH_DEFINES | SLOPPY_CLANG_INDEX_STORE
- | SLOPPY_IVFSOVERLAY));
+ CHECK(config.sloppiness().to_bitmask()
+ == (static_cast<uint32_t>(core::Sloppy::clang_index_store)
+ | static_cast<uint32_t>(core::Sloppy::file_stat_matches)
+ | static_cast<uint32_t>(core::Sloppy::file_stat_matches_ctime)
+ | static_cast<uint32_t>(core::Sloppy::gcno_cwd)
+ | static_cast<uint32_t>(core::Sloppy::include_file_ctime)
+ | static_cast<uint32_t>(core::Sloppy::include_file_mtime)
+ | static_cast<uint32_t>(core::Sloppy::ivfsoverlay)
+ | static_cast<uint32_t>(core::Sloppy::pch_defines)
+ | static_cast<uint32_t>(core::Sloppy::system_headers)
+ | static_cast<uint32_t>(core::Sloppy::time_macros)));
CHECK_FALSE(config.stats());
CHECK(config.temporary_dir() == FMT("{}_foo", user));
- CHECK(config.umask() == 0777);
+ CHECK(config.umask() == 0777U);
}
TEST_CASE("Config::update_from_file, error handling")
SUBCASE("missing equal sign")
{
- Util::write_file("ccache.conf", "no equal sign");
+ util::write_file("ccache.conf", "no equal sign");
REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
"ccache.conf:1: missing equal sign");
}
SUBCASE("unknown key")
{
- Util::write_file("ccache.conf", "# Comment\nfoo = bar");
+ util::write_file("ccache.conf", "# Comment\nfoo = bar");
CHECK(config.update_from_file("ccache.conf"));
}
SUBCASE("invalid bool")
{
- Util::write_file("ccache.conf", "disable=");
+ util::write_file("ccache.conf", "disable=");
REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
"ccache.conf:1: not a boolean value: \"\"");
- Util::write_file("ccache.conf", "disable=foo");
+ util::write_file("ccache.conf", "disable=foo");
REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
"ccache.conf:1: not a boolean value: \"foo\"");
}
SUBCASE("invalid variable reference")
{
- Util::write_file("ccache.conf", "base_dir = ${foo");
+ util::write_file("ccache.conf", "base_dir = ${foo");
REQUIRE_THROWS_WITH(
config.update_from_file("ccache.conf"),
"ccache.conf:1: syntax error: missing '}' after \"foo\"");
SUBCASE("empty umask")
{
- Util::write_file("ccache.conf", "umask = ");
+ util::write_file("ccache.conf", "umask = ");
CHECK(config.update_from_file("ccache.conf"));
- CHECK(config.umask() == std::numeric_limits<uint32_t>::max());
+ CHECK(config.umask() == std::nullopt);
}
SUBCASE("invalid size")
{
- Util::write_file("ccache.conf", "max_size = foo");
+ util::write_file("ccache.conf", "max_size = foo");
REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
"ccache.conf:1: invalid size: \"foo\"");
// Other cases tested in test_Util.c.
SUBCASE("unknown sloppiness")
{
- Util::write_file("ccache.conf", "sloppiness = time_macros, foo");
+ util::write_file("ccache.conf", "sloppiness = time_macros, foo");
CHECK(config.update_from_file("ccache.conf"));
- CHECK(config.sloppiness() == SLOPPY_TIME_MACROS);
+ CHECK(config.sloppiness().to_bitmask()
+ == static_cast<uint32_t>(core::Sloppy::time_macros));
}
SUBCASE("invalid unsigned")
{
- Util::write_file("ccache.conf", "max_files =");
+ util::write_file("ccache.conf", "max_files =");
REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
"ccache.conf:1: invalid unsigned integer: \"\"");
- Util::write_file("ccache.conf", "max_files = -42");
+ util::write_file("ccache.conf", "max_files = -42");
REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
"ccache.conf:1: invalid unsigned integer: \"-42\"");
- Util::write_file("ccache.conf", "max_files = foo");
+ util::write_file("ccache.conf", "max_files = foo");
REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
"ccache.conf:1: invalid unsigned integer: \"foo\"");
}
SUBCASE("relative base dir")
{
- Util::write_file("ccache.conf", "base_dir = relative/path");
+ util::write_file("ccache.conf", "base_dir = relative/path");
REQUIRE_THROWS_WITH(
config.update_from_file("ccache.conf"),
"ccache.conf:1: not an absolute path: \"relative/path\"");
- Util::write_file("ccache.conf", "base_dir =");
+ util::write_file("ccache.conf", "base_dir =");
CHECK(config.update_from_file("ccache.conf"));
}
}
TEST_CASE("Config::set_value_in_file")
{
TestContext test_context;
+ Config config;
SUBCASE("set new value")
{
- Util::write_file("ccache.conf", "path = vanilla\n");
- Config::set_value_in_file("ccache.conf", "compiler", "chocolate");
- std::string content = Util::read_file("ccache.conf");
+ util::write_file("ccache.conf", "path = vanilla\n");
+ config.set_value_in_file("ccache.conf", "compiler", "chocolate");
+ std::string content = *util::read_file<std::string>("ccache.conf");
CHECK(content == "path = vanilla\ncompiler = chocolate\n");
}
SUBCASE("existing value")
{
- Util::write_file("ccache.conf", "path = chocolate\nstats = chocolate\n");
- Config::set_value_in_file("ccache.conf", "path", "vanilla");
- std::string content = Util::read_file("ccache.conf");
+ util::write_file("ccache.conf", "path = chocolate\nstats = chocolate\n");
+ config.set_value_in_file("ccache.conf", "path", "vanilla");
+ std::string content = *util::read_file<std::string>("ccache.conf");
CHECK(content == "path = vanilla\nstats = chocolate\n");
}
SUBCASE("unknown option")
{
- Util::write_file("ccache.conf", "path = chocolate\nstats = chocolate\n");
+ util::write_file("ccache.conf", "path = chocolate\nstats = chocolate\n");
try {
- Config::set_value_in_file("ccache.conf", "foo", "bar");
+ config.set_value_in_file("ccache.conf", "foo", "bar");
CHECK(false);
- } catch (const Error& e) {
+ } catch (const core::Error& e) {
CHECK(std::string(e.what()) == "unknown configuration option \"foo\"");
}
- std::string content = Util::read_file("ccache.conf");
+ std::string content = *util::read_file<std::string>("ccache.conf");
CHECK(content == "path = chocolate\nstats = chocolate\n");
}
SUBCASE("unknown sloppiness")
{
- Util::write_file("ccache.conf", "path = vanilla\n");
- Config::set_value_in_file("ccache.conf", "sloppiness", "foo");
- std::string content = Util::read_file("ccache.conf");
+ util::write_file("ccache.conf", "path = vanilla\n");
+ config.set_value_in_file("ccache.conf", "sloppiness", "foo");
+ std::string content = *util::read_file<std::string>("ccache.conf");
CHECK(content == "path = vanilla\nsloppiness = foo\n");
}
SUBCASE("comments are kept")
{
- Util::write_file("ccache.conf", "# c1\npath = blueberry\n#c2\n");
- Config::set_value_in_file("ccache.conf", "path", "vanilla");
- Config::set_value_in_file("ccache.conf", "compiler", "chocolate");
- std::string content = Util::read_file("ccache.conf");
+ util::write_file("ccache.conf", "# c1\npath = blueberry\n#c2\n");
+ config.set_value_in_file("ccache.conf", "path", "vanilla");
+ config.set_value_in_file("ccache.conf", "compiler", "chocolate");
+ std::string content = *util::read_file<std::string>("ccache.conf");
CHECK(content == "# c1\npath = vanilla\n#c2\ncompiler = chocolate\n");
}
}
try {
config.get_string_value("foo");
CHECK(false);
- } catch (const Error& e) {
+ } catch (const core::Error& e) {
CHECK(std::string(e.what()) == "unknown configuration option \"foo\"");
}
}
{
TestContext test_context;
- Util::write_file(
+ util::write_file(
"test.conf",
"absolute_paths_in_stderr = true\n"
#ifndef _WIN32
"log_file = lf\n"
"max_files = 4711\n"
"max_size = 98.7M\n"
+ "msvc_dep_prefix = mdp\n"
+ "namespace = ns\n"
"path = p\n"
"pch_external_checksum = true\n"
"prefix_command = pc\n"
"read_only = true\n"
"read_only_direct = true\n"
"recache = true\n"
+ "remote_only = true\n"
+ "remote_storage = rs\n"
+ "reshare = true\n"
"run_second_cpp = false\n"
"sloppiness = include_file_mtime, include_file_ctime, time_macros,"
" file_stat_matches, file_stat_matches_ctime, pch_defines, system_headers,"
- " clang_index_store, ivfsoverlay\n"
+ " clang_index_store, ivfsoverlay, gcno_cwd \n"
"stats = false\n"
+ "stats_log = sl\n"
"temporary_dir = td\n"
"umask = 022\n");
std::vector<std::string> received_items;
- config.visit_items([&](const std::string& key,
- const std::string& value,
- const std::string& origin) {
- received_items.push_back(FMT("({}) {} = {}", origin, key, value));
- });
+ config.visit_items(
+ [&](const auto& key, const auto& value, const auto& origin) {
+ received_items.push_back(FMT("({}) {} = {}", origin, key, value));
+ });
std::vector<std::string> expected = {
"(test.conf) absolute_paths_in_stderr = true",
"(test.conf) log_file = lf",
"(test.conf) max_files = 4711",
"(test.conf) max_size = 98.7M",
+ "(test.conf) msvc_dep_prefix = mdp",
+ "(test.conf) namespace = ns",
"(test.conf) path = p",
"(test.conf) pch_external_checksum = true",
"(test.conf) prefix_command = pc",
"(test.conf) read_only = true",
"(test.conf) read_only_direct = true",
"(test.conf) recache = true",
+ "(test.conf) remote_only = true",
+ "(test.conf) remote_storage = rs",
+ "(test.conf) reshare = true",
"(test.conf) run_second_cpp = false",
- "(test.conf) sloppiness = include_file_mtime, include_file_ctime,"
- " time_macros, pch_defines, file_stat_matches, file_stat_matches_ctime,"
- " system_headers, clang_index_store, ivfsoverlay",
+ "(test.conf) sloppiness = clang_index_store, file_stat_matches,"
+ " file_stat_matches_ctime, gcno_cwd, include_file_ctime,"
+ " include_file_mtime, ivfsoverlay, pch_defines, system_headers,"
+ " time_macros",
"(test.conf) stats = false",
+ "(test.conf) stats_log = sl",
"(test.conf) temporary_dir = td",
"(test.conf) umask = 022",
};
+++ /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/Counters.hpp"
-#include "../src/Statistic.hpp"
-#include "TestUtil.hpp"
-
-#include "third_party/doctest.h"
-
-using TestUtil::TestContext;
-
-TEST_SUITE_BEGIN("Counters");
-
-TEST_CASE("Counters")
-{
- TestContext test_context;
-
- Counters counters;
- CHECK(counters.size() == static_cast<size_t>(Statistic::END));
-
- SUBCASE("Get and set statistic")
- {
- CHECK(counters.get(Statistic::cache_miss) == 0);
- counters.set(Statistic::cache_miss, 27);
- CHECK(counters.get(Statistic::cache_miss) == 27);
- }
-
- SUBCASE("Get and set raw index")
- {
- CHECK(counters.get_raw(4) == 0);
- counters.set_raw(4, 27);
- CHECK(counters.get(Statistic::cache_miss) == 27);
- }
-
- SUBCASE("Set future raw counter")
- {
- const auto future_index = static_cast<size_t>(Statistic::END) + 2;
- counters.set_raw(future_index, 42);
- CHECK(counters.get_raw(future_index) == 42);
- }
-
- SUBCASE("Increment single counter")
- {
- counters.set(Statistic::cache_miss, 4);
-
- counters.increment(Statistic::cache_miss);
- CHECK(counters.get(Statistic::cache_miss) == 5);
-
- counters.increment(Statistic::cache_miss, -3);
- CHECK(counters.get(Statistic::cache_miss) == 2);
-
- counters.increment(Statistic::cache_miss, -3);
- CHECK(counters.get(Statistic::cache_miss) == 0);
- }
-
- SUBCASE("Increment many counters")
- {
- counters.set(Statistic::direct_cache_hit, 3);
- counters.set(Statistic::cache_miss, 2);
- counters.set(Statistic::files_in_cache, 10);
- counters.set(Statistic::cache_size_kibibyte, 1);
-
- Counters updates;
- updates.set(Statistic::direct_cache_hit, 6);
- updates.set(Statistic::cache_miss, 5);
- updates.set(Statistic::files_in_cache, -1);
- updates.set(Statistic::cache_size_kibibyte, -4);
-
- counters.increment(updates);
- CHECK(counters.get(Statistic::direct_cache_hit) == 9);
- CHECK(counters.get(Statistic::cache_miss) == 7);
- CHECK(counters.get(Statistic::files_in_cache) == 9);
- CHECK(counters.get(Statistic::cache_size_kibibyte) == 0); // No wrap-around
- }
-}
-
-TEST_SUITE_END();
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
CHECK(Depfile::escape_filename("foo$bar") == "foo$$bar");
}
-TEST_CASE("Depfile::rewrite_paths")
+TEST_CASE("Depfile::rewrite_source_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));
+ const auto content =
+ FMT("{0}/foo.o {0}/foo.o: bar.c {0}/bar.h \\\n\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));
+ CHECK(!Depfile::rewrite_source_paths(ctx, ""));
+ CHECK(!Depfile::rewrite_source_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));
+ CHECK(!Depfile::rewrite_source_paths(ctx, ""));
+ CHECK(!Depfile::rewrite_source_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 actual = Depfile::rewrite_source_paths(ctx, content);
const auto expected =
- FMT("foo.o: bar.c ./bar.h \\\n {}/fie.h ./fum.h\n", Util::dir_name(cwd));
+ FMT("{0}/foo.o {0}/foo.o: bar.c ./bar.h \\\n\n {1}/fie.h ./fum.h\n",
+ cwd,
+ Util::dir_name(cwd));
REQUIRE(actual);
CHECK(*actual == expected);
}
TEST_CASE("Depfile::tokenize")
{
- SUBCASE("Parse empty depfile")
+ SUBCASE("Empty")
{
std::vector<std::string> result = Depfile::tokenize("");
CHECK(result.size() == 0);
}
- SUBCASE("Parse simple depfile")
+ SUBCASE("Simple")
{
std::vector<std::string> result =
Depfile::tokenize("cat.o: meow meow purr");
CHECK(result[3] == "purr");
}
- SUBCASE("Parse depfile with a dollar sign followed by a dollar sign")
+ SUBCASE("Dollar sign followed by a dollar sign")
{
std::vector<std::string> result = Depfile::tokenize("cat.o: meow$$");
REQUIRE(result.size() == 2);
CHECK(result[1] == "meow$");
}
- SUBCASE("Parse depfile with a dollar sign followed by an alphabet")
+ SUBCASE("Dollar sign followed by an alphabet")
{
std::vector<std::string> result = Depfile::tokenize("cat.o: meow$w");
REQUIRE(result.size() == 2);
CHECK(result[1] == "meow$w");
}
- SUBCASE("Parse depfile with a backslash followed by a number sign or a colon")
+ SUBCASE("Backslash followed by a number sign or a colon")
{
std::vector<std::string> result =
Depfile::tokenize("cat.o: meow\\# meow\\:");
CHECK(result[2] == "meow:");
}
- SUBCASE("Parse depfile with a backslash followed by an alphabet")
+ SUBCASE("Backslash followed by an alphabet")
{
std::vector<std::string> result =
Depfile::tokenize("cat.o: meow\\w purr\\r");
CHECK(result[2] == "purr\\r");
}
- SUBCASE("Parse depfile with a backslash followed by a space or a tab")
+ SUBCASE("Backslash followed by a space or a tab")
{
std::vector<std::string> result =
Depfile::tokenize("cat.o: meow\\ meow purr\\\tpurr");
CHECK(result[2] == "purr\tpurr");
}
- SUBCASE("Parse depfile with backslashes followed by a space or a tab")
+ SUBCASE("Backslashes followed by a space or a tab")
{
std::vector<std::string> result =
Depfile::tokenize("cat.o: meow\\\\\\ meow purr\\\\ purr");
CHECK(result[3] == "purr");
}
- SUBCASE("Parse depfile with a backslash newline")
+ SUBCASE("Backslash newline")
{
std::vector<std::string> result =
Depfile::tokenize("cat.o: meow\\\nmeow\\\n purr\\\n\tpurr");
CHECK(result[4] == "purr");
}
- SUBCASE("Parse depfile with a new line")
+ SUBCASE("Newlines")
{
- // 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.
+ // This is an invalid dependency file since it has multiple lines without
+ // backslash, which is not valid Makefile syntax. However, the
+ // Depfile::tokenize's simplistic parser accepts them.
std::vector<std::string> result =
Depfile::tokenize("cat.o: meow\nmeow\npurr\n");
REQUIRE(result.size() == 4);
CHECK(result[3] == "purr");
}
- SUBCASE("Parse depfile with a trailing dollar sign")
+ SUBCASE("Trailing dollar sign")
{
std::vector<std::string> result = Depfile::tokenize("cat.o: meow$");
REQUIRE(result.size() == 2);
CHECK(result[1] == "meow$");
}
- SUBCASE("Parse depfile with a trailing backslash")
+ SUBCASE("Trailing backslash")
{
std::vector<std::string> result = Depfile::tokenize("cat.o: meow\\");
REQUIRE(result.size() == 2);
CHECK(result[1] == "meow\\");
}
- SUBCASE("Parse depfile with a trailing backslash newline")
+ SUBCASE("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");
}
+
+ SUBCASE("Space before the colon but not after")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat.o :meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat.o:");
+ CHECK(result[1] == "meow");
+ }
+
+ SUBCASE("Space around the colon")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat.o : meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat.o:");
+ CHECK(result[1] == "meow");
+ }
+
+ SUBCASE("No space between colon and dependency")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat.o:meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat.o:");
+ CHECK(result[1] == "meow");
+ }
+
+ SUBCASE("Windows filename (with backslashes in target)")
+ {
+ std::vector<std::string> result = Depfile::tokenize("e:\\cat.o: meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "e:\\cat.o:");
+ CHECK(result[1] == "meow");
+ }
+
+ SUBCASE("Windows filename (with backslashes in prerequisite)")
+ {
+ std::vector<std::string> result =
+ Depfile::tokenize("cat.o: c:\\meow\\purr");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat.o:");
+ CHECK(result[1] == "c:\\meow\\purr");
+ }
+
+ SUBCASE("Windows filename (with slashes in target)")
+ {
+ std::vector<std::string> result = Depfile::tokenize("e:/cat.o: meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "e:/cat.o:");
+ CHECK(result[1] == "meow");
+ }
+
+ SUBCASE("Windows filename (with slashes in prerequisite)")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat.o: c:/meow/purr");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat.o:");
+ CHECK(result[1] == "c:/meow/purr");
+ }
+
+ SUBCASE("Windows filename (with slashes and trailing colon)")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat.o: c: /meow/purr");
+ REQUIRE(result.size() == 3);
+ CHECK(result[0] == "cat.o:");
+ CHECK(result[1] == "c:");
+ CHECK(result[2] == "/meow/purr");
+ }
+
+ SUBCASE("Windows filename: cat:/meow")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat:/meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat:");
+ CHECK(result[1] == "/meow");
+ }
+
+ SUBCASE("Windows filename: cat:\\meow")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat:\\meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat:");
+ CHECK(result[1] == "\\meow");
+ }
+
+ SUBCASE("Windows filename: cat:\\ meow")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat:\\ meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat:");
+ CHECK(result[1] == " meow");
+ }
+
+ SUBCASE("Windows filename: cat:c:/meow")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat:c:/meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat:");
+ CHECK(result[1] == "c:/meow");
+ }
+
+ SUBCASE("Windows filename: cat:c:\\meow")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat:c:\\meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat:");
+ CHECK(result[1] == "c:\\meow");
+ }
+
+ SUBCASE("Windows filename: cat:c:")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat:c:");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat:");
+ CHECK(result[1] == "c:");
+ }
+
+ SUBCASE("Windows filename: cat:c:\\")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat:c:\\");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat:");
+ CHECK(result[1] == "c:\\");
+ }
+
+ SUBCASE("Windows filename: cat:c:/")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat:c:/");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "cat:");
+ CHECK(result[1] == "c:/");
+ }
+
+ SUBCASE("Windows filename: cat:c:meow")
+ {
+ std::vector<std::string> result = Depfile::tokenize("cat:c:meow");
+ REQUIRE(result.size() == 3);
+ CHECK(result[0] == "cat:");
+ CHECK(result[1] == "c:");
+ CHECK(result[2] == "meow");
+ }
+
+ SUBCASE("Windows filename: c:c:/meow")
+ {
+ std::vector<std::string> result = Depfile::tokenize("c:c:/meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "c:");
+ CHECK(result[1] == "c:/meow");
+ }
+
+ SUBCASE("Windows filename: c:c:\\meow")
+ {
+ std::vector<std::string> result = Depfile::tokenize("c:c:\\meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "c:");
+ CHECK(result[1] == "c:\\meow");
+ }
+
+ SUBCASE("Windows filename: c:z:\\meow")
+ {
+ std::vector<std::string> result = Depfile::tokenize("c:z:\\meow");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "c:");
+ CHECK(result[1] == "z:\\meow");
+ }
+
+ SUBCASE("Windows filename: c:cd:\\meow")
+ {
+ std::vector<std::string> result = Depfile::tokenize("c:cd:\\meow");
+ REQUIRE(result.size() == 3);
+ CHECK(result[0] == "c:");
+ CHECK(result[1] == "cd:");
+ CHECK(result[2] == "\\meow");
+ }
}
TEST_SUITE_END();
+++ /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
-
-#include "FormatNonstdStringView.hpp"
-
-#include "third_party/doctest.h"
-
-using nonstd::string_view;
-
-TEST_SUITE_BEGIN("FormatNonstdStringView");
-
-TEST_CASE("fmt::format and nonstd::string_view")
-{
- string_view null;
- CHECK(fmt::format("{}", null) == "");
-
- const std::string s = "0123456789";
-
- string_view empty(s.data(), 0);
- CHECK(fmt::format("{}", empty) == "");
-
- string_view empty_end(s.data() + s.length(), 0);
- CHECK(fmt::format("{}", empty_end) == "");
-
- string_view start(s.data(), 2);
- CHECK(fmt::format("{}", start) == "01");
-
- string_view middle(s.data() + 3, 4);
- CHECK(fmt::format("{}", middle) == "3456");
-
- string_view end(s.data() + s.length() - 2, 2);
- CHECK(fmt::format("{}", end) == "89");
-}
-
-TEST_SUITE_END();
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "../src/Util.hpp"
#include "TestUtil.hpp"
+#include <Fd.hpp>
+#include <util/file.hpp>
+
#include "third_party/doctest.h"
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
using TestUtil::TestContext;
namespace {
+bool
+inode_cache_available()
+{
+ Fd fd(open(Util::get_actual_cwd().c_str(), O_RDONLY));
+ return fd && InodeCache::available(*fd);
+}
+
void
-init(Context& ctx)
+init(Config& config)
{
- ctx.config.set_debug(true);
- ctx.config.set_inode_cache(true);
- ctx.config.set_cache_dir(Util::get_home_directory());
+ config.set_debug(true);
+ config.set_inode_cache(true);
+ config.set_temporary_dir(Util::get_actual_cwd());
}
bool
-put(const Context& ctx,
+put(InodeCache& inode_cache,
const std::string& filename,
const std::string& str,
int return_value)
{
- return ctx.inode_cache.put(filename,
- InodeCache::ContentType::code,
- Hash().hash(str).digest(),
- return_value);
+ return inode_cache.put(filename,
+ InodeCache::ContentType::checked_for_temporal_macros,
+ Hash().hash(str).digest(),
+ return_value);
}
} // namespace
-TEST_SUITE_BEGIN("InodeCache");
+TEST_SUITE_BEGIN("InodeCache" * doctest::skip(!inode_cache_available()));
TEST_CASE("Test disabled")
{
TestContext test_context;
- Context ctx;
- init(ctx);
- ctx.config.set_inode_cache(false);
+ Config config;
+ init(config);
+ config.set_inode_cache(false);
+ InodeCache inode_cache(config, util::Duration(0));
Digest digest;
int return_value;
- CHECK(!ctx.inode_cache.get(
- "a", InodeCache::ContentType::code, digest, &return_value));
- CHECK(!ctx.inode_cache.put(
- "a", InodeCache::ContentType::code, digest, return_value));
- CHECK(ctx.inode_cache.get_hits() == -1);
- CHECK(ctx.inode_cache.get_misses() == -1);
- CHECK(ctx.inode_cache.get_errors() == -1);
+ CHECK(!inode_cache.get("a",
+ InodeCache::ContentType::checked_for_temporal_macros,
+ digest,
+ &return_value));
+ CHECK(!inode_cache.put("a",
+ InodeCache::ContentType::checked_for_temporal_macros,
+ digest,
+ return_value));
+ CHECK(inode_cache.get_hits() == -1);
+ CHECK(inode_cache.get_misses() == -1);
+ CHECK(inode_cache.get_errors() == -1);
}
TEST_CASE("Test lookup nonexistent")
{
TestContext test_context;
- Context ctx;
- init(ctx);
- ctx.inode_cache.drop();
- Util::write_file("a", "");
+ Config config;
+ init(config);
+
+ InodeCache inode_cache(config, util::Duration(0));
+ util::write_file("a", "");
Digest digest;
int return_value;
- CHECK(!ctx.inode_cache.get(
- "a", InodeCache::ContentType::code, digest, &return_value));
- CHECK(ctx.inode_cache.get_hits() == 0);
- CHECK(ctx.inode_cache.get_misses() == 1);
- CHECK(ctx.inode_cache.get_errors() == 0);
+ CHECK(!inode_cache.get("a",
+ InodeCache::ContentType::checked_for_temporal_macros,
+ digest,
+ &return_value));
+ CHECK(inode_cache.get_hits() == 0);
+ CHECK(inode_cache.get_misses() == 1);
+ CHECK(inode_cache.get_errors() == 0);
}
TEST_CASE("Test put and lookup")
{
TestContext test_context;
- Context ctx;
- init(ctx);
- ctx.inode_cache.drop();
- Util::write_file("a", "a text");
+ Config config;
+ init(config);
+
+ InodeCache inode_cache(config, util::Duration(0));
+ util::write_file("a", "a text");
- CHECK(put(ctx, "a", "a text", 1));
+ CHECK(put(inode_cache, "a", "a text", 1));
Digest digest;
int return_value;
- CHECK(ctx.inode_cache.get(
- "a", InodeCache::ContentType::code, digest, &return_value));
+ CHECK(inode_cache.get("a",
+ InodeCache::ContentType::checked_for_temporal_macros,
+ digest,
+ &return_value));
CHECK(digest == Hash().hash("a text").digest());
CHECK(return_value == 1);
- CHECK(ctx.inode_cache.get_hits() == 1);
- CHECK(ctx.inode_cache.get_misses() == 0);
- CHECK(ctx.inode_cache.get_errors() == 0);
+ CHECK(inode_cache.get_hits() == 1);
+ CHECK(inode_cache.get_misses() == 0);
+ CHECK(inode_cache.get_errors() == 0);
- Util::write_file("a", "something else");
+ util::write_file("a", "something else");
- CHECK(!ctx.inode_cache.get(
- "a", InodeCache::ContentType::code, digest, &return_value));
- CHECK(ctx.inode_cache.get_hits() == 1);
- CHECK(ctx.inode_cache.get_misses() == 1);
- CHECK(ctx.inode_cache.get_errors() == 0);
+ CHECK(!inode_cache.get("a",
+ InodeCache::ContentType::checked_for_temporal_macros,
+ digest,
+ &return_value));
+ CHECK(inode_cache.get_hits() == 1);
+ CHECK(inode_cache.get_misses() == 1);
+ CHECK(inode_cache.get_errors() == 0);
- CHECK(put(ctx, "a", "something else", 2));
+ CHECK(put(inode_cache, "a", "something else", 2));
- CHECK(ctx.inode_cache.get(
- "a", InodeCache::ContentType::code, digest, &return_value));
+ CHECK(inode_cache.get("a",
+ InodeCache::ContentType::checked_for_temporal_macros,
+ digest,
+ &return_value));
CHECK(digest == Hash().hash("something else").digest());
CHECK(return_value == 2);
- CHECK(ctx.inode_cache.get_hits() == 2);
- CHECK(ctx.inode_cache.get_misses() == 1);
- CHECK(ctx.inode_cache.get_errors() == 0);
+ CHECK(inode_cache.get_hits() == 2);
+ CHECK(inode_cache.get_misses() == 1);
+ CHECK(inode_cache.get_errors() == 0);
}
TEST_CASE("Drop file")
{
TestContext test_context;
- Context ctx;
- init(ctx);
+ Config config;
+ init(config);
+
+ InodeCache inode_cache(config, util::Duration(0));
Digest digest;
- ctx.inode_cache.get("a", InodeCache::ContentType::binary, digest);
- CHECK(Stat::stat(ctx.inode_cache.get_file()));
- CHECK(ctx.inode_cache.drop());
- CHECK(!Stat::stat(ctx.inode_cache.get_file()));
- CHECK(!ctx.inode_cache.drop());
+ inode_cache.get("a", InodeCache::ContentType::raw, digest);
+ CHECK(Stat::stat(inode_cache.get_file()));
+ CHECK(inode_cache.drop());
+ CHECK(!Stat::stat(inode_cache.get_file()));
+ CHECK(!inode_cache.drop());
}
TEST_CASE("Test content type")
{
TestContext test_context;
- Context ctx;
- init(ctx);
- ctx.inode_cache.drop();
- Util::write_file("a", "a text");
+ Config config;
+ init(config);
+
+ InodeCache inode_cache(config, util::Duration(0));
+ util::write_file("a", "a text");
Digest binary_digest = Hash().hash("binary").digest();
Digest code_digest = Hash().hash("code").digest();
- Digest code_with_sloppy_time_macros_digest =
- Hash().hash("sloppy_time_macros").digest();
- CHECK(ctx.inode_cache.put(
- "a", InodeCache::ContentType::binary, binary_digest, 1));
- CHECK(
- ctx.inode_cache.put("a", InodeCache::ContentType::code, code_digest, 2));
- CHECK(
- ctx.inode_cache.put("a",
- InodeCache::ContentType::code_with_sloppy_time_macros,
- code_with_sloppy_time_macros_digest,
- 3));
+ CHECK(inode_cache.put("a", InodeCache::ContentType::raw, binary_digest, 1));
+ CHECK(inode_cache.put(
+ "a", InodeCache::ContentType::checked_for_temporal_macros, code_digest, 2));
Digest digest;
int return_value;
- CHECK(ctx.inode_cache.get(
- "a", InodeCache::ContentType::binary, digest, &return_value));
+ CHECK(
+ inode_cache.get("a", InodeCache::ContentType::raw, digest, &return_value));
CHECK(digest == binary_digest);
CHECK(return_value == 1);
- CHECK(ctx.inode_cache.get(
- "a", InodeCache::ContentType::code, digest, &return_value));
- CHECK(digest == code_digest);
- CHECK(return_value == 2);
-
- CHECK(
- ctx.inode_cache.get("a",
- InodeCache::ContentType::code_with_sloppy_time_macros,
+ CHECK(inode_cache.get("a",
+ InodeCache::ContentType::checked_for_temporal_macros,
digest,
&return_value));
- CHECK(digest == code_with_sloppy_time_macros_digest);
- CHECK(return_value == 3);
+ CHECK(digest == code_digest);
+ CHECK(return_value == 2);
}
TEST_SUITE_END();
+++ /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/Lockfile.hpp"
-#include "../src/Stat.hpp"
-#include "TestUtil.hpp"
-
-#include "third_party/doctest.h"
-
-TEST_SUITE_BEGIN("LockFile");
-
-using TestUtil::TestContext;
-
-TEST_CASE("Lockfile acquire and release")
-{
- TestContext test_context;
-
- {
- Lockfile lock("test", 1000);
- CHECK(lock.acquired());
- auto st = Stat::lstat("test.lock");
- CHECK(st);
-#ifndef _WIN32
- CHECK(st.is_symlink());
-#else
- CHECK(st.is_regular());
-#endif
- }
-
- CHECK(!Stat::lstat("test.lock"));
-}
-
-TEST_CASE("Lockfile creates missing directories")
-{
- TestContext test_context;
-
- Lockfile lock("a/b/c/test", 1000);
- CHECK(lock.acquired());
- CHECK(Stat::lstat("a/b/c/test.lock"));
-}
-
-#ifndef _WIN32
-TEST_CASE("Lockfile breaking")
-{
- TestContext test_context;
-
- CHECK(symlink("foo", "test.lock") == 0);
-
- Lockfile lock("test", 1000);
- CHECK(lock.acquired());
-}
-#endif // !_WIN32
-
-TEST_SUITE_END();
+++ /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
-
-#include "../src/Compression.hpp"
-#include "../src/Compressor.hpp"
-#include "../src/Decompressor.hpp"
-#include "../src/File.hpp"
-#include "TestUtil.hpp"
-
-#include "third_party/doctest.h"
-
-using TestUtil::TestContext;
-
-TEST_SUITE_BEGIN("NullCompression");
-
-TEST_CASE("Compression::Type::none roundtrip")
-{
- TestContext test_context;
-
- File f("data.uncompressed", "w");
- auto compressor =
- Compressor::create_from_type(Compression::Type::none, f.get(), 1);
- CHECK(compressor->actual_compression_level() == 0);
- compressor->write("foobar", 6);
- compressor->finalize();
-
- f.open("data.uncompressed", "r");
- auto decompressor =
- Decompressor::create_from_type(Compression::Type::none, f.get());
-
- char buffer[4];
- decompressor->read(buffer, 4);
- CHECK(memcmp(buffer, "foob", 4) == 0);
-
- SUBCASE("Garbage data")
- {
- // Not reached the end.
- CHECK_THROWS_WITH(decompressor->finalize(),
- "garbage data at end of uncompressed stream");
- }
-
- SUBCASE("Read to end")
- {
- decompressor->read(buffer, 2);
- CHECK(memcmp(buffer, "ar", 2) == 0);
-
- // Reached the end.
- decompressor->finalize();
-
- // Nothing left to read.
- CHECK_THROWS_WITH(decompressor->read(buffer, 1),
- "failed to read from uncompressed stream");
- }
-}
-
-TEST_SUITE_END();
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "../src/Util.hpp"
#include "TestUtil.hpp"
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <util/file.hpp>
+
#include "third_party/doctest.h"
#ifdef HAVE_UNISTD_H
CHECK(stat.device() == 0);
CHECK(stat.inode() == 0);
CHECK(stat.mode() == 0);
- CHECK(stat.ctime() == 0);
- CHECK(stat.mtime() == 0);
+ CHECK(stat.ctime().sec() == 0);
+ CHECK(stat.ctime().nsec() == 0);
+ CHECK(stat.mtime().sec() == 0);
+ CHECK(stat.mtime().nsec() == 0);
CHECK(stat.size() == 0);
CHECK(stat.size_on_disk() == 0);
CHECK(!stat.is_directory());
CHECK(!stat.is_regular());
CHECK(!stat.is_symlink());
- CHECK(stat.ctim().tv_sec == 0);
- CHECK(stat.ctim().tv_nsec == 0);
-
- CHECK(stat.mtim().tv_sec == 0);
- CHECK(stat.mtim().tv_nsec == 0);
-
#ifdef _WIN32
CHECK(stat.file_attributes() == 0);
CHECK(stat.reparse_tag() == 0);
{
TestContext test_context;
- Util::write_file("a", "");
- Util::write_file("b", "");
+ util::write_file("a", "");
+ util::write_file("b", "");
auto a_stat = Stat::stat("a");
auto b_stat = Stat::stat("b");
CHECK(a_stat.same_inode_as(a_stat));
CHECK(!a_stat.same_inode_as(b_stat));
- Util::write_file("a", "change size");
+ util::write_file("a", "change size", util::InPlace::yes);
auto new_a_stat = Stat::stat("a");
CHECK(new_a_stat.same_inode_as(a_stat));
+
+ CHECK(!Stat::stat("nonexistent").same_inode_as(Stat::stat("nonexistent")));
}
TEST_CASE("Return values when file is missing")
CHECK(stat.device() == 0);
CHECK(stat.inode() == 0);
CHECK(stat.mode() == 0);
- CHECK(stat.ctime() == 0);
- CHECK(stat.mtime() == 0);
+ CHECK(stat.ctime().sec() == 0);
+ CHECK(stat.ctime().nsec() == 0);
+ CHECK(stat.mtime().sec() == 0);
+ CHECK(stat.mtime().nsec() == 0);
CHECK(stat.size() == 0);
CHECK(stat.size_on_disk() == 0);
CHECK(!stat.is_directory());
CHECK(!stat.is_regular());
CHECK(!stat.is_symlink());
- CHECK(stat.ctim().tv_sec == 0);
- CHECK(stat.ctim().tv_nsec == 0);
-
- CHECK(stat.mtim().tv_sec == 0);
- CHECK(stat.mtim().tv_nsec == 0);
-
#ifdef _WIN32
CHECK(stat.file_attributes() == 0);
CHECK(stat.reparse_tag() == 0);
{
TestContext test_context;
- Util::write_file("file", "1234567");
+ util::write_file("file", "1234567");
auto stat = Stat::stat("file");
CHECK(stat);
struct timespec last_write_time =
win32_filetime_to_timespec(info.ftLastWriteTime);
- CHECK(stat.ctime() == creation_time.tv_sec);
- CHECK(stat.mtime() == last_write_time.tv_sec);
-
- CHECK(stat.ctim().tv_sec == creation_time.tv_sec);
- CHECK(stat.ctim().tv_nsec == creation_time.tv_nsec);
- CHECK(stat.mtim().tv_sec == last_write_time.tv_sec);
- CHECK(stat.mtim().tv_nsec == last_write_time.tv_nsec);
+ CHECK(stat.ctime().sec() == creation_time.tv_sec);
+ CHECK(stat.ctime().nsec_decimal_part() == creation_time.tv_nsec);
+ CHECK(stat.mtime().sec() == last_write_time.tv_sec);
+ CHECK(stat.mtime().nsec_decimal_part() == last_write_time.tv_nsec);
CHECK(stat.size_on_disk() == ((stat.size() + 1023) & ~1023));
CHECK(stat.file_attributes() == info.dwFileAttributes);
CHECK(stat.device() == st.st_dev);
CHECK(stat.inode() == st.st_ino);
CHECK(stat.mode() == st.st_mode);
- CHECK(stat.ctime() == st.st_ctime);
- CHECK(stat.mtime() == st.st_mtime);
CHECK(stat.size_on_disk() == st.st_blocks * 512);
# ifdef HAVE_STRUCT_STAT_ST_CTIM
- CHECK(stat.ctim().tv_sec == st.st_ctim.tv_sec);
- CHECK(stat.ctim().tv_nsec == st.st_ctim.tv_nsec);
+ CHECK(stat.ctime().sec() == st.st_ctim.tv_sec);
+ CHECK(stat.ctime().nsec_decimal_part() == st.st_ctim.tv_nsec);
+# elif defined(HAVE_STRUCT_STAT_ST_CTIMESPEC)
+ CHECK(stat.ctime().sec() == st.st_ctimespec.tv_sec);
+ CHECK(stat.ctime().nsec_decimal_part() == st.st_ctimespec.tv_nsec);
# else
- CHECK(stat.ctim().tv_sec == st.st_ctime);
- CHECK(stat.ctim().tv_nsec == 0);
+ CHECK(stat.ctime().sec() == st.st_ctime);
+ CHECK(stat.ctime().nsec_decimal_part() == 0);
# endif
# ifdef HAVE_STRUCT_STAT_ST_MTIM
- CHECK(stat.mtim().tv_sec == st.st_mtim.tv_sec);
- CHECK(stat.mtim().tv_nsec == st.st_mtim.tv_nsec);
+ CHECK(stat.mtime().sec() == st.st_mtim.tv_sec);
+ CHECK(stat.mtime().nsec_decimal_part() == st.st_mtim.tv_nsec);
+# elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC)
+ CHECK(stat.mtime().sec() == st.st_mtimespec.tv_sec);
+ CHECK(stat.mtime().nsec_decimal_part() == st.st_mtimespec.tv_nsec);
# else
- CHECK(stat.mtim().tv_sec == st.st_mtime);
- CHECK(stat.mtim().tv_nsec == 0);
+ CHECK(stat.mtime().sec() == st.st_mtime);
+ CHECK(stat.mtime().nsec_decimal_part() == 0);
# endif
#endif
}
{
TestContext test_context;
- Util::write_file("file", "1234567");
+ util::write_file("file", "1234567");
#ifdef _WIN32
REQUIRE(CreateSymbolicLinkA(
{
TestContext test_context;
- Util::write_file("a", "");
+ util::write_file("a", "");
#ifdef _WIN32
REQUIRE(CreateHardLinkA("b", "a", nullptr));
CHECK(stat_a.inode() == stat_b.inode());
CHECK(stat_a.same_inode_as(stat_b));
- Util::write_file("a", "1234567");
+ util::write_file("a", "1234567", util::InPlace::yes);
stat_a = Stat::stat("a");
stat_b = Stat::stat("b");
#endif
}
-#ifdef _WIN32
SUBCASE("block device")
{
+#ifdef _WIN32
auto stat = Stat::stat("\\\\.\\C:");
CHECK(stat);
CHECK(stat.error_number() == 0);
CHECK(S_ISBLK(stat.mode()));
CHECK(stat.file_attributes() == 0);
CHECK(stat.reparse_tag() == 0);
- }
#endif
+ }
}
#ifdef _WIN32
{
TestContext test_context;
- Util::write_file("file", "");
+ util::write_file("file", "");
DWORD prev_attrs = GetFileAttributesA("file");
REQUIRE(prev_attrs != INVALID_FILE_ATTRIBUTES);
Finalizer cleanup([&] { CloseHandle(handle); });
// Sanity check we can't open the file for read/write access.
- REQUIRE_THROWS_AS(Util::read_file("file"), const Error&);
+ REQUIRE(!util::read_file<std::string>("file"));
SUBCASE("stat file no sharing")
{
+++ /dev/null
-// Copyright (C) 2011-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/Statistic.hpp"
-#include "../src/Statistics.hpp"
-#include "../src/Util.hpp"
-#include "../src/fmtmacros.hpp"
-#include "TestUtil.hpp"
-
-#include "third_party/doctest.h"
-
-using TestUtil::TestContext;
-
-TEST_SUITE_BEGIN("Statistics");
-
-TEST_CASE("Read nonexistent")
-{
- TestContext test_context;
-
- Counters counters = Statistics::read("test");
-
- REQUIRE(counters.size() == static_cast<size_t>(Statistic::END));
- CHECK(counters.get(Statistic::cache_miss) == 0);
-}
-
-TEST_CASE("Read bad")
-{
- TestContext test_context;
-
- Util::write_file("test", "bad 1 2 3 4 5\n");
- Counters counters = Statistics::read("test");
-
- REQUIRE(counters.size() == static_cast<size_t>(Statistic::END));
- CHECK(counters.get(Statistic::cache_miss) == 0);
-}
-
-TEST_CASE("Read existing")
-{
- TestContext test_context;
-
- Util::write_file("test", "0 1 2 3 27 5\n");
- Counters counters = Statistics::read("test");
-
- REQUIRE(counters.size() == static_cast<size_t>(Statistic::END));
- CHECK(counters.get(Statistic::cache_miss) == 27);
- CHECK(counters.get(Statistic::could_not_use_modules) == 0);
-}
-
-TEST_CASE("Read future counters")
-{
- TestContext test_context;
-
- std::string content;
- size_t count = static_cast<size_t>(Statistic::END) + 1;
- for (size_t i = 0; i < count; ++i) {
- content += FMT("{}\n", i);
- }
-
- Util::write_file("test", content);
- Counters counters = Statistics::read("test");
-
- REQUIRE(counters.size() == count);
- for (size_t i = 0; i < count; ++i) {
- CHECK(counters.get_raw(i) == i);
- }
-}
-
-TEST_CASE("Update")
-{
- TestContext test_context;
-
- Util::write_file("test", "0 1 2 3 27 5\n");
-
- auto counters = Statistics::update("test", [](Counters& cs) {
- cs.increment(Statistic::internal_error, 1);
- cs.increment(Statistic::cache_miss, 6);
- });
- REQUIRE(counters);
-
- CHECK(counters->get(Statistic::internal_error) == 4);
- CHECK(counters->get(Statistic::cache_miss) == 33);
-
- counters = Statistics::read("test");
- CHECK(counters->get(Statistic::internal_error) == 4);
- CHECK(counters->get(Statistic::cache_miss) == 33);
-}
-
-TEST_SUITE_END();
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "../src/fmtmacros.hpp"
#include "TestUtil.hpp"
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <util/file.hpp>
+
#include "third_party/doctest.h"
-#include "third_party/nonstd/optional.hpp"
+
+#include <fcntl.h>
+
+#include <optional>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
#include <algorithm>
-using doctest::Approx;
-using nonstd::nullopt;
using TestUtil::TestContext;
TEST_SUITE_BEGIN("Util");
CHECK(Util::change_extension("foo.bar.txt", ".o") == "foo.bar.o");
}
-TEST_CASE("Util::clamp")
-{
- CHECK(Util::clamp(0, 1, 2) == 1);
- CHECK(Util::clamp(1, 1, 2) == 1);
- CHECK(Util::clamp(2, 1, 2) == 2);
- CHECK(Util::clamp(3, 1, 2) == 2);
-
- CHECK(Util::clamp(7.0, 7.7, 8.8) == Approx(7.7));
- CHECK(Util::clamp(8.0, 7.7, 8.8) == Approx(8.0));
- CHECK(Util::clamp(9.0, 7.7, 8.8) == Approx(8.8));
-}
-
TEST_CASE("Util::common_dir_prefix_length")
{
CHECK(Util::common_dir_prefix_length("", "") == 0);
CHECK(Util::create_dir("create/dir"));
CHECK(Stat::stat("create/dir").is_directory());
- Util::write_file("create/dir/file", "");
+ util::write_file("create/dir/file", "");
CHECK(!Util::create_dir("create/dir/file"));
}
CHECK(Util::strip_ansi_csi_seqs(input) == "Normal, bold, red, bold green.\n");
}
-TEST_CASE("Util::ends_with")
-{
- CHECK(Util::ends_with("", ""));
- CHECK(Util::ends_with("x", ""));
- CHECK(Util::ends_with("x", "x"));
- CHECK(Util::ends_with("xy", ""));
- CHECK(Util::ends_with("xy", "y"));
- CHECK(Util::ends_with("xy", "xy"));
- CHECK(Util::ends_with("xyz", ""));
- CHECK(Util::ends_with("xyz", "z"));
- CHECK(Util::ends_with("xyz", "yz"));
- CHECK(Util::ends_with("xyz", "xyz"));
-
- CHECK_FALSE(Util::ends_with("", "x"));
- CHECK_FALSE(Util::ends_with("x", "y"));
- CHECK_FALSE(Util::ends_with("x", "xy"));
-}
-
TEST_CASE("Util::ensure_dir_exists")
{
TestContext test_context;
CHECK_NOTHROW(Util::ensure_dir_exists("create/dir"));
CHECK(Stat::stat("create/dir").is_directory());
- Util::write_file("create/dir/file", "");
+ util::write_file("create/dir/file", "");
CHECK_THROWS_WITH(
Util::ensure_dir_exists("create/dir/file"),
"Failed to create directory create/dir/file: Not a directory");
CHECK(Stat::stat(filename).size() == 20000);
}
-TEST_CASE("Util::for_each_level_1_subdir")
-{
- std::vector<std::string> actual;
- Util::for_each_level_1_subdir(
- "cache_dir",
- [&](const std::string& subdir, const Util::ProgressReceiver&) {
- actual.push_back(subdir);
- },
- [](double) {});
-
- std::vector<std::string> expected = {
- "cache_dir/0",
- "cache_dir/1",
- "cache_dir/2",
- "cache_dir/3",
- "cache_dir/4",
- "cache_dir/5",
- "cache_dir/6",
- "cache_dir/7",
- "cache_dir/8",
- "cache_dir/9",
- "cache_dir/a",
- "cache_dir/b",
- "cache_dir/c",
- "cache_dir/d",
- "cache_dir/e",
- "cache_dir/f",
- };
- CHECK(actual == expected);
-}
-
TEST_CASE("Util::format_argv_for_logging")
{
const char* argv_0[] = {nullptr};
static inline std::string
os_path(std::string path)
{
-#if !defined(HAVE_DIRENT_H) && DIR_DELIM_CH != '/'
- std::replace(path.begin(), path.end(), '/', DIR_DELIM_CH);
+#if defined(_WIN32) && !defined(HAVE_DIRENT_H)
+ std::replace(path.begin(), path.end(), '/', '\\');
#endif
return path;
}
-TEST_CASE("Util::get_level_1_files")
-{
- TestContext test_context;
-
- Util::create_dir("e/m/p/t/y");
-
- Util::create_dir("0/1");
- Util::create_dir("0/f/c");
- Util::write_file("0/file_a", "");
- Util::write_file("0/1/file_b", "1");
- Util::write_file("0/1/file_c", "12");
- Util::write_file("0/f/c/file_d", "123");
-
- auto null_receiver = [](double) {};
-
- SUBCASE("nonexistent subdirectory")
- {
- const auto files = Util::get_level_1_files("2", null_receiver);
- CHECK(files.empty());
- }
-
- SUBCASE("empty subdirectory")
- {
- const auto files = Util::get_level_1_files("e", null_receiver);
- CHECK(files.empty());
- }
-
- SUBCASE("simple case")
- {
- auto files = Util::get_level_1_files("0", null_receiver);
- REQUIRE(files.size() == 4);
-
- // Files within a level are in arbitrary order, sort them to be able to
- // verify them.
- std::sort(
- files.begin(), files.end(), [](const CacheFile& f1, const CacheFile& f2) {
- return f1.path() < f2.path();
- });
-
- CHECK(files[0].path() == os_path("0/1/file_b"));
- CHECK(files[0].lstat().size() == 1);
- CHECK(files[1].path() == os_path("0/1/file_c"));
- CHECK(files[1].lstat().size() == 2);
- CHECK(files[2].path() == os_path("0/f/c/file_d"));
- CHECK(files[2].lstat().size() == 3);
- CHECK(files[3].path() == os_path("0/file_a"));
- CHECK(files[3].lstat().size() == 0);
- }
-}
-
TEST_CASE("Util::get_relative_path")
{
#ifdef _WIN32
#endif
}
-TEST_CASE("Util::get_path_in_cache")
-{
- CHECK(Util::get_path_in_cache("/zz/ccache", 1, "ABCDEF.suffix")
- == "/zz/ccache/A/BCDEF.suffix");
- CHECK(Util::get_path_in_cache("/zz/ccache", 4, "ABCDEF.suffix")
- == "/zz/ccache/A/B/C/D/EF.suffix");
-}
-
TEST_CASE("Util::hard_link")
{
TestContext test_context;
SUBCASE("Link file to nonexistent destination")
{
- Util::write_file("old", "content");
+ util::write_file("old", "content");
CHECK_NOTHROW(Util::hard_link("old", "new"));
- CHECK(Util::read_file("new") == "content");
+ CHECK(*util::read_file<std::string>("new") == "content");
}
SUBCASE("Link file to existing destination")
{
- Util::write_file("old", "content");
- Util::write_file("new", "other content");
+ util::write_file("old", "content");
+ util::write_file("new", "other content");
CHECK_NOTHROW(Util::hard_link("old", "new"));
- CHECK(Util::read_file("new") == "content");
+ CHECK(*util::read_file<std::string>("new") == "content");
}
SUBCASE("Link nonexistent file")
{
- CHECK_THROWS_AS(Util::hard_link("old", "new"), Error);
+ CHECK_THROWS_AS(Util::hard_link("old", "new"), core::Error);
}
}
CHECK(bytes[7] == 0xca);
}
-TEST_CASE("Util::is_absolute_path")
+TEST_CASE("Util::is_absolute_path_with_prefix")
{
+ CHECK(*Util::is_absolute_path_with_prefix("-I/c/foo") == 2);
+ CHECK(*Util::is_absolute_path_with_prefix("-W,path/c/foo") == 7);
+ CHECK(!Util::is_absolute_path_with_prefix("-DMACRO"));
#ifdef _WIN32
- CHECK(Util::is_absolute_path("C:/"));
- CHECK(Util::is_absolute_path("C:\\foo/fie"));
- CHECK(Util::is_absolute_path("/C:\\foo/fie")); // MSYS/Cygwin path
- CHECK(!Util::is_absolute_path(""));
- CHECK(!Util::is_absolute_path("foo\\fie/fum"));
- CHECK(!Util::is_absolute_path("C:foo/fie"));
+ CHECK(*Util::is_absolute_path_with_prefix("-I/C:/foo") == 2);
+ CHECK(*Util::is_absolute_path_with_prefix("-IC:/foo") == 2);
+ CHECK(*Util::is_absolute_path_with_prefix("-W,path/c:/foo") == 7);
+ CHECK(*Util::is_absolute_path_with_prefix("-W,pathc:/foo") == 7);
+ CHECK(!Util::is_absolute_path_with_prefix("-opt:value"));
+#endif
+}
+
+TEST_CASE("Util::is_ccache_executable")
+{
+ CHECK(Util::is_ccache_executable("ccache"));
+ CHECK(Util::is_ccache_executable("ccache-1.2.3"));
+ CHECK(!Util::is_ccache_executable("fooccache"));
+ CHECK(!Util::is_ccache_executable("gcc"));
+#ifdef _WIN32
+ CHECK(Util::is_ccache_executable("CCACHE"));
+ CHECK(Util::is_ccache_executable("CCACHE.exe"));
+ CHECK(Util::is_ccache_executable("CCACHE-1.2.3"));
+ CHECK(Util::is_ccache_executable("CCACHE.EXE"));
+ CHECK(Util::is_ccache_executable("CCACHE-1.2.3.EXE"));
#endif
- CHECK(Util::is_absolute_path("/"));
- CHECK(Util::is_absolute_path("/foo/fie"));
- CHECK(!Util::is_absolute_path(""));
- CHECK(!Util::is_absolute_path("foo/fie"));
}
TEST_CASE("Util::is_dir_separator")
make_relative_path(
actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "/x")
== "./x");
+ CHECK(
+ make_relative_path(
+ actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "\\x")
+ == ".\\x");
+ CHECK(
+ make_relative_path(
+ actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "\\\\x")
+ == ".\\x");
#else
CHECK(make_relative_path("/", actual_cwd, apparent_cwd, actual_cwd + "/x")
== "./x");
#endif
}
-TEST_CASE("Util::normalize_absolute_path")
+TEST_CASE("Util::normalize_abstract_absolute_path")
{
- CHECK(Util::normalize_absolute_path("") == "");
- CHECK(Util::normalize_absolute_path(".") == ".");
- CHECK(Util::normalize_absolute_path("..") == "..");
- CHECK(Util::normalize_absolute_path("...") == "...");
- CHECK(Util::normalize_absolute_path("x/./") == "x/./");
+ CHECK(Util::normalize_abstract_absolute_path("") == "");
+ CHECK(Util::normalize_abstract_absolute_path(".") == ".");
+ CHECK(Util::normalize_abstract_absolute_path("..") == "..");
+ CHECK(Util::normalize_abstract_absolute_path("...") == "...");
+ CHECK(Util::normalize_abstract_absolute_path("x/./") == "x/./");
#ifdef _WIN32
- CHECK(Util::normalize_absolute_path("c:/") == "c:/");
- CHECK(Util::normalize_absolute_path("c:\\") == "c:/");
- CHECK(Util::normalize_absolute_path("c:/.") == "c:/");
- CHECK(Util::normalize_absolute_path("c:\\..") == "c:/");
- CHECK(Util::normalize_absolute_path("c:\\x/..") == "c:/");
- CHECK(Util::normalize_absolute_path("c:\\x/./y\\..\\\\z") == "c:/x/z");
+ CHECK(Util::normalize_abstract_absolute_path("c:/") == "c:/");
+ CHECK(Util::normalize_abstract_absolute_path("c:\\") == "c:/");
+ CHECK(Util::normalize_abstract_absolute_path("c:/.") == "c:/");
+ CHECK(Util::normalize_abstract_absolute_path("c:\\..") == "c:/");
+ CHECK(Util::normalize_abstract_absolute_path("c:\\x/..") == "c:/");
+ CHECK(Util::normalize_abstract_absolute_path("c:\\x/./y\\..\\\\z")
+ == "c:/x/z");
#else
- CHECK(Util::normalize_absolute_path("/") == "/");
- CHECK(Util::normalize_absolute_path("/.") == "/");
- CHECK(Util::normalize_absolute_path("/..") == "/");
- CHECK(Util::normalize_absolute_path("/./") == "/");
- CHECK(Util::normalize_absolute_path("//") == "/");
- CHECK(Util::normalize_absolute_path("/../x") == "/x");
- CHECK(Util::normalize_absolute_path("/x/./y/z") == "/x/y/z");
- CHECK(Util::normalize_absolute_path("/x/../y/z/") == "/y/z");
- CHECK(Util::normalize_absolute_path("/x/.../y/z") == "/x/.../y/z");
- CHECK(Util::normalize_absolute_path("/x/yyy/../zz") == "/x/zz");
- CHECK(Util::normalize_absolute_path("//x/yyy///.././zz") == "/x/zz");
+ CHECK(Util::normalize_abstract_absolute_path("/") == "/");
+ CHECK(Util::normalize_abstract_absolute_path("/.") == "/");
+ CHECK(Util::normalize_abstract_absolute_path("/..") == "/");
+ CHECK(Util::normalize_abstract_absolute_path("/./") == "/");
+ CHECK(Util::normalize_abstract_absolute_path("//") == "/");
+ CHECK(Util::normalize_abstract_absolute_path("/../x") == "/x");
+ CHECK(Util::normalize_abstract_absolute_path("/x/./y/z") == "/x/y/z");
+ CHECK(Util::normalize_abstract_absolute_path("/x/../y/z/") == "/y/z");
+ CHECK(Util::normalize_abstract_absolute_path("/x/.../y/z") == "/x/.../y/z");
+ CHECK(Util::normalize_abstract_absolute_path("/x/yyy/../zz") == "/x/zz");
+ CHECK(Util::normalize_abstract_absolute_path("//x/yyy///.././zz") == "/x/zz");
+#endif
+}
+
+TEST_CASE("Util::normalize_concrete_absolute_path")
+{
+#ifndef _WIN32
+ TestContext test_context;
+
+ util::write_file("file", "");
+ REQUIRE(Util::create_dir("dir1/dir2"));
+ REQUIRE(symlink("dir1/dir2", "symlink") == 0);
+ const auto cwd = Util::get_actual_cwd();
+
+ CHECK(Util::normalize_concrete_absolute_path(FMT("{}/file", cwd))
+ == FMT("{}/file", cwd));
+ CHECK(Util::normalize_concrete_absolute_path(FMT("{}/dir1/../file", cwd))
+ == FMT("{}/file", cwd));
+ CHECK(Util::normalize_concrete_absolute_path(FMT("{}/symlink/../file", cwd))
+ == FMT("{}/symlink/../file", cwd));
#endif
}
"invalid suffix (supported: d (day) and s (second)): \"2\"");
}
-TEST_CASE("Util::parse_signed")
-{
- CHECK(Util::parse_signed("0") == 0);
- CHECK(Util::parse_signed("2") == 2);
- CHECK(Util::parse_signed("-17") == -17);
- CHECK(Util::parse_signed("42") == 42);
- CHECK(Util::parse_signed("0666") == 666);
- CHECK(Util::parse_signed(" 777 ") == 777);
-
- CHECK_THROWS_WITH(Util::parse_signed(""), "invalid integer: \"\"");
- CHECK_THROWS_WITH(Util::parse_signed("x"), "invalid integer: \"x\"");
- CHECK_THROWS_WITH(Util::parse_signed("0x"), "invalid integer: \"0x\"");
- CHECK_THROWS_WITH(Util::parse_signed("0x4"), "invalid integer: \"0x4\"");
-
- // Custom description not used for invalid value.
- CHECK_THROWS_WITH(Util::parse_signed("apple", nullopt, nullopt, "banana"),
- "invalid integer: \"apple\"");
-
- // Boundary values.
- CHECK_THROWS_WITH(Util::parse_signed("-9223372036854775809"),
- "invalid integer: \"-9223372036854775809\"");
- CHECK(Util::parse_signed("-9223372036854775808") == INT64_MIN);
- CHECK(Util::parse_signed("9223372036854775807") == INT64_MAX);
- CHECK_THROWS_WITH(Util::parse_signed("9223372036854775808"),
- "invalid integer: \"9223372036854775808\"");
-
- // Min and max values.
- CHECK_THROWS_WITH(Util::parse_signed("-2", -1, 1),
- "integer must be between -1 and 1");
- CHECK(Util::parse_signed("-1", -1, 1) == -1);
- CHECK(Util::parse_signed("0", -1, 1) == 0);
- CHECK(Util::parse_signed("1", -1, 1) == 1);
- CHECK_THROWS_WITH(Util::parse_signed("2", -1, 1),
- "integer must be between -1 and 1");
-
- // Custom description used for boundary violation.
- CHECK_THROWS_WITH(Util::parse_signed("0", 1, 2, "banana"),
- "banana must be between 1 and 2");
-}
-
TEST_CASE("Util::parse_size")
{
CHECK(Util::parse_size("0") == 0);
CHECK_THROWS_WITH(Util::parse_size("10x"), "invalid size: \"10x\"");
}
-TEST_CASE("Util::parse_unsigned")
-{
- CHECK(Util::parse_unsigned("0") == 0);
- CHECK(Util::parse_unsigned("2") == 2);
- CHECK(Util::parse_unsigned("42") == 42);
- CHECK(Util::parse_unsigned("0666") == 666);
- CHECK(Util::parse_unsigned(" 777 ") == 777);
-
- CHECK_THROWS_WITH(Util::parse_unsigned(""), "invalid unsigned integer: \"\"");
- CHECK_THROWS_WITH(Util::parse_unsigned("x"),
- "invalid unsigned integer: \"x\"");
- CHECK_THROWS_WITH(Util::parse_unsigned("0x"),
- "invalid unsigned integer: \"0x\"");
- CHECK_THROWS_WITH(Util::parse_unsigned("0x4"),
- "invalid unsigned integer: \"0x4\"");
-
- // Custom description not used for invalid value.
- CHECK_THROWS_WITH(Util::parse_unsigned("apple", nullopt, nullopt, "banana"),
- "invalid unsigned integer: \"apple\"");
-
- // Boundary values.
- CHECK_THROWS_WITH(Util::parse_unsigned("-1"),
- "invalid unsigned integer: \"-1\"");
- CHECK(Util::parse_unsigned("0") == 0);
- CHECK(Util::parse_unsigned("18446744073709551615") == UINT64_MAX);
- CHECK_THROWS_WITH(Util::parse_unsigned("18446744073709551616"),
- "invalid unsigned integer: \"18446744073709551616\"");
-}
-
-TEST_CASE("Util::read_file and Util::write_file")
-{
- TestContext test_context;
-
- Util::write_file("test", "foo\nbar\n");
- std::string data = Util::read_file("test");
- CHECK(data == "foo\nbar\n");
-
- Util::write_file("test", "car");
- data = Util::read_file("test");
- CHECK(data == "car");
-
- Util::write_file("test", "pet", std::ios::app);
- data = Util::read_file("test");
- CHECK(data == "carpet");
-
- Util::write_file("test", "\n", std::ios::app | std::ios::binary);
- data = Util::read_file("test");
- CHECK(data == "carpet\n");
-
- Util::write_file("test", "\n", std::ios::app); // text mode
- data = Util::read_file("test");
-#ifdef _WIN32
- CHECK(data == "carpet\n\r\n");
-#else
- CHECK(data == "carpet\n\n");
-#endif
-
- Util::write_file("size_hint_test", std::string(8192, '\0'));
- CHECK(Util::read_file("size_hint_test", 4096 /*size_hint*/).size() == 8192);
-
- CHECK_THROWS_WITH(Util::read_file("does/not/exist"),
- "No such file or directory");
-
- CHECK_THROWS_WITH(Util::write_file("", "does/not/exist"),
- "No such file or directory");
-
- CHECK_THROWS_WITH(Util::write_file("does/not/exist", "does/not/exist"),
- "No such file or directory");
-}
-
TEST_CASE("Util::remove_extension")
{
CHECK(Util::remove_extension("") == "");
CHECK(Util::remove_extension("/foo/bar/f.abc.txt") == "/foo/bar/f.abc");
}
-TEST_CASE("Util::same_program_name")
-{
- CHECK(Util::same_program_name("foo", "foo"));
-#ifdef _WIN32
- CHECK(Util::same_program_name("FOO", "foo"));
- CHECK(Util::same_program_name("FOO.exe", "foo"));
-#else
- CHECK(!Util::same_program_name("FOO", "foo"));
- CHECK(!Util::same_program_name("FOO.exe", "foo"));
-#endif
-}
-
-TEST_CASE("Util::split_into_views")
-{
- {
- CHECK(Util::split_into_views("", "/").empty());
- }
- {
- CHECK(Util::split_into_views("///", "/").empty());
- }
- {
- auto s = Util::split_into_views("a/b", "/");
- REQUIRE(s.size() == 2);
- CHECK(s.at(0) == "a");
- CHECK(s.at(1) == "b");
- }
- {
- auto s = Util::split_into_views("a/b", "x");
- REQUIRE(s.size() == 1);
- CHECK(s.at(0) == "a/b");
- }
- {
- auto s = Util::split_into_views("a/b:c", "/:");
- REQUIRE(s.size() == 3);
- CHECK(s.at(0) == "a");
- CHECK(s.at(1) == "b");
- CHECK(s.at(2) == "c");
- }
- {
- auto s = Util::split_into_views(":a//b..:.c/:/.", "/:.");
- REQUIRE(s.size() == 3);
- CHECK(s.at(0) == "a");
- CHECK(s.at(1) == "b");
- CHECK(s.at(2) == "c");
- }
- {
- auto s = Util::split_into_views(".0.1.2.3.4.5.6.7.8.9.", "/:.+_abcdef");
- REQUIRE(s.size() == 10);
- CHECK(s.at(0) == "0");
- CHECK(s.at(9) == "9");
- }
-}
-
-TEST_CASE("Util::split_into_strings")
-{
- {
- CHECK(Util::split_into_strings("", "/").empty());
- }
- {
- CHECK(Util::split_into_strings("///", "/").empty());
- }
- {
- auto s = Util::split_into_strings("a/b", "/");
- REQUIRE(s.size() == 2);
- CHECK(s.at(0) == "a");
- CHECK(s.at(1) == "b");
- }
- {
- auto s = Util::split_into_strings("a/b", "x");
- REQUIRE(s.size() == 1);
- CHECK(s.at(0) == "a/b");
- }
- {
- auto s = Util::split_into_strings("a/b:c", "/:");
- REQUIRE(s.size() == 3);
- CHECK(s.at(0) == "a");
- CHECK(s.at(1) == "b");
- CHECK(s.at(2) == "c");
- }
- {
- auto s = Util::split_into_strings(":a//b..:.c/:/.", "/:.");
- REQUIRE(s.size() == 3);
- CHECK(s.at(0) == "a");
- CHECK(s.at(1) == "b");
- CHECK(s.at(2) == "c");
- }
- {
- auto s = Util::split_into_strings(".0.1.2.3.4.5.6.7.8.9.", "/:.+_abcdef");
- REQUIRE(s.size() == 10);
- CHECK(s.at(0) == "0");
- CHECK(s.at(9) == "9");
- }
-}
-
-TEST_CASE("Util::starts_with")
-{
- // starts_with(const char*, string_view)
- CHECK(Util::starts_with("", ""));
- CHECK(Util::starts_with("x", ""));
- CHECK(Util::starts_with("x", "x"));
- CHECK(Util::starts_with("xy", ""));
- CHECK(Util::starts_with("xy", "x"));
- CHECK(Util::starts_with("xy", "xy"));
- CHECK(Util::starts_with("xyz", ""));
- CHECK(Util::starts_with("xyz", "x"));
- CHECK(Util::starts_with("xyz", "xy"));
- CHECK(Util::starts_with("xyz", "xyz"));
-
- CHECK_FALSE(Util::starts_with("", "x"));
- CHECK_FALSE(Util::starts_with("x", "y"));
- CHECK_FALSE(Util::starts_with("x", "xy"));
-
- // starts_with(string_view, string_view)
- CHECK(Util::starts_with(std::string(""), ""));
- CHECK(Util::starts_with(std::string("x"), ""));
- CHECK(Util::starts_with(std::string("x"), "x"));
- CHECK(Util::starts_with(std::string("xy"), ""));
- CHECK(Util::starts_with(std::string("xy"), "x"));
- CHECK(Util::starts_with(std::string("xy"), "xy"));
- CHECK(Util::starts_with(std::string("xyz"), ""));
- CHECK(Util::starts_with(std::string("xyz"), "x"));
- CHECK(Util::starts_with(std::string("xyz"), "xy"));
- CHECK(Util::starts_with(std::string("xyz"), "xyz"));
-
- CHECK_FALSE(Util::starts_with(std::string(""), "x"));
- CHECK_FALSE(Util::starts_with(std::string("x"), "y"));
- CHECK_FALSE(Util::starts_with(std::string("x"), "xy"));
-}
-
-TEST_CASE("Util::strip_whitespace")
-{
- CHECK(Util::strip_whitespace("") == "");
- CHECK(Util::strip_whitespace("x") == "x");
- CHECK(Util::strip_whitespace(" x") == "x");
- CHECK(Util::strip_whitespace("x ") == "x");
- CHECK(Util::strip_whitespace(" x ") == "x");
- CHECK(Util::strip_whitespace(" \n\tx \n\t") == "x");
- CHECK(Util::strip_whitespace(" x y ") == "x y");
-}
+// Util::split_into_strings and Util::split_into_views are tested implicitly in
+// test_util_Tokenizer.cpp.
TEST_CASE("Util::to_lowercase")
{
TestContext test_context;
REQUIRE(Util::create_dir("dir-with-subdir-and-file/subdir"));
- Util::write_file("dir-with-subdir-and-file/subdir/f", "");
+ util::write_file("dir-with-subdir-and-file/subdir/f", "");
REQUIRE(Util::create_dir("dir-with-files"));
- Util::write_file("dir-with-files/f1", "");
- Util::write_file("dir-with-files/f2", "");
+ util::write_file("dir-with-files/f1", "");
+ util::write_file("dir-with-files/f2", "");
REQUIRE(Util::create_dir("empty-dir"));
std::vector<std::string> visited;
SUBCASE("Wipe file")
{
- Util::write_file("a", "");
+ util::write_file("a", "");
CHECK_NOTHROW(Util::wipe_path("a"));
CHECK(!Stat::stat("a"));
}
SUBCASE("Wipe directory")
{
REQUIRE(Util::create_dir("a/b"));
- Util::write_file("a/1", "");
- Util::write_file("a/b/1", "");
+ util::write_file("a/1", "");
+ util::write_file("a/b/1", "");
CHECK_NOTHROW(Util::wipe_path("a"));
CHECK(!Stat::stat("a"));
}
#include "third_party/doctest.h"
+#include <iostream>
+
TEST_SUITE_BEGIN("Win32Util");
TEST_CASE("Win32Util::argv_to_string")
+++ /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
-
-#include "../src/Compression.hpp"
-#include "../src/Compressor.hpp"
-#include "../src/Decompressor.hpp"
-#include "../src/File.hpp"
-#include "TestUtil.hpp"
-
-#include "third_party/doctest.h"
-
-using TestUtil::TestContext;
-
-TEST_SUITE_BEGIN("ZstdCompression");
-
-TEST_CASE("Small Compression::Type::zstd roundtrip")
-{
- TestContext test_context;
-
- File f("data.zstd", "wb");
- auto compressor =
- Compressor::create_from_type(Compression::Type::zstd, f.get(), 1);
- CHECK(compressor->actual_compression_level() == 1);
- compressor->write("foobar", 6);
- compressor->finalize();
-
- f.open("data.zstd", "rb");
- auto decompressor =
- Decompressor::create_from_type(Compression::Type::zstd, f.get());
-
- char buffer[4];
- decompressor->read(buffer, 4);
- CHECK(memcmp(buffer, "foob", 4) == 0);
-
- // Not reached the end.
- CHECK_THROWS_WITH(decompressor->finalize(),
- "garbage data at end of zstd input stream");
-
- decompressor->read(buffer, 2);
- CHECK(memcmp(buffer, "ar", 2) == 0);
-
- // Reached the end.
- decompressor->finalize();
-
- // Nothing left to read.
- CHECK_THROWS_WITH(decompressor->read(buffer, 1),
- "failed to read from zstd input stream");
-}
-
-TEST_CASE("Large compressible Compression::Type::zstd roundtrip")
-{
- TestContext test_context;
-
- char data[] = "The quick brown fox jumps over the lazy dog";
-
- File f("data.zstd", "wb");
- auto compressor =
- Compressor::create_from_type(Compression::Type::zstd, f.get(), 1);
- for (size_t i = 0; i < 1000; i++) {
- compressor->write(data, sizeof(data));
- }
- compressor->finalize();
-
- f.open("data.zstd", "rb");
- auto decompressor =
- Decompressor::create_from_type(Compression::Type::zstd, f.get());
-
- char buffer[sizeof(data)];
- for (size_t i = 0; i < 1000; i++) {
- decompressor->read(buffer, sizeof(buffer));
- CHECK(memcmp(buffer, data, sizeof(data)) == 0);
- }
-
- // Reached the end.
- decompressor->finalize();
-
- // Nothing left to read.
- CHECK_THROWS_WITH(decompressor->read(buffer, 1),
- "failed to read from zstd input stream");
-}
-
-TEST_CASE("Large uncompressible Compression::Type::zstd roundtrip")
-{
- TestContext test_context;
-
- char data[100000];
- for (char& c : data) {
- c = rand() % 256;
- }
-
- File f("data.zstd", "wb");
- auto compressor =
- Compressor::create_from_type(Compression::Type::zstd, f.get(), 1);
- compressor->write(data, sizeof(data));
- compressor->finalize();
-
- f.open("data.zstd", "rb");
- auto decompressor =
- Decompressor::create_from_type(Compression::Type::zstd, f.get());
-
- char buffer[sizeof(data)];
- decompressor->read(buffer, sizeof(buffer));
- CHECK(memcmp(buffer, data, sizeof(data)) == 0);
-
- decompressor->finalize();
-}
-
-TEST_SUITE_END();
-// Copyright (C) 2010-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "../src/Args.hpp"
#include "../src/Config.hpp"
#include "../src/Context.hpp"
-#include "../src/Statistic.hpp"
#include "../src/Util.hpp"
#include "../src/fmtmacros.hpp"
#include "TestUtil.hpp"
#include "argprocessing.hpp"
+#include <core/Statistic.hpp>
+#include <core/wincompat.hpp>
+#include <util/file.hpp>
+
#include "third_party/doctest.h"
#include <algorithm>
+using core::Statistic;
using TestUtil::TestContext;
namespace {
Context ctx;
ctx.orig_args = Args::from_string("cc -c foo.c -fsyntax-only");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
Context ctx;
ctx.orig_args = Args::from_string("cc -c foo.c -E");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
CHECK(process_args(ctx).error == Statistic::called_for_preprocessing);
}
Context ctx;
ctx.orig_args = Args::from_string("cc -c foo.c -M");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
CHECK(process_args(ctx).error == Statistic::unsupported_compiler_option);
}
{
TestContext test_context;
const std::string dep_args =
- "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MD,wpmd"
- " -Wp,-MMD,wpmmd -Wp,-MP -Wp,-MT,wpmt -Wp,-MQ,wpmq -Wp,-MF,wpf";
+ "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MP"
+ " -Wp,-MT,wpmt -Wp,-MQ,wpmq -Wp,-MF,wpf";
Context ctx;
ctx.orig_args = Args::from_string("cc " + dep_args + " -c foo.c -o foo.o");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
ctx.config.set_run_second_cpp(false);
const ProcessArgsResult result = process_args(ctx);
{
TestContext test_context;
const std::string dep_args =
- "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MD,wpmd"
- " -Wp,-MMD,wpmmd -Wp,-MP -Wp,-MT,wpmt -Wp,-MQ,wpmq -Wp,-MF,wpf";
+ "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MP"
+ " -Wp,-MT,wpmt -Wp,-MQ,wpmq -Wp,-MF,wpf";
Context ctx;
ctx.orig_args = Args::from_string("cc " + dep_args + " -c foo.c -o foo.o");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
" -iwithprefix . -iwithprefixbefore . -DTEST_MACRO -DTEST_MACRO2=1 -F."
" -trigraphs -fworking-directory -fno-working-directory";
const std::string dep_args =
- "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MD,wpmd"
- " -Wp,-MMD,wpmmd -Wp,-MP -Wp,-MT,wpmt -Wp,-MQ,wpmq -Wp,-MF,wpf";
+ "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MP"
+ " -Wp,-MT,wpmt -Wp,-MQ,wpmq -Wp,-MF,wpf";
Context ctx;
ctx.orig_args =
Args::from_string("cc " + cpp_args + " " + dep_args + " -c foo.c -o foo.o");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
ctx.config.set_run_second_cpp(false);
const ProcessArgsResult result = process_args(ctx);
" -iwithprefix . -iwithprefixbefore . -DTEST_MACRO -DTEST_MACRO2=1 -F."
" -trigraphs -fworking-directory -fno-working-directory";
const std::string dep_args =
- "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MD,wpmd"
- " -Wp,-MMD,wpmmd";
+ "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2";
Context ctx;
ctx.orig_args =
Args::from_string("cc " + cpp_args + " " + dep_args + " -c foo.c -o foo.o");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
const std::string dep_args = "-MMD -MFfoo.d -MT mt -MTmt -MQmq";
Context ctx;
ctx.orig_args = Args::from_string("cc -c " + dep_args + " foo.c -o foo.o");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("cc -c -MD foo.c -MF foo.d -o foo.o");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("cc -c -MD foo.c -MF foo.d -o foo.o");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
ctx.config.set_run_second_cpp(false);
const ProcessArgsResult result = process_args(ctx);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("cc -c -MD foo.c -o foo.o");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
ctx.config.set_run_second_cpp(false);
const ProcessArgsResult result = process_args(ctx);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("cc -c -MD foo.c -o foo.o");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("cc -c -MF=path foo.c -o foo.o");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
Context ctx;
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
ctx.config.set_base_dir(get_root());
std::string arg_string =
FMT("cc --sysroot={}/foo/bar -c foo.c", ctx.actual_cwd);
Context ctx;
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
ctx.config.set_base_dir(get_root());
std::string arg_string = FMT("cc --sysroot {}/foo -c foo.c", ctx.actual_cwd);
ctx.orig_args = Args::from_string(arg_string);
ctx.orig_args =
Args::from_string("cc -c foo.c -o foo.o -MMD -MT bar -MFfoo.d");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
CHECK(!result.error);
ctx.orig_args =
Args::from_string("cc -c foo.c -o foo.o -MMD -MFfoo.d -MT foo -MTbar");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
CHECK(!result.error);
ctx.orig_args =
Args::from_string("cc -c foo.c -o foo.o -MMD -MFfoo.d -MQ foo -MQbar");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
CHECK(!result.error);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("gcc -c -MD -MP -MFfoo.d -MQ foo.d foo.c");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("gcc -c -MD -MP -MFfoo.d -MT foo.d foo.c");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("gcc -c -MD -MP -MFfoo.d -MQfoo.d foo.c");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("gcc -c -MD -MP -MFfoo.d -MTfoo.d foo.c");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
Context ctx;
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
ctx.config.set_base_dir(get_root());
std::string arg_string = FMT("cc -isystem {}/foo -c foo.c", ctx.actual_cwd);
ctx.orig_args = Args::from_string(arg_string);
Context ctx;
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
ctx.config.set_base_dir("/"); // posix
// Windows path doesn't work concatenated.
std::string cwd = get_posix_path(ctx.actual_cwd);
Context ctx;
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
ctx.config.set_base_dir("/"); // posix
// Windows path doesn't work concatenated.
std::string cwd = get_posix_path(ctx.actual_cwd);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("cc -g1 -gsplit-dwarf foo.c -c");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
TestContext test_context;
Context ctx;
ctx.orig_args = Args::from_string("cc -gsplit-dwarf -g1 foo.c -c");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
Context ctx;
ctx.orig_args = Args::from_string(
"cc -Wa,foo foo.c -g -c -DX -Werror -Xlinker fie -Xlinker,fum -Wno-error");
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
Context ctx;
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");
- Util::write_file("bar.optf", "out -DX");
+ util::write_file("foo.c", "");
+ util::write_file("foo.optf", "-c foo.c -g -Wall -o");
+ util::write_file("bar.optf", "out -DX");
const ProcessArgsResult result = process_args(ctx);
{
TestContext test_context;
Context ctx;
+ ctx.config.set_compiler_type(CompilerType::clang);
+
const std::string common_args =
"-Xclang -fno-pch-timestamp"
" -Xclang unsupported";
" -Xclang -include-pth -Xclang pth_path2";
ctx.orig_args =
- Args::from_string("gcc -c foo.c " + common_args + " " + color_diag + " "
+ Args::from_string("clang -c foo.c " + common_args + " " + color_diag + " "
+ extra_args + " " + pch_pth_variants);
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
CHECK(result.preprocessor_args.to_string()
- == "gcc " + common_args + " " + pch_pth_variants);
+ == "clang " + common_args + " " + pch_pth_variants);
CHECK(result.extra_args_to_hash.to_string() == extra_args);
CHECK(result.compiler_args.to_string()
- == "gcc " + common_args + " " + color_diag + " " + extra_args + " "
- + pch_pth_variants + " -c");
+ == "clang " + common_args + " " + color_diag + " " + extra_args + " "
+ + pch_pth_variants + " -c -fcolor-diagnostics");
}
TEST_CASE("-x")
{
TestContext test_context;
Context ctx;
- Util::write_file("foo.c", "");
+ util::write_file("foo.c", "");
SUBCASE("intel option")
{
-// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "../src/Finalizer.hpp"
#include "TestUtil.hpp"
+#include <core/wincompat.hpp>
+
#include "third_party/doctest.h"
#include "third_party/win32/mktemp.h"
+#include <sddl.h>
+
#include <algorithm>
#include <memory>
#include <ostream>
-#include <sddl.h>
#include <utility>
using TestUtil::TestContext;
} // namespace
-TEST_SUITE_BEGIN("bsd_mkstemp");
+TEST_SUITE_BEGIN("bsd_mkstemps");
-TEST_CASE("bsd_mkstemp")
+TEST_CASE("bsd_mkstemps")
{
TestContext test_context;
SUBCASE("successful")
{
std::string path = "XXXXXX";
- CHECK_MESSAGE(Fd(bsd_mkstemp(&path[0])), "errno=" << errno);
+ Fd fd(bsd_mkstemps(&path[0], 0));
+ CHECK_MESSAGE(fd, "errno=" << errno);
CHECK(path == "AAAAAA");
}
+ SUBCASE("successful with suffix")
+ {
+ std::string path = "XXXXXX123";
+ Fd fd(bsd_mkstemps(&path[0], 3));
+ CHECK_MESSAGE(fd, "errno=" << errno);
+ CHECK(path == "AAAAAA123");
+ }
+
SUBCASE("existing file")
{
- CHECK_MESSAGE(ScopedHANDLE(CreateFileA("AAAAAA",
- GENERIC_READ | GENERIC_WRITE,
- 0,
- nullptr,
- CREATE_NEW,
- FILE_ATTRIBUTE_NORMAL,
- nullptr)),
- "errno=" << errno);
+ ScopedHANDLE handle(CreateFileA("AAAAAA",
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ nullptr,
+ CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL,
+ nullptr));
+ CHECK_MESSAGE(handle, "errno=" << errno);
std::string path = "XXXXXX";
- CHECK_MESSAGE(Fd(bsd_mkstemp(&path[0])), "errno=" << errno);
+ Fd fd(bsd_mkstemps(&path[0], 0));
+ CHECK_MESSAGE(fd, "errno=" << errno);
CHECK(path == "BBBBBB");
}
"errno=" << errno);
std::string path = "XXXXXX";
- CHECK_MESSAGE(Fd(bsd_mkstemp(&path[0])), "errno=" << errno);
+ Fd fd(bsd_mkstemps(&path[0], 0));
+ CHECK_MESSAGE(fd, "errno=" << errno);
CHECK(path == "BBBBBB");
}
CHECK_MESSAGE(CreateDirectoryA("AAAAAA", nullptr), "errno=" << errno);
std::string path = "XXXXXX";
- CHECK_MESSAGE(Fd(bsd_mkstemp(&path[0])), "errno=" << errno);
+ Fd fd(bsd_mkstemps(&path[0], 0));
+ CHECK_MESSAGE(fd, "errno=" << errno);
CHECK(path == "BBBBBB");
}
if (!broken_acls) {
std::string path = "my_readonly_dir/XXXXXX";
- CHECK(!Fd(bsd_mkstemp(&path[0])));
+ CHECK(!Fd(bsd_mkstemps(&path[0], 0)));
CHECK(errno == EACCES);
} else {
MESSAGE("ACLs do not appear to function properly on this filesystem");
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "../src/Context.hpp"
-#include "../src/Sloppiness.hpp"
#include "../src/ccache.hpp"
#include "../src/fmtmacros.hpp"
#include "TestUtil.hpp"
+#include <core/wincompat.hpp>
+#include <util/file.hpp>
+
#include "third_party/doctest.h"
-#include "third_party/nonstd/optional.hpp"
-#ifdef MYNAME
-# define CCACHE_NAME MYNAME
-#else
-# define CCACHE_NAME "ccache"
+#include <optional>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
#endif
using TestUtil::TestContext;
const char* find_executable_return_string = nullptr)
{
const auto find_executable_stub =
- [&find_executable_return_string](
- const Context&, const std::string& s, const std::string&) -> std::string {
- return find_executable_return_string ? find_executable_return_string
- : "resolved_" + s;
- };
+ [&find_executable_return_string](const auto&, const auto& s, const auto&) {
+ return find_executable_return_string ? find_executable_return_string
+ : "resolved_" + s;
+ };
Context ctx;
ctx.config.set_compiler(config_compiler);
// In case the first parameter is ccache, resolve the second parameter to
// the real compiler unless it's a relative or absolute path.
- CHECK(helper(CCACHE_NAME " gcc", "") == "resolved_gcc");
- CHECK(helper(CCACHE_NAME " rel/gcc", "") == "rel/gcc");
- CHECK(helper(CCACHE_NAME " /abs/gcc", "") == "/abs/gcc");
+ CHECK(helper("ccache gcc", "") == "resolved_gcc");
+ CHECK(helper("ccache rel/gcc", "") == "rel/gcc");
+ CHECK(helper("ccache /abs/gcc", "") == "/abs/gcc");
- CHECK(helper("rel/" CCACHE_NAME " gcc", "") == "resolved_gcc");
- CHECK(helper("rel/" CCACHE_NAME " rel/gcc", "") == "rel/gcc");
- CHECK(helper("rel/" CCACHE_NAME " /abs/gcc", "") == "/abs/gcc");
+ CHECK(helper("rel/ccache gcc", "") == "resolved_gcc");
+ CHECK(helper("rel/ccache rel/gcc", "") == "rel/gcc");
+ CHECK(helper("rel/ccache /abs/gcc", "") == "/abs/gcc");
- CHECK(helper("/abs/" CCACHE_NAME " gcc", "") == "resolved_gcc");
- CHECK(helper("/abs/" CCACHE_NAME " rel/gcc", "") == "rel/gcc");
- CHECK(helper("/abs/" CCACHE_NAME " /abs/gcc", "") == "/abs/gcc");
+ CHECK(helper("/abs/ccache gcc", "") == "resolved_gcc");
+ CHECK(helper("/abs/ccache rel/gcc", "") == "rel/gcc");
+ CHECK(helper("/abs/ccache /abs/gcc", "") == "/abs/gcc");
// If gcc points back to ccache throw, unless either ccache or gcc is a
// relative or absolute path.
- CHECK_THROWS(helper(CCACHE_NAME " gcc", "", CCACHE_NAME));
- CHECK(helper(CCACHE_NAME " rel/gcc", "", CCACHE_NAME) == "rel/gcc");
- CHECK(helper(CCACHE_NAME " /abs/gcc", "", CCACHE_NAME) == "/abs/gcc");
+ CHECK_THROWS(helper("ccache gcc", "", "ccache"));
+ CHECK(helper("ccache rel/gcc", "", "ccache") == "rel/gcc");
+ CHECK(helper("ccache /abs/gcc", "", "ccache") == "/abs/gcc");
- CHECK_THROWS(helper("rel/" CCACHE_NAME " gcc", "", CCACHE_NAME));
- CHECK(helper("rel/" CCACHE_NAME " rel/gcc", "", CCACHE_NAME) == "rel/gcc");
- CHECK(helper("rel/" CCACHE_NAME " /a/gcc", "", CCACHE_NAME) == "/a/gcc");
+ CHECK_THROWS(helper("rel/ccache gcc", "", "ccache"));
+ CHECK(helper("rel/ccache rel/gcc", "", "ccache") == "rel/gcc");
+ CHECK(helper("rel/ccache /a/gcc", "", "ccache") == "/a/gcc");
- CHECK_THROWS(helper("/abs/" CCACHE_NAME " gcc", "", CCACHE_NAME));
- CHECK(helper("/abs/" CCACHE_NAME " rel/gcc", "", CCACHE_NAME) == "rel/gcc");
- CHECK(helper("/abs/" CCACHE_NAME " /a/gcc", "", CCACHE_NAME) == "/a/gcc");
+ CHECK_THROWS(helper("/abs/ccache gcc", "", "ccache"));
+ CHECK(helper("/abs/ccache rel/gcc", "", "ccache") == "rel/gcc");
+ CHECK(helper("/abs/ccache /a/gcc", "", "ccache") == "/a/gcc");
// If compiler is not found then throw, unless the compiler has a relative
// or absolute path.
- CHECK_THROWS(helper(CCACHE_NAME " gcc", "", ""));
- CHECK(helper(CCACHE_NAME " rel/gcc", "", "") == "rel/gcc");
- CHECK(helper(CCACHE_NAME " /abs/gcc", "", "") == "/abs/gcc");
+ CHECK_THROWS(helper("ccache gcc", "", ""));
+ CHECK(helper("ccache rel/gcc", "", "") == "rel/gcc");
+ CHECK(helper("ccache /abs/gcc", "", "") == "/abs/gcc");
- CHECK_THROWS(helper("rel/" CCACHE_NAME " gcc", "", ""));
- CHECK(helper("rel/" CCACHE_NAME " rel/gcc", "", "") == "rel/gcc");
- CHECK(helper("rel/" CCACHE_NAME " /abs/gcc", "", "") == "/abs/gcc");
+ CHECK_THROWS(helper("rel/ccache gcc", "", ""));
+ CHECK(helper("rel/ccache rel/gcc", "", "") == "rel/gcc");
+ CHECK(helper("rel/ccache /abs/gcc", "", "") == "/abs/gcc");
- CHECK_THROWS(helper("/abs/" CCACHE_NAME " gcc", "", ""));
- CHECK(helper("/abs/" CCACHE_NAME " rel/gcc", "", "") == "rel/gcc");
- CHECK(helper("/abs/" CCACHE_NAME " /abs/gcc", "", "") == "/abs/gcc");
+ CHECK_THROWS(helper("/abs/ccache gcc", "", ""));
+ CHECK(helper("/abs/ccache rel/gcc", "", "") == "rel/gcc");
+ CHECK(helper("/abs/ccache /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");
+ CHECK(helper("ccache gcc", "") == "resolved_gcc");
+ CHECK(helper("ccache ccache gcc", "") == "resolved_gcc");
+ CHECK(helper("ccache ccache-1.2.3 ccache gcc", "") == "resolved_gcc");
}
SUBCASE("config")
// In case the first parameter is ccache, use the configuration value. Don't
// resolve configuration value if it's a relative or absolute path.
- CHECK(helper(CCACHE_NAME " gcc", "config") == "resolved_config");
- CHECK(helper(CCACHE_NAME " gcc", "rel/config") == "rel/config");
- CHECK(helper(CCACHE_NAME " gcc", "/abs/config") == "/abs/config");
- CHECK(helper(CCACHE_NAME " rel/gcc", "config") == "resolved_config");
- CHECK(helper(CCACHE_NAME " /abs/gcc", "config") == "resolved_config");
+ CHECK(helper("ccache gcc", "config") == "resolved_config");
+ CHECK(helper("ccache gcc", "rel/config") == "rel/config");
+ CHECK(helper("ccache gcc", "/abs/config") == "/abs/config");
+ CHECK(helper("ccache rel/gcc", "config") == "resolved_config");
+ CHECK(helper("ccache /abs/gcc", "config") == "resolved_config");
// Same as above with relative path to ccache.
- CHECK(helper("r/" CCACHE_NAME " gcc", "conf") == "resolved_conf");
- CHECK(helper("r/" CCACHE_NAME " gcc", "rel/conf") == "rel/conf");
- CHECK(helper("r/" CCACHE_NAME " gcc", "/abs/conf") == "/abs/conf");
- CHECK(helper("r/" CCACHE_NAME " rel/gcc", "conf") == "resolved_conf");
- CHECK(helper("r/" CCACHE_NAME " /abs/gcc", "conf") == "resolved_conf");
+ CHECK(helper("r/ccache gcc", "conf") == "resolved_conf");
+ CHECK(helper("r/ccache gcc", "rel/conf") == "rel/conf");
+ CHECK(helper("r/ccache gcc", "/abs/conf") == "/abs/conf");
+ CHECK(helper("r/ccache rel/gcc", "conf") == "resolved_conf");
+ CHECK(helper("r/ccache /abs/gcc", "conf") == "resolved_conf");
// Same as above with absolute path to ccache.
- CHECK(helper("/a/" CCACHE_NAME " gcc", "conf") == "resolved_conf");
- CHECK(helper("/a/" CCACHE_NAME " gcc", "rel/conf") == "rel/conf");
- CHECK(helper("/a/" CCACHE_NAME " gcc", "/a/conf") == "/a/conf");
- CHECK(helper("/a/" CCACHE_NAME " rel/gcc", "conf") == "resolved_conf");
- CHECK(helper("/a/" CCACHE_NAME " /abs/gcc", "conf") == "resolved_conf");
+ CHECK(helper("/a/ccache gcc", "conf") == "resolved_conf");
+ CHECK(helper("/a/ccache gcc", "rel/conf") == "rel/conf");
+ CHECK(helper("/a/ccache gcc", "/a/conf") == "/a/conf");
+ CHECK(helper("/a/ccache rel/gcc", "conf") == "resolved_conf");
+ CHECK(helper("/a/ccache /abs/gcc", "conf") == "resolved_conf");
}
}
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("Follow symlink to actual compiler")
{
const auto cwd = Util::get_actual_cwd();
- Util::write_file(FMT("{}/gcc", 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);
--- /dev/null
+// Copyright (C) 2019-2022 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/Config.hpp"
+
+#include <core/types.hpp>
+
+#include "third_party/doctest.h"
+
+TEST_SUITE_BEGIN("compression");
+
+TEST_CASE("compression_level_from_config")
+{
+ Config config;
+ CHECK(core::compression_level_from_config(config) == 0);
+}
+
+TEST_CASE("compression_type_from_config")
+{
+ Config config;
+ CHECK(core::compression_type_from_config(config)
+ == core::CompressionType::zstd);
+}
+
+TEST_CASE("compression_type_from_int")
+{
+ CHECK(core::compression_type_from_int(0) == core::CompressionType::none);
+ CHECK(core::compression_type_from_int(1) == core::CompressionType::zstd);
+ CHECK_THROWS_WITH(core::compression_type_from_int(2), "Unknown type: 2");
+}
+
+TEST_CASE("to_string(CompressionType)")
+{
+ CHECK(core::to_string(core::CompressionType::none) == "none");
+ CHECK(core::to_string(core::CompressionType::zstd) == "zstd");
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2020-2022 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/core/MsvcShowIncludesOutput.hpp"
+#include "../src/util/string.hpp"
+#include "TestUtil.hpp"
+
+#include "third_party/doctest.h"
+
+static const std::string defaultPrefix = "Note: including file:";
+
+TEST_SUITE_BEGIN("MsvcShowIncludesOutput");
+
+TEST_CASE("MsvcShowIncludesOutput::get_includes")
+{
+ SUBCASE("Parse empty output")
+ {
+ std::string contents;
+ const auto result =
+ core::MsvcShowIncludesOutput::get_includes(contents, defaultPrefix);
+ CHECK(result.size() == 0);
+ }
+
+ SUBCASE("Parse real output")
+ {
+ std::string contents = R"(Just a line
+Note: including file: F:/Projects/ccache/build-msvc/config.h
+Note: including file: F:\Projects\ccache\unittest\../src/Context.hpp
+Note: including file: F:\Projects\ccache\src\Args.hpp
+Note: including file: F:\Projects\ccache\src\NonCopyable.hpp
+Note: including file: C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.33.31629\include\deque
+)";
+ const auto result =
+ core::MsvcShowIncludesOutput::get_includes(contents, defaultPrefix);
+ REQUIRE(result.size() == 5);
+ CHECK(result[0] == "F:/Projects/ccache/build-msvc/config.h");
+ CHECK(result[1] == R"(F:\Projects\ccache\unittest\../src/Context.hpp)");
+ CHECK(result[2] == R"(F:\Projects\ccache\src\Args.hpp)");
+ CHECK(result[3] == R"(F:\Projects\ccache\src\NonCopyable.hpp)");
+ CHECK(
+ result[4]
+ == R"(C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.33.31629\include\deque)");
+ }
+
+ SUBCASE("Parse output with CRLF")
+ {
+ std::string contents =
+ "Note: including file: foo\r\n"
+ "Note: including file: bar\r\n";
+ const auto result =
+ core::MsvcShowIncludesOutput::get_includes(contents, defaultPrefix);
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "foo");
+ CHECK(result[1] == "bar");
+ }
+
+ SUBCASE("Parse output with an empty entry")
+ {
+ std::string contents =
+ "Note: including file: foo\n"
+ "Note: including file: \n"
+ "Note: including file: bar\n";
+ const auto result =
+ core::MsvcShowIncludesOutput::get_includes(contents, defaultPrefix);
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "foo");
+ CHECK(result[1] == "bar");
+ }
+
+ SUBCASE("Parse output with a custom prefix")
+ {
+ std::string contents = R"(custom foo
+custom bar
+Just a line with custom in the middle)";
+ const auto result =
+ core::MsvcShowIncludesOutput::get_includes(contents, "custom");
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == "foo");
+ CHECK(result[1] == "bar");
+ }
+}
+
+TEST_CASE("MsvcShowIncludesOutput::strip_includes")
+{
+ Context ctx;
+ const util::Bytes input = util::to_span(
+ "First\n"
+ "Note: including file: foo\n"
+ "Second\n");
+
+ SUBCASE("Empty output")
+ {
+ const util::Bytes result =
+ core::MsvcShowIncludesOutput::strip_includes(ctx, {});
+ CHECK(result.size() == 0);
+ }
+
+ SUBCASE("Feature disabled")
+ {
+ const util::Bytes result =
+ core::MsvcShowIncludesOutput::strip_includes(ctx, util::Bytes(input));
+ CHECK(result == input);
+ }
+
+ ctx.auto_depend_mode = true;
+
+ SUBCASE("Wrong compiler")
+ {
+ const util::Bytes result =
+ core::MsvcShowIncludesOutput::strip_includes(ctx, util::Bytes(input));
+ CHECK(result == input);
+ }
+
+ ctx.config.set_compiler_type(CompilerType::msvc);
+
+ SUBCASE("Simple output")
+ {
+ const util::Bytes result =
+ core::MsvcShowIncludesOutput::strip_includes(ctx, util::Bytes(input));
+ CHECK(result == util::to_span("First\nSecond\n"));
+ }
+
+ SUBCASE("Empty lines")
+ {
+ const util::Bytes result = core::MsvcShowIncludesOutput::strip_includes(
+ ctx,
+ util::to_span("First\n"
+ "\n"
+ "Note: including file: foo\n"
+ "\n"
+ "Second\n"
+ "\n"));
+ CHECK(result == util::to_span("First\n\n\nSecond\n\n"));
+ }
+
+ SUBCASE("CRLF")
+ {
+ const util::Bytes result = core::MsvcShowIncludesOutput::strip_includes(
+ ctx,
+ util::to_span("First\r\n"
+ "Note: including file: foo\r\n"
+ "Second\r\n"));
+ CHECK(result == util::to_span("First\r\nSecond\r\n"));
+ }
+
+ SUBCASE("Custom prefix")
+ {
+ ctx.config.set_msvc_dep_prefix("custom");
+ const util::Bytes result = core::MsvcShowIncludesOutput::strip_includes(
+ ctx,
+ util::to_span("First\n"
+ "custom: including file: foo\n"
+ "Second\n"
+ "Third custom line\n"));
+ CHECK(result == util::to_span("First\nSecond\nThird custom line\n"));
+ }
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2011-2021 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 "TestUtil.hpp"
+
+#include <core/Statistic.hpp>
+#include <core/Statistics.hpp>
+
+#include <third_party/doctest.h>
+
+#include <iostream> // macOS bug: https://github.com/onqtam/doctest/issues/126
+
+using core::Statistic;
+using core::Statistics;
+using core::StatisticsCounters;
+using TestUtil::TestContext;
+
+TEST_SUITE_BEGIN("core::Statistics");
+
+TEST_CASE("get_statistics_ids")
+{
+ TestContext test_context;
+
+ StatisticsCounters counters;
+ counters.increment(Statistic::cache_size_kibibyte);
+ counters.increment(Statistic::cache_miss);
+ counters.increment(Statistic::direct_cache_hit);
+ counters.increment(Statistic::autoconf_test);
+
+ std::vector<std::string> expected = {
+ "autoconf_test", "cache_miss", "direct_cache_hit"};
+ CHECK(Statistics(counters).get_statistics_ids() == expected);
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2020-2021 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 "TestUtil.hpp"
+
+#include <core/Statistic.hpp>
+#include <core/StatisticsCounters.hpp>
+
+#include "third_party/doctest.h"
+
+using core::Statistic;
+using core::StatisticsCounters;
+using TestUtil::TestContext;
+
+TEST_SUITE_BEGIN("core::StatisticsCounters");
+
+TEST_CASE("StatisticsCounters")
+{
+ TestContext test_context;
+
+ StatisticsCounters counters;
+ CHECK(counters.size() == static_cast<size_t>(Statistic::END));
+
+ SUBCASE("Get and set statistic")
+ {
+ CHECK(counters.get(Statistic::cache_miss) == 0);
+ counters.set(Statistic::cache_miss, 27);
+ CHECK(counters.get(Statistic::cache_miss) == 27);
+ }
+
+ SUBCASE("Get and set raw index")
+ {
+ CHECK(counters.get_raw(4) == 0);
+ counters.set_raw(4, 27);
+ CHECK(counters.get(Statistic::cache_miss) == 27);
+ }
+
+ SUBCASE("Set future raw counter")
+ {
+ const auto future_index = static_cast<size_t>(Statistic::END) + 2;
+ counters.set_raw(future_index, 42);
+ CHECK(counters.get_raw(future_index) == 42);
+ }
+
+ SUBCASE("Increment single counter")
+ {
+ counters.set(Statistic::cache_miss, 4);
+
+ counters.increment(Statistic::cache_miss);
+ CHECK(counters.get(Statistic::cache_miss) == 5);
+
+ counters.increment(Statistic::cache_miss, -3);
+ CHECK(counters.get(Statistic::cache_miss) == 2);
+
+ counters.increment(Statistic::cache_miss, -3);
+ CHECK(counters.get(Statistic::cache_miss) == 0);
+ }
+
+ SUBCASE("Increment many counters")
+ {
+ counters.set(Statistic::direct_cache_hit, 3);
+ counters.set(Statistic::cache_miss, 2);
+ counters.set(Statistic::files_in_cache, 10);
+ counters.set(Statistic::cache_size_kibibyte, 1);
+
+ StatisticsCounters updates;
+ updates.set(Statistic::direct_cache_hit, 6);
+ updates.set(Statistic::cache_miss, 5);
+ updates.set(Statistic::files_in_cache, -1);
+ updates.set(Statistic::cache_size_kibibyte, -4);
+
+ counters.increment(updates);
+ CHECK(counters.get(Statistic::direct_cache_hit) == 9);
+ CHECK(counters.get(Statistic::cache_miss) == 7);
+ CHECK(counters.get(Statistic::files_in_cache) == 9);
+ CHECK(counters.get(Statistic::cache_size_kibibyte) == 0); // No wrap-around
+ }
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2021-2022 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 "TestUtil.hpp"
+
+#include <Util.hpp>
+#include <core/StatsLog.hpp>
+#include <util/file.hpp>
+
+#include <third_party/doctest.h>
+
+using core::Statistic;
+using core::StatsLog;
+using TestUtil::TestContext;
+
+TEST_SUITE_BEGIN("core::StatsFile");
+
+TEST_CASE("read")
+{
+ TestContext test_context;
+
+ util::write_file("stats.log", "# comment\ndirect_cache_hit\n");
+ const auto counters = StatsLog("stats.log").read();
+
+ CHECK(counters.get(Statistic::direct_cache_hit) == 1);
+ CHECK(counters.get(Statistic::cache_miss) == 0);
+}
+
+TEST_CASE("log_result")
+{
+ TestContext test_context;
+
+ StatsLog stats_log("stats.log");
+ stats_log.log_result("foo.c", {"cache_miss"});
+ stats_log.log_result("bar.c", {"preprocessed_cache_hit"});
+
+ CHECK(*util::read_file<std::string>("stats.log")
+ == "# foo.c\ncache_miss\n# bar.c\npreprocessed_cache_hit\n");
+}
+
+TEST_SUITE_END();
-// Copyright (C) 2010-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
// 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/Hash.hpp"
#include "../src/hashutil.hpp"
#include "TestUtil.hpp"
+#include <util/file.hpp>
+
#include "third_party/doctest.h"
-using nonstd::string_view;
using TestUtil::TestContext;
TEST_SUITE_BEGIN("hashutil");
Hash h2;
#ifndef _WIN32
- Util::write_file("stderr.sh", "#!/bin/sh\necho foo >&2\n");
+ util::write_file("stderr.sh", "#!/bin/sh\necho foo >&2\n");
chmod("stderr.sh", 0555);
CHECK(hash_command_output(h1, "echo foo", "not used"));
CHECK(hash_command_output(h2, "./stderr.sh", "not used"));
#else
- Util::write_file("stderr.bat", "@echo off\r\necho foo>&2\r\n");
+ util::write_file("stderr.bat", "@echo off\r\necho foo>&2\r\n");
CHECK(hash_command_output(h1, "echo foo", "not used"));
CHECK(hash_command_output(h2, "stderr.bat", "not used"));
#endif
Hash h2;
#ifndef _WIN32
- Util::write_file("foo.sh", "#!/bin/sh\necho foo\necho bar\n");
+ util::write_file("foo.sh", "#!/bin/sh\necho foo\necho bar\n");
chmod("foo.sh", 0555);
CHECK(hash_multicommand_output(h2, "echo foo; echo bar", "not used"));
CHECK(hash_multicommand_output(h1, "./foo.sh", "not used"));
#else
- Util::write_file("foo.bat", "@echo off\r\necho foo\r\necho bar\r\n");
+ util::write_file("foo.bat", "@echo off\r\necho foo\r\necho bar\r\n");
CHECK(hash_multicommand_output(h2, "echo foo; echo bar", "not used"));
CHECK(hash_multicommand_output(h1, "foo.bat", "not used"));
#endif
TEST_CASE("hash_multicommand_output_error_handling")
{
- Context ctx;
-
Hash h1;
Hash h2;
TEST_CASE("check_for_temporal_macros")
{
- const string_view time_start =
+ const std::string_view time_start =
"__TIME__\n"
"int a;\n";
- const string_view time_middle =
+ const std::string_view time_middle =
"#define a __TIME__\n"
"int a;\n";
- const string_view time_end = "#define a __TIME__";
+ const std::string_view time_end = "#define a __TIME__";
- const string_view date_start =
+ const std::string_view date_start =
"__DATE__\n"
"int ab;\n";
- const string_view date_middle =
+ const std::string_view date_middle =
"#define ab __DATE__\n"
"int ab;\n";
- const string_view date_end = "#define ab __DATE__";
+ const std::string_view date_end = "#define ab __DATE__";
- const string_view timestamp_start =
+ const std::string_view timestamp_start =
"__TIMESTAMP__\n"
"int c;\n";
- const string_view timestamp_middle =
+ const std::string_view timestamp_middle =
"#define c __TIMESTAMP__\n"
"int c;\n";
- const string_view timestamp_end = "#define c __TIMESTAMP__";
+ const std::string_view timestamp_end = "#define c __TIMESTAMP__";
- const string_view no_temporal =
+ const std::string_view no_temporal =
"#define ab a__DATE__\n"
"#define ab __DATE__a\n"
"#define ab A__DATE__\n"
"#define ab __TIME __\n"
"#define ab __TIME_ _\n";
- const string_view temporal_at_avx_boundary =
+ const std::string_view temporal_at_avx_boundary =
"#define alphabet abcdefghijklmnopqrstuvwxyz\n"
"__DATE__";
--- /dev/null
+// Copyright (C) 2011-2022 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 "TestUtil.hpp"
+
+#include <Util.hpp>
+#include <core/Statistic.hpp>
+#include <fmtmacros.hpp>
+#include <storage/local/StatsFile.hpp>
+#include <util/file.hpp>
+
+#include <third_party/doctest.h>
+
+using core::Statistic;
+using storage::local::StatsFile;
+using TestUtil::TestContext;
+
+TEST_SUITE_BEGIN("storage::local::StatsFile");
+
+TEST_CASE("Read nonexistent")
+{
+ TestContext test_context;
+
+ const auto counters = StatsFile("test").read();
+
+ REQUIRE(counters.size() == static_cast<size_t>(Statistic::END));
+ CHECK(counters.get(Statistic::cache_miss) == 0);
+}
+
+TEST_CASE("Read bad")
+{
+ TestContext test_context;
+
+ util::write_file("test", "bad 1 2 3 4 5\n");
+ const auto counters = StatsFile("test").read();
+
+ REQUIRE(counters.size() == static_cast<size_t>(Statistic::END));
+ CHECK(counters.get(Statistic::cache_miss) == 0);
+}
+
+TEST_CASE("Read existing")
+{
+ TestContext test_context;
+
+ util::write_file("test", "0 1 2 3 27 5\n");
+ const auto counters = StatsFile("test").read();
+
+ REQUIRE(counters.size() == static_cast<size_t>(Statistic::END));
+ CHECK(counters.get(Statistic::cache_miss) == 27);
+ CHECK(counters.get(Statistic::could_not_use_modules) == 0);
+}
+
+TEST_CASE("Read future counters")
+{
+ TestContext test_context;
+
+ std::string content;
+ size_t count = static_cast<size_t>(Statistic::END) + 1;
+ for (size_t i = 0; i < count; ++i) {
+ content += FMT("{}\n", i);
+ }
+
+ util::write_file("test", content);
+ const auto counters = StatsFile("test").read();
+
+ REQUIRE(counters.size() == count);
+ for (size_t i = 0; i < count; ++i) {
+ CHECK(counters.get_raw(i) == i);
+ }
+}
+
+TEST_CASE("Update")
+{
+ TestContext test_context;
+
+ util::write_file("test", "0 1 2 3 27 5\n");
+
+ auto counters = StatsFile("test").update([](auto& cs) {
+ cs.increment(Statistic::internal_error, 1);
+ cs.increment(Statistic::cache_miss, 6);
+ });
+ REQUIRE(counters);
+
+ CHECK(counters->get(Statistic::internal_error) == 4);
+ CHECK(counters->get(Statistic::cache_miss) == 33);
+
+ counters = StatsFile("test").read();
+ CHECK(counters->get(Statistic::internal_error) == 4);
+ CHECK(counters->get(Statistic::cache_miss) == 33);
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2021-2022 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 "TestUtil.hpp"
+
+#include <Util.hpp>
+#include <storage/local/util.hpp>
+#include <util/file.hpp>
+
+#include <third_party/doctest.h>
+
+#include <algorithm>
+#include <string>
+
+using TestUtil::TestContext;
+
+static inline std::string
+os_path(std::string path)
+{
+#if defined(_WIN32) && !defined(HAVE_DIRENT_H)
+ std::replace(path.begin(), path.end(), '/', '\\');
+#endif
+
+ return path;
+}
+
+TEST_SUITE_BEGIN("storage::local::util");
+
+TEST_CASE("storage::local::for_each_level_1_subdir")
+{
+ std::vector<std::string> actual;
+ storage::local::for_each_level_1_subdir(
+ "cache_dir",
+ [&](const auto& subdir, const auto&) { actual.push_back(subdir); },
+ [](double) {});
+
+ std::vector<std::string> expected = {
+ "cache_dir/0",
+ "cache_dir/1",
+ "cache_dir/2",
+ "cache_dir/3",
+ "cache_dir/4",
+ "cache_dir/5",
+ "cache_dir/6",
+ "cache_dir/7",
+ "cache_dir/8",
+ "cache_dir/9",
+ "cache_dir/a",
+ "cache_dir/b",
+ "cache_dir/c",
+ "cache_dir/d",
+ "cache_dir/e",
+ "cache_dir/f",
+ };
+ CHECK(actual == expected);
+}
+
+TEST_CASE("storage::local::get_level_1_files")
+{
+ TestContext test_context;
+
+ Util::create_dir("e/m/p/t/y");
+
+ Util::create_dir("0/1");
+ Util::create_dir("0/f/c");
+ util::write_file("0/file_a", "");
+ util::write_file("0/1/file_b", "1");
+ util::write_file("0/1/file_c", "12");
+ util::write_file("0/f/c/file_d", "123");
+
+ auto null_receiver = [](double) {};
+
+ SUBCASE("nonexistent subdirectory")
+ {
+ const auto files = storage::local::get_level_1_files("2", null_receiver);
+ CHECK(files.empty());
+ }
+
+ SUBCASE("empty subdirectory")
+ {
+ const auto files = storage::local::get_level_1_files("e", null_receiver);
+ CHECK(files.empty());
+ }
+
+ SUBCASE("simple case")
+ {
+ auto files = storage::local::get_level_1_files("0", null_receiver);
+ REQUIRE(files.size() == 4);
+
+ // Files within a level are in arbitrary order, sort them to be able to
+ // verify them.
+ std::sort(files.begin(), files.end(), [](const auto& f1, const auto& f2) {
+ return f1.path() < f2.path();
+ });
+
+ CHECK(files[0].path() == os_path("0/1/file_b"));
+ CHECK(files[0].lstat().size() == 1);
+ CHECK(files[1].path() == os_path("0/1/file_c"));
+ CHECK(files[1].lstat().size() == 2);
+ CHECK(files[2].path() == os_path("0/f/c/file_d"));
+ CHECK(files[2].lstat().size() == 3);
+ CHECK(files[3].path() == os_path("0/file_a"));
+ CHECK(files[3].lstat().size() == 0);
+ }
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2021-2022 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 <util/Bytes.hpp>
+
+#include <third_party/doctest.h>
+#include <third_party/nonstd/span.hpp>
+
+#include <vector>
+
+TEST_SUITE_BEGIN("util::Bytes");
+
+using util::Bytes;
+
+TEST_CASE("Basics")
+{
+ Bytes bytes1("abc", 3);
+
+ SUBCASE("Default construction")
+ {
+ Bytes bytes0;
+
+ CHECK(bytes0.data() == nullptr);
+ CHECK(bytes0.size() == 0);
+ CHECK(bytes0.capacity() == 0);
+ }
+
+ SUBCASE("Sized construction")
+ {
+ Bytes bytes2(42);
+
+ CHECK(bytes2.data() != nullptr);
+ CHECK(bytes2.size() == 42);
+ CHECK(bytes2.capacity() == 42);
+ }
+
+ SUBCASE("Construction from data and size")
+ {
+ CHECK(bytes1.data() != nullptr);
+ REQUIRE(bytes1.size() == 3);
+ REQUIRE(bytes1.capacity() == 3);
+ CHECK(bytes1[0] == 'a');
+ CHECK(bytes1[1] == 'b');
+ CHECK(bytes1[2] == 'c');
+ }
+
+ SUBCASE("Construction from span")
+ {
+ std::vector<uint8_t> vector{'a', 'b', 'c'};
+ Bytes bytes2(vector);
+
+ CHECK(bytes2.data() != nullptr);
+ REQUIRE(bytes2.size() == 3);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+ }
+
+ SUBCASE("Copy construction")
+ {
+ const Bytes bytes2(bytes1);
+
+ CHECK(bytes2.data() != nullptr);
+ CHECK(bytes2.data() != bytes1.data());
+ REQUIRE(bytes2.size() == 3);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+ }
+
+ SUBCASE("Move construction")
+ {
+ const auto bytes1_orig_data = bytes1.data();
+ Bytes bytes2(std::move(bytes1));
+
+ CHECK(bytes1.data() == nullptr);
+ CHECK(bytes1.size() == 0);
+
+ CHECK(bytes2.data() != nullptr);
+ CHECK(bytes2.data() == bytes1_orig_data);
+ REQUIRE(bytes2.size() == 3);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+ }
+
+ SUBCASE("Construction from initializer list")
+ {
+ Bytes bytes2{'a', 'b', 'c'};
+ CHECK(bytes2 == bytes1);
+ }
+
+ SUBCASE("Copy assignment")
+ {
+ Bytes bytes2;
+
+ bytes2 = bytes1;
+ CHECK(bytes2.data() != nullptr);
+ CHECK(bytes2.data() != bytes1.data());
+ REQUIRE(bytes2.size() == 3);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+ }
+
+ SUBCASE("Move assignment")
+ {
+ const auto bytes1_orig_data = bytes1.data();
+ Bytes bytes2;
+ bytes2 = std::move(bytes1);
+
+ CHECK(bytes1.data() == nullptr);
+ CHECK(bytes1.size() == 0);
+
+ CHECK(bytes2.data() == bytes1_orig_data);
+ REQUIRE(bytes2.size() == 3);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+ }
+
+ SUBCASE("Assignment from initializer list")
+ {
+ Bytes bytes2;
+ bytes2 = {'a', 'b', 'c'};
+ CHECK(bytes2 == bytes1);
+ }
+
+ SUBCASE("Non-const operator[]")
+ {
+ bytes1[1] = 'x';
+ CHECK(bytes1[1] == 'x');
+ }
+
+ SUBCASE("Comparison")
+ {
+ CHECK(bytes1 == bytes1);
+ CHECK(!(bytes1 != bytes1));
+
+ Bytes bytes2(bytes1);
+ CHECK(bytes2 == bytes1);
+ CHECK(!(bytes2 != bytes1));
+
+ Bytes bytes3;
+ CHECK(bytes3 != bytes1);
+ CHECK(!(bytes3 == bytes1));
+
+ Bytes bytes4("xyz", 3);
+ CHECK(bytes4 != bytes1);
+ CHECK(!(bytes4 == bytes1));
+ }
+ SUBCASE("Begin")
+ {
+ const Bytes bytes2(bytes1);
+
+ CHECK(bytes1.begin() == bytes1.data());
+ CHECK(bytes2.begin() == bytes2.data());
+ CHECK(bytes1.cbegin() == bytes1.data());
+ }
+
+ SUBCASE("end")
+ {
+ const Bytes bytes2(bytes1);
+
+ CHECK(bytes1.end() == bytes1.data() + bytes1.size());
+ CHECK(bytes2.end() == bytes2.data() + bytes2.size());
+ CHECK(bytes1.cend() == bytes1.data() + bytes1.size());
+ }
+
+ SUBCASE("Clear and empty")
+ {
+ CHECK(bytes1.size() == 3);
+ CHECK(bytes1.capacity() == 3);
+ CHECK(!bytes1.empty());
+
+ bytes1.clear();
+
+ CHECK(bytes1.size() == 0);
+ CHECK(bytes1.capacity() == 3);
+ CHECK(bytes1.empty());
+ }
+
+ SUBCASE("Reserve and capacity")
+ {
+ const auto bytes1_orig_data = bytes1.data();
+ CHECK(bytes1.size() == 3);
+ CHECK(bytes1.capacity() == 3);
+
+ bytes1.reserve(2);
+ CHECK(bytes1.size() == 3);
+ CHECK(bytes1.capacity() == 3);
+ CHECK(bytes1.data() == bytes1_orig_data);
+
+ bytes1.reserve(4);
+ CHECK(bytes1.size() == 3);
+ CHECK(bytes1.capacity() == 4);
+ CHECK(bytes1.data() != bytes1_orig_data);
+ }
+
+ SUBCASE("Increase size")
+ {
+ const auto bytes1_orig_data = bytes1.data();
+ bytes1.resize(4);
+ CHECK(bytes1.data() != bytes1_orig_data);
+ CHECK(bytes1.size() == 4);
+ CHECK(bytes1.capacity() == 4);
+ CHECK(bytes1[0] == 'a');
+ CHECK(bytes1[1] == 'b');
+ CHECK(bytes1[2] == 'c');
+ }
+
+ SUBCASE("Decrease size")
+ {
+ const auto bytes1_orig_data = bytes1.data();
+ bytes1.resize(2);
+ CHECK(bytes1.data() == bytes1_orig_data);
+ CHECK(bytes1.size() == 2);
+ CHECK(bytes1.capacity() == 3);
+ CHECK(bytes1[0] == 'a');
+ CHECK(bytes1[1] == 'b');
+ }
+
+ SUBCASE("Insert")
+ {
+ Bytes bytes2;
+
+ bytes2.insert(bytes2.end(), bytes1.begin(), bytes1.end());
+ CHECK(bytes2.size() == 3);
+ CHECK(bytes2.capacity() == 3);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+
+ // Insert at end, reallocating.
+ bytes2.insert(bytes2.end(), bytes1.begin(), bytes1.begin() + 1);
+ CHECK(bytes2.size() == 4);
+ CHECK(bytes2.capacity() == 6);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+ CHECK(bytes2[3] == 'a');
+
+ // Insert at end, not reallocating.
+ Bytes bytes3("xyz", 3);
+ bytes2.insert(bytes2.end(), bytes3.begin(), bytes3.begin() + 1);
+ CHECK(bytes2.size() == 5);
+ CHECK(bytes2.capacity() == 6);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+ CHECK(bytes2[3] == 'a');
+ CHECK(bytes2[4] == 'x');
+
+ // Insert in middle, reallocating.
+ bytes2.insert(bytes2.begin() + 2, bytes3.begin(), bytes3.end());
+ CHECK(bytes2.size() == 8);
+ CHECK(bytes2.capacity() == 12);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'x');
+ CHECK(bytes2[3] == 'y');
+ CHECK(bytes2[4] == 'z');
+ CHECK(bytes2[5] == 'c');
+ CHECK(bytes2[6] == 'a');
+ CHECK(bytes2[7] == 'x');
+
+ // Insert in middle, not reallocating.
+ bytes2.insert(bytes2.begin() + 1, bytes3.begin(), bytes3.begin() + 2);
+ CHECK(bytes2.size() == 10);
+ CHECK(bytes2.capacity() == 12);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'x');
+ CHECK(bytes2[2] == 'y');
+ CHECK(bytes2[3] == 'b');
+ CHECK(bytes2[4] == 'x');
+ CHECK(bytes2[5] == 'y');
+ CHECK(bytes2[6] == 'z');
+ CHECK(bytes2[7] == 'c');
+ CHECK(bytes2[8] == 'a');
+ CHECK(bytes2[9] == 'x');
+
+ // Insert at beginning, reallocating.
+ bytes2.insert(bytes2.begin(), bytes3.begin(), bytes3.end());
+ CHECK(bytes2.size() == 13);
+ CHECK(bytes2.capacity() == 24);
+ CHECK(bytes2[0] == 'x');
+ CHECK(bytes2[1] == 'y');
+ CHECK(bytes2[2] == 'z');
+ CHECK(bytes2[3] == 'a');
+ CHECK(bytes2[4] == 'x');
+ CHECK(bytes2[5] == 'y');
+ CHECK(bytes2[6] == 'b');
+ CHECK(bytes2[7] == 'x');
+ CHECK(bytes2[8] == 'y');
+ CHECK(bytes2[9] == 'z');
+ CHECK(bytes2[10] == 'c');
+ CHECK(bytes2[11] == 'a');
+ CHECK(bytes2[12] == 'x');
+
+ // Insert at beginning, not reallocating.
+ bytes2.insert(bytes2.begin(), bytes3.begin() + 2, bytes3.begin() + 3);
+ CHECK(bytes2.size() == 14);
+ CHECK(bytes2.capacity() == 24);
+ CHECK(bytes2[0] == 'z');
+ CHECK(bytes2[1] == 'x');
+ CHECK(bytes2[2] == 'y');
+ CHECK(bytes2[3] == 'z');
+ CHECK(bytes2[4] == 'a');
+ CHECK(bytes2[5] == 'x');
+ CHECK(bytes2[6] == 'y');
+ CHECK(bytes2[7] == 'b');
+ CHECK(bytes2[8] == 'x');
+ CHECK(bytes2[9] == 'y');
+ CHECK(bytes2[10] == 'z');
+ CHECK(bytes2[11] == 'c');
+ CHECK(bytes2[12] == 'a');
+ CHECK(bytes2[13] == 'x');
+ }
+
+ SUBCASE("Insert util::Bytes data and size")
+ {
+ Bytes bytes2;
+
+ bytes2.insert(bytes2.end(), bytes1.data(), bytes1.size());
+ CHECK(bytes2.size() == 3);
+ CHECK(bytes2.capacity() == 3);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+ }
+
+ SUBCASE("Insert const char* first and last")
+ {
+ Bytes bytes2;
+ std::string data("abc");
+
+ bytes2.insert(bytes2.end(), data.data(), data.data() + data.size());
+ CHECK(bytes2.size() == 3);
+ CHECK(bytes2.capacity() == 3);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+ }
+
+ SUBCASE("Insert const char* data and size")
+ {
+ Bytes bytes2;
+ std::string data("abc");
+
+ bytes2.insert(bytes2.end(), data.data(), data.size());
+ CHECK(bytes2.size() == 3);
+ CHECK(bytes2.capacity() == 3);
+ CHECK(bytes2[0] == 'a');
+ CHECK(bytes2[1] == 'b');
+ CHECK(bytes2[2] == 'c');
+ }
+}
+
+TEST_CASE("Conversion to span")
+{
+ Bytes bytes;
+ bytes.resize(42);
+
+ SUBCASE("Const span")
+ {
+ nonstd::span<const uint8_t> span(bytes);
+ CHECK(span.data() == bytes.data());
+ CHECK(span.size() == bytes.size());
+ }
+
+ SUBCASE("Non-const span")
+ {
+ nonstd::span<uint8_t> span(bytes);
+ CHECK(span.data() == bytes.data());
+ CHECK(span.size() == bytes.size());
+ span[1] = 'x';
+ CHECK(bytes[1] == 'x');
+ }
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2022 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 <util/Duration.hpp>
+
+#include <third_party/doctest.h>
+
+TEST_SUITE_BEGIN("util::Duration");
+
+using util::Duration;
+
+TEST_CASE("Basics")
+{
+ Duration d0(4711, 2042);
+
+ CHECK(d0.sec() == 4711);
+ CHECK(d0.nsec() == 4711000002042);
+ CHECK(d0.nsec_decimal_part() == 2042);
+}
+
+TEST_CASE("Comparison operators")
+{
+ Duration d0(1000, 0);
+ Duration d1(1000, 42);
+ Duration d2(1001, 0);
+
+ SUBCASE("operator==")
+ {
+ CHECK(d0 == d0);
+ CHECK(!(d0 == d1));
+ CHECK(!(d1 == d0));
+ CHECK(!(d0 == d2));
+ CHECK(!(d2 == d0));
+ }
+
+ SUBCASE("operator!=")
+ {
+ CHECK(!(d0 != d0));
+ CHECK(d0 != d1);
+ CHECK(d1 != d0);
+ }
+
+ SUBCASE("operator<")
+ {
+ CHECK(d0 < d1);
+ CHECK(d0 < d2);
+ CHECK(d1 < d2);
+ CHECK(!(d1 < d0));
+ CHECK(!(d2 < d0));
+ CHECK(!(d2 < d1));
+ }
+
+ SUBCASE("operator>")
+ {
+ CHECK(d2 > d1);
+ CHECK(d2 > d0);
+ CHECK(d1 > d0);
+ CHECK(!(d1 > d2));
+ CHECK(!(d0 > d2));
+ CHECK(!(d0 > d1));
+ }
+
+ SUBCASE("operator<=")
+ {
+ CHECK(d0 <= d0);
+ CHECK(d0 <= d1);
+ CHECK(d0 <= d2);
+ CHECK(!(d1 <= d0));
+ CHECK(!(d2 <= d0));
+ }
+
+ SUBCASE("operator>=")
+ {
+ CHECK(d2 >= d1);
+ CHECK(d2 >= d0);
+ CHECK(d1 >= d0);
+ CHECK(!(d1 >= d2));
+ CHECK(!(d0 >= d2));
+ }
+}
+
+TEST_CASE("Arithmetic operators")
+{
+ Duration d0(1, 2);
+ Duration d1(3, 9);
+
+ SUBCASE("operator+")
+ {
+ Duration d = d0 + d1;
+ CHECK(d.sec() == 4);
+ CHECK(d.nsec_decimal_part() == 11);
+ }
+
+ SUBCASE("operator-")
+ {
+ Duration d = d0 - d1;
+ CHECK(d.sec() == -2);
+ CHECK(d.nsec_decimal_part() == -7);
+ }
+
+ SUBCASE("operator*")
+ {
+ Duration d = d1 * 4;
+ CHECK(d.sec() == 12);
+ CHECK(d.nsec_decimal_part() == 36);
+ }
+
+ SUBCASE("operator/")
+ {
+ Duration d = d1 / 0.8;
+ CHECK(d.sec() == 3);
+ CHECK(d.nsec_decimal_part() == 750'000'011);
+ }
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2020-2022 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/Stat.hpp"
+#include "TestUtil.hpp"
+
+#include <Util.hpp>
+#include <core/wincompat.hpp>
+#include <util/LockFile.hpp>
+#include <util/file.hpp>
+
+#include "third_party/doctest.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+using namespace std::chrono_literals;
+
+TEST_SUITE_BEGIN("LockFile");
+
+using TestUtil::TestContext;
+
+TEST_CASE("Acquire and release short-lived lock file")
+{
+ TestContext test_context;
+
+ util::ShortLivedLockFile lock_file("test");
+ {
+ CHECK(!lock_file.acquired());
+ CHECK(!Stat::lstat("test.lock"));
+ CHECK(!Stat::lstat("test.alive"));
+
+ util::LockFileGuard lock(lock_file);
+ CHECK(lock_file.acquired());
+ CHECK(lock.acquired());
+ CHECK(!Stat::lstat("test.alive"));
+ const auto st = Stat::lstat("test.lock");
+ CHECK(st);
+#ifndef _WIN32
+ CHECK(st.is_symlink());
+#else
+ CHECK(st.is_regular());
+#endif
+ }
+
+ CHECK(!lock_file.acquired());
+ CHECK(!Stat::lstat("test.lock"));
+ CHECK(!Stat::lstat("test.alive"));
+}
+
+TEST_CASE("Non-blocking short-lived lock")
+{
+ TestContext test_context;
+
+ util::ShortLivedLockFile lock_file_1("test");
+ CHECK(!lock_file_1.acquired());
+
+ util::ShortLivedLockFile lock_file_2("test");
+ CHECK(!lock_file_2.acquired());
+
+ CHECK(lock_file_1.try_acquire());
+ CHECK(lock_file_1.acquired());
+
+ CHECK(!lock_file_2.try_acquire());
+ CHECK(lock_file_1.acquired());
+ CHECK(!lock_file_2.acquired());
+
+ lock_file_2.release();
+ CHECK(lock_file_1.acquired());
+ CHECK(!lock_file_2.acquired());
+
+ lock_file_1.release();
+ CHECK(!lock_file_1.acquired());
+ CHECK(!lock_file_2.acquired());
+}
+
+TEST_CASE("Acquire and release long-lived lock file")
+{
+ TestContext test_context;
+
+ util::LongLivedLockFile lock_file("test");
+ {
+ CHECK(!lock_file.acquired());
+ CHECK(!Stat::lstat("test.lock"));
+ CHECK(!Stat::lstat("test.alive"));
+
+ util::LockFileGuard lock(lock_file);
+ CHECK(lock_file.acquired());
+ CHECK(lock.acquired());
+#ifndef _WIN32
+ CHECK(Stat::lstat("test.alive"));
+#endif
+ const auto st = Stat::lstat("test.lock");
+ CHECK(st);
+#ifndef _WIN32
+ CHECK(st.is_symlink());
+#else
+ CHECK(st.is_regular());
+#endif
+ }
+
+ CHECK(!lock_file.acquired());
+ CHECK(!Stat::lstat("test.lock"));
+ CHECK(!Stat::lstat("test.alive"));
+}
+
+TEST_CASE("LockFile creates missing directories")
+{
+ TestContext test_context;
+
+ util::ShortLivedLockFile lock_file("a/b/c/test");
+ util::LockFileGuard lock(lock_file);
+ CHECK(lock.acquired());
+ CHECK(Stat::lstat("a/b/c/test.lock"));
+}
+
+#ifndef _WIN32
+TEST_CASE("Break stale lock, blocking")
+{
+ TestContext test_context;
+
+ util::write_file("test.alive", "");
+ const util::TimePoint long_time_ago(0, 0);
+ util::set_timestamps("test.alive", long_time_ago);
+ CHECK(symlink("foo", "test.lock") == 0);
+
+ util::LongLivedLockFile lock_file("test");
+ util::LockFileGuard lock(lock_file);
+ CHECK(lock.acquired());
+}
+
+TEST_CASE("Break stale lock, non-blocking")
+{
+ TestContext test_context;
+
+ util::write_file("test.alive", "");
+ const util::TimePoint long_time_ago(0, 0);
+ util::set_timestamps("test.alive", long_time_ago);
+ CHECK(symlink("foo", "test.lock") == 0);
+
+ util::LongLivedLockFile lock_file("test");
+ util::LockFileGuard lock(lock_file, util::LockFileGuard::Mode::non_blocking);
+ CHECK(lock.acquired());
+}
+#endif // !_WIN32
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2021 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 <util/TextTable.hpp>
+
+#include <third_party/doctest.h>
+
+#include <iostream> // macOS bug: https://github.com/onqtam/doctest/issues/126
+
+TEST_CASE("TextTable")
+{
+ using C = util::TextTable::Cell;
+
+ util::TextTable table;
+
+ SUBCASE("empty")
+ {
+ CHECK(table.render() == "");
+ }
+
+ SUBCASE("1x1")
+ {
+ table.add_row({"a"});
+ CHECK(table.render() == "a\n");
+ }
+
+ SUBCASE("2x1 with space prefix/suffix")
+ {
+ table.add_row({std::string(" a "), C(" b ")});
+ CHECK(table.render() == " a b\n");
+ }
+
+ SUBCASE("1x2")
+ {
+ table.add_row({"a"});
+ table.add_row({1});
+ CHECK(table.render() == "a\n1\n");
+ }
+
+ SUBCASE("3 + 2")
+ {
+ table.add_row({"a", "b", "c"});
+ table.add_row({"aa", "bbb"});
+ CHECK(table.render()
+ == ("a b c\n"
+ "aa bbb\n"));
+ }
+
+ SUBCASE("strings and numbers")
+ {
+ table.add_row({"a", 123, "cc"});
+ table.add_row({"aa", 4, "ccc"});
+ table.add_row({"aaa", 56, "c"});
+ CHECK(table.render()
+ == ("a 123 cc\n"
+ "aa 4 ccc\n"
+ "aaa 56 c\n"));
+ }
+
+ SUBCASE("left align")
+ {
+ table.add_row({"a", 123, "cc"});
+ table.add_row({"aa", C(4).left_align(), "ccc"});
+ table.add_row({"aaa", 56, "c"});
+ CHECK(table.render()
+ == ("a 123 cc\n"
+ "aa 4 ccc\n"
+ "aaa 56 c\n"));
+ }
+
+ SUBCASE("right align")
+ {
+ table.add_row({"a", "bbb", "cc"});
+ table.add_row(
+ {C("aa").right_align(), C("b").right_align(), C("ccc").right_align()});
+ table.add_row({"aaa", "bb", "c"});
+ CHECK(table.render()
+ == ("a bbb cc\n"
+ " aa b ccc\n"
+ "aaa bb c\n"));
+ }
+
+ SUBCASE("heading")
+ {
+ table.add_row({"a", "b", "c"});
+ table.add_heading("DDDDDD");
+ table.add_row({"aaa", "bbb", "ccc"});
+ CHECK(table.render()
+ == ("a b c\n"
+ "DDDDDD\n"
+ "aaa bbb ccc\n"));
+ }
+
+ SUBCASE("colspan")
+ {
+ table.add_row({C("22").colspan(2), C("2r").colspan(2).right_align()});
+ table.add_row({C("1").colspan(1), C("22222").colspan(2), "1"});
+ table.add_row({"1", "1", "1", "1", "1"});
+ table.add_row({"1", C("3333333333").colspan(3), "1"});
+ CHECK(table.render()
+ == ("22 2r\n" // 4 columns
+ "1 22222 1\n" // 4 columns
+ "1 1 1 1 1\n" // 5 columns
+ "1 3333333333 1\n")); // 5 columns
+ }
+}
--- /dev/null
+// Copyright (C) 2022 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 <util/TimePoint.hpp>
+
+#include <third_party/doctest.h>
+
+TEST_SUITE_BEGIN("util::TimePoint");
+
+using util::TimePoint;
+
+TEST_CASE("Basics")
+{
+ TimePoint t0(4711, 2042);
+
+ CHECK(t0.sec() == 4711);
+ CHECK(t0.nsec() == 4711000002042);
+ CHECK(t0.nsec_decimal_part() == 2042);
+}
+
+TEST_CASE("Conversions")
+{
+ TimePoint t0(4711, 2042);
+
+ SUBCASE("to_timespec")
+ {
+ timespec ts = t0.to_timespec();
+ CHECK(ts.tv_sec == 4711);
+ CHECK(ts.tv_nsec == 2042);
+ }
+}
+
+TEST_CASE("Comparison operators")
+{
+ TimePoint t0(1000, 0);
+ TimePoint t1(1000, 42);
+ TimePoint t2(1001, 0);
+
+ SUBCASE("operator==")
+ {
+ CHECK(t0 == t0);
+ CHECK(!(t0 == t1));
+ CHECK(!(t1 == t0));
+ CHECK(!(t0 == t2));
+ CHECK(!(t2 == t0));
+ }
+
+ SUBCASE("operator!=")
+ {
+ CHECK(!(t0 != t0));
+ CHECK(t0 != t1);
+ CHECK(t1 != t0);
+ }
+
+ SUBCASE("operator<")
+ {
+ CHECK(t0 < t1);
+ CHECK(t0 < t2);
+ CHECK(t1 < t2);
+ CHECK(!(t1 < t0));
+ CHECK(!(t2 < t0));
+ CHECK(!(t2 < t1));
+ }
+
+ SUBCASE("operator>")
+ {
+ CHECK(t2 > t1);
+ CHECK(t2 > t0);
+ CHECK(t1 > t0);
+ CHECK(!(t1 > t2));
+ CHECK(!(t0 > t2));
+ CHECK(!(t0 > t1));
+ }
+
+ SUBCASE("operator<=")
+ {
+ CHECK(t0 <= t0);
+ CHECK(t0 <= t1);
+ CHECK(t0 <= t2);
+ CHECK(!(t1 <= t0));
+ CHECK(!(t2 <= t0));
+ }
+
+ SUBCASE("operator>=")
+ {
+ CHECK(t2 >= t1);
+ CHECK(t2 >= t0);
+ CHECK(t1 >= t0);
+ CHECK(!(t1 >= t2));
+ CHECK(!(t0 >= t2));
+ }
+}
+
+TEST_CASE("Operations with duration")
+{
+ TimePoint t0(1, 2);
+ TimePoint t1(3, 17);
+
+ SUBCASE("operator-(TimePoint)")
+ {
+ CHECK(t1 - t0 == util::Duration(2, 15));
+ CHECK(t0 - t1 == util::Duration(-2, -15));
+ }
+
+ SUBCASE("operator+(Duration)")
+ {
+ CHECK(t0 + util::Duration(4, 999999999) == util::TimePoint(6, 1));
+ }
+
+ SUBCASE("operator-(Duration))")
+ {
+ auto t = t0 - util::Duration(4, 999999999);
+ CHECK(t.sec() == -3);
+ CHECK(t.nsec_decimal_part() == -999999997);
+ }
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2021-2022 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/Util.hpp"
+
+#include "third_party/doctest.h"
+
+#include <ostream> // https://github.com/doctest/doctest/issues/618
+
+TEST_CASE("util::Tokenizer")
+{
+ using Mode = util::Tokenizer::Mode;
+ using IncludeDelimiter = util::Tokenizer::IncludeDelimiter;
+ struct SplitTest
+ {
+ SplitTest(Mode mode,
+ IncludeDelimiter include_delimiter = IncludeDelimiter::no)
+ : m_mode(mode),
+ m_include_delimiter(include_delimiter)
+ {
+ }
+
+ void
+ operator()(const char* input,
+ const char* separators,
+ const std::vector<std::string>& expected) const
+ {
+ const auto res =
+ Util::split_into_views(input, separators, m_mode, m_include_delimiter);
+ REQUIRE(res.size() == expected.size());
+ for (int i = 0, total = expected.size(); i < total; ++i) {
+ CHECK(res[i] == expected[i]);
+ }
+ }
+
+ Mode m_mode;
+ IncludeDelimiter m_include_delimiter;
+ };
+
+ SUBCASE("include empty tokens")
+ {
+ SplitTest split(Mode::include_empty);
+ split("", "/", {""});
+ split("/", "/", {"", ""});
+ split("a/", "/", {"a", ""});
+ split("/b", "/", {"", "b"});
+ split("a/b", "/", {"a", "b"});
+ split("/a:", "/:", {"", "a", ""});
+ }
+
+ SUBCASE("skip empty")
+ {
+ SplitTest split(Mode::skip_empty);
+ split("", "/", {});
+ split("///", "/", {});
+ split("a/b", "/", {"a", "b"});
+ split("a/b", "x", {"a/b"});
+ split("a/b:c", "/:", {"a", "b", "c"});
+ split("/a:", "/:", {"a"});
+ split(":a//b..:.c/:/.", "/:.", {"a", "b", "c"});
+ split(".0.1.2.3.4.5.6.7.8.9.",
+ "/:.+_abcdef",
+ {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"});
+ }
+
+ SUBCASE("include empty and delimiter")
+ {
+ SplitTest split(Mode::include_empty, IncludeDelimiter::yes);
+ split("", "/", {""});
+ split("/", "/", {"/", ""});
+ split("a/", "/", {"a/", ""});
+ split("/b", "/", {"/", "b"});
+ split("a/b", "/", {"a/", "b"});
+ split("/a:", "/:", {"/", "a:", ""});
+ split("a//b/", "/", {"a/", "/", "b/", ""});
+ }
+
+ SUBCASE("skip empty and include delimiter")
+ {
+ SplitTest split(Mode::skip_empty, IncludeDelimiter::yes);
+ split("", "/", {});
+ split("///", "/", {});
+ split("a/b", "/", {"a/", "b"});
+ split("a/b", "x", {"a/b"});
+ split("a/b:c", "/:", {"a/", "b:", "c"});
+ split("/a:", "/:", {"a:"});
+ split(":a//b..:.c/:/.", "/:.", {"a/", "b.", "c/"});
+ split(".0.1.2.3.4.5.6.7.8.9.",
+ "/:.+_abcdef",
+ {"0.", "1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.", "9."});
+ }
+}
--- /dev/null
+// Copyright (C) 2011-2022 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 <util/XXH3_128.hpp>
+#include <util/string.hpp>
+
+#include <third_party/doctest.h>
+
+TEST_SUITE_BEGIN("util::XXH3_128");
+
+TEST_CASE("util::XXH3_128")
+{
+ util::XXH3_128 checksum;
+ auto digest = checksum.digest();
+ CHECK(Util::format_base16(digest.data(), 16)
+ == "99aa06d3014798d86001c324468d497f");
+
+ checksum.update(util::to_span("foo"));
+ digest = checksum.digest();
+ CHECK(Util::format_base16(digest.data(), 16)
+ == "79aef92e83454121ab6e5f64077e7d8a");
+
+ checksum.update(util::to_span("t"));
+ digest = checksum.digest();
+ CHECK(Util::format_base16(digest.data(), 16)
+ == "e6045075b5bf1ae7a3e4c87775e6c97f");
+
+ checksum.reset();
+ digest = checksum.digest();
+ CHECK(Util::format_base16(digest.data(), 16)
+ == "99aa06d3014798d86001c324468d497f");
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2011-2021 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 <util/XXH3_64.hpp>
+
+#include <third_party/doctest.h>
+
+TEST_SUITE_BEGIN("util::XXH3_64");
+
+TEST_CASE("util::XXH3_64")
+{
+ util::XXH3_64 checksum;
+ CHECK(checksum.digest() == 0x2d06800538d394c2);
+
+ checksum.update("foo", 3);
+ CHECK(checksum.digest() == 0xab6e5f64077e7d8a);
+
+ checksum.update("t", 1);
+ CHECK(checksum.digest() == 0x3fd918aed1a9e7e4);
+
+ checksum.reset();
+ CHECK(checksum.digest() == 0x2d06800538d394c2);
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2021-2022 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 <util/expected.hpp>
+
+#include <third_party/doctest.h>
+#include <third_party/nonstd/expected.hpp>
+
+#include <iostream> // macOS bug: https://github.com/onqtam/doctest/issues/126
+#include <memory>
+#include <stdexcept>
+#include <string>
+
+class TestException : public std::runtime_error
+{
+ using std::runtime_error::runtime_error;
+};
+
+TEST_CASE("util::value_or_throw")
+{
+ using util::throw_on_error;
+ using util::value_or_throw;
+
+ SUBCASE("const ref")
+ {
+ const nonstd::expected<int, const char*> with_value = 42;
+ const nonstd::expected<int, const char*> with_error =
+ nonstd::make_unexpected("no value");
+
+ CHECK(value_or_throw<TestException>(with_value) == 42);
+ CHECK_THROWS_WITH(value_or_throw<TestException>(with_error), "no value");
+ }
+
+ SUBCASE("move")
+ {
+ const std::string value = "value";
+ nonstd::expected<std::unique_ptr<std::string>, const char*> with_value =
+ std::make_unique<std::string>(value);
+ const nonstd::expected<int, const char*> with_error =
+ nonstd::make_unexpected("no value");
+
+ CHECK(*value_or_throw<TestException>(std::move(with_value)) == value);
+ CHECK_THROWS_WITH(value_or_throw<TestException>(std::move(with_error)),
+ "no value");
+ }
+
+ SUBCASE("const ref with prefix")
+ {
+ const nonstd::expected<int, const char*> with_value = 42;
+ const nonstd::expected<int, const char*> with_error =
+ nonstd::make_unexpected("no value");
+
+ CHECK(value_or_throw<TestException>(with_value, "prefix: ") == 42);
+ CHECK_THROWS_WITH(value_or_throw<TestException>(with_error, "prefix: "),
+ "prefix: no value");
+ }
+
+ SUBCASE("move with prefix")
+ {
+ const std::string value = "value";
+ nonstd::expected<std::unique_ptr<std::string>, const char*> with_value =
+ std::make_unique<std::string>(value);
+ const nonstd::expected<int, const char*> with_error =
+ nonstd::make_unexpected("no value");
+
+ CHECK(*value_or_throw<TestException>(std::move(with_value), "prefix: ")
+ == value);
+ CHECK_THROWS_WITH(
+ value_or_throw<TestException>(std::move(with_error), "prefix: "),
+ "prefix: no value");
+ }
+
+ SUBCASE("void T::value_type")
+ {
+ const nonstd::expected<void, const char*> without_error;
+ const nonstd::expected<void, const char*> with_error =
+ nonstd::make_unexpected("no value");
+
+ CHECK_NOTHROW(throw_on_error<TestException>(without_error));
+ CHECK_THROWS_WITH(throw_on_error<TestException>(with_error), "no value");
+ }
+
+ SUBCASE("void T::value_type with prefix")
+ {
+ const nonstd::expected<void, const char*> without_error;
+ const nonstd::expected<void, const char*> with_error =
+ nonstd::make_unexpected("no value");
+
+ CHECK_NOTHROW(throw_on_error<TestException>(without_error, "prefix: "));
+ CHECK_THROWS_WITH(throw_on_error<TestException>(with_error, "prefix: "),
+ "prefix: no value");
+ }
+}
--- /dev/null
+// Copyright (C) 2022 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 "TestUtil.hpp"
+
+#include <util/Bytes.hpp>
+#include <util/file.hpp>
+#include <util/string.hpp>
+
+#include <third_party/doctest.h>
+
+#include <cstring>
+#include <string_view>
+
+using TestUtil::TestContext;
+
+TEST_CASE("util::read_file and util::write_file, text data")
+{
+ TestContext test_context;
+
+ REQUIRE(util::write_file("test", "foo\nbar\n"));
+ auto data = util::read_file<std::string>("test");
+ REQUIRE(data);
+ CHECK(*data == "foo\nbar\n");
+
+ REQUIRE(util::write_file("test", "foo\r\nbar\r\n"));
+ data = util::read_file<std::string>("test");
+ REQUIRE(data);
+ CHECK(*data == "foo\r\nbar\r\n");
+
+ // Newline handling
+ REQUIRE(util::write_file("test", "foo\r\nbar\n"));
+ auto bin_data = util::read_file<std::vector<uint8_t>>("test");
+ REQUIRE(bin_data);
+#ifdef _WIN32
+ const std::string expected_bin_data = "foo\r\r\nbar\r\n";
+#else
+ const std::string expected_bin_data = "foo\r\nbar\n";
+#endif
+ CHECK(*bin_data
+ == std::vector<uint8_t>(expected_bin_data.begin(),
+ expected_bin_data.end()));
+
+ REQUIRE(util::write_file("size_hint_test", std::string(8192, '\0')));
+ data = util::read_file<std::string>("size_hint_test", 8191 /*size_hint*/);
+ REQUIRE(data);
+ CHECK(data->size() == 8192);
+ data = util::read_file<std::string>("size_hint_test", 8193 /*size_hint*/);
+ REQUIRE(data);
+ CHECK(data->size() == 8192);
+
+ data = util::read_file<std::string>("does/not/exist");
+ REQUIRE(!data);
+ CHECK(data.error() == "No such file or directory");
+
+ auto result = util::write_file("", "does/not/exist");
+ REQUIRE(!result);
+ CHECK(result.error() == "No such file or directory");
+
+ result = util::write_file("does/not/exist", "does/not/exist");
+ REQUIRE(!result);
+ CHECK(result.error() == "No such file or directory");
+}
+
+TEST_CASE("util::read_file and util::write_file, binary data")
+{
+ TestContext test_context;
+
+ std::vector<uint8_t> expected;
+ for (size_t i = 0; i < 512; ++i) {
+ expected.push_back((32 + i) % 256);
+ }
+
+ CHECK(util::write_file("test", expected));
+ auto actual = util::read_file<std::vector<uint8_t>>("test");
+ REQUIRE(actual);
+ CHECK(*actual == expected);
+
+ REQUIRE(util::write_file("size_hint_test", std::vector<uint8_t>(8192, 0)));
+ auto data =
+ util::read_file<std::vector<uint8_t>>("size_hint_test", 8191 /*size_hint*/);
+ REQUIRE(data);
+ CHECK(data->size() == 8192);
+ data =
+ util::read_file<std::vector<uint8_t>>("size_hint_test", 8193 /*size_hint*/);
+ REQUIRE(data);
+ CHECK(data->size() == 8192);
+}
+
+#ifdef _WIN32
+TEST_CASE("util::read_file<std::string> with UTF-16 little endian encoding")
+{
+ TestContext test_context;
+
+ std::string data;
+ data.push_back(static_cast<unsigned char>(0xff));
+ data.push_back(static_cast<unsigned char>(0xfe));
+ data.push_back('a');
+ data.push_back('\0');
+ data.push_back('b');
+ data.push_back('\0');
+ data.push_back('c');
+ data.push_back('\0');
+
+ CHECK(util::write_file("test", data));
+ auto read_data = util::read_file<std::string>("test");
+ REQUIRE(read_data);
+ CHECK(*read_data == "abc");
+
+ data.push_back('\0');
+ data.push_back(static_cast<unsigned char>(0xd8));
+ data.push_back('d');
+ data.push_back('\0');
+ CHECK(util::write_file("test", data));
+ read_data = util::read_file<std::string>("test");
+ REQUIRE(!read_data);
+}
+#endif
+
+TEST_CASE("util::read_file_part")
+{
+ CHECK(util::write_file("test", "banana"));
+
+ CHECK(util::read_file_part<util::Bytes>("test", 0, 0) == util::to_span(""));
+ CHECK(util::read_file_part<util::Bytes>("test", 0, 6)
+ == util::to_span("banana"));
+ CHECK(util::read_file_part<util::Bytes>("test", 0, 1000)
+ == util::to_span("banana"));
+
+ CHECK(util::read_file_part<util::Bytes>("test", 3, 0) == util::to_span(""));
+ CHECK(util::read_file_part<util::Bytes>("test", 3, 2) == util::to_span("an"));
+ CHECK(util::read_file_part<util::Bytes>("test", 3, 1000)
+ == util::to_span("ana"));
+
+ CHECK(util::read_file_part<util::Bytes>("test", 1000, 1000)
+ == util::to_span(""));
+}
--- /dev/null
+// Copyright (C) 2021-2022 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 <Util.hpp>
+#include <fmtmacros.hpp>
+#include <util/path.hpp>
+
+#include <third_party/doctest.h>
+
+#include <ostream> // https://github.com/doctest/doctest/issues/618
+
+TEST_CASE("util::is_absolute_path")
+{
+#ifdef _WIN32
+ CHECK(util::is_absolute_path("C:/"));
+ CHECK(util::is_absolute_path("C:\\foo/fie"));
+ CHECK(util::is_absolute_path("/C:\\foo/fie")); // MSYS/Cygwin path
+ CHECK(!util::is_absolute_path(""));
+ CHECK(!util::is_absolute_path("foo\\fie/fum"));
+ CHECK(!util::is_absolute_path("C:foo/fie"));
+#endif
+ CHECK(util::is_absolute_path("/"));
+ CHECK(util::is_absolute_path("/foo/fie"));
+ CHECK(!util::is_absolute_path(""));
+ CHECK(!util::is_absolute_path("foo/fie"));
+}
+
+TEST_CASE("util::is_absolute_path")
+{
+ CHECK(!util::is_full_path(""));
+ CHECK(!util::is_full_path("foo"));
+ CHECK(util::is_full_path("/foo"));
+ CHECK(util::is_full_path("foo/"));
+ CHECK(util::is_full_path("foo/bar"));
+#ifdef _WIN32
+ CHECK(util::is_full_path("foo\\bar"));
+#else
+ CHECK(!util::is_full_path("foo\\bar"));
+#endif
+}
+
+TEST_CASE("util::split_path_list")
+{
+ CHECK(util::split_path_list("").empty());
+ {
+ const auto v = util::split_path_list("a");
+ REQUIRE(v.size() == 1);
+ CHECK(v[0] == "a");
+ }
+ {
+ const auto v = util::split_path_list("a/b");
+ REQUIRE(v.size() == 1);
+ CHECK(v[0] == "a/b");
+ }
+ {
+#ifdef _WIN32
+ const auto v = util::split_path_list("a/b;c");
+#else
+ const auto v = util::split_path_list("a/b:c");
+#endif
+ REQUIRE(v.size() == 2);
+ CHECK(v[0] == "a/b");
+ CHECK(v[1] == "c");
+ }
+}
+
+TEST_CASE("util::to_absolute_path")
+{
+ CHECK(util::to_absolute_path("/foo/bar") == "/foo/bar");
+
+#ifdef _WIN32
+ CHECK(util::to_absolute_path("C:\\foo\\bar") == "C:\\foo\\bar");
+#endif
+
+ const auto cwd = Util::get_actual_cwd();
+
+ CHECK(util::to_absolute_path("") == cwd);
+ CHECK(util::to_absolute_path(".") == cwd);
+ CHECK(util::to_absolute_path("..") == Util::dir_name(cwd));
+ CHECK(util::to_absolute_path("foo") == FMT("{}/foo", cwd));
+ CHECK(util::to_absolute_path("../foo/bar")
+ == FMT("{}/foo/bar", Util::dir_name(cwd)));
+}
+
+TEST_CASE("util::to_absolute_path_no_drive")
+{
+ CHECK(util::to_absolute_path_no_drive("/foo/bar") == "/foo/bar");
+
+#ifdef _WIN32
+ CHECK(util::to_absolute_path_no_drive("C:\\foo\\bar") == "\\foo\\bar");
+#endif
+
+ auto cwd = Util::get_actual_cwd();
+#ifdef _WIN32
+ cwd = cwd.substr(2);
+#endif
+
+ CHECK(util::to_absolute_path_no_drive("") == cwd);
+ CHECK(util::to_absolute_path_no_drive(".") == cwd);
+ CHECK(util::to_absolute_path_no_drive("..") == Util::dir_name(cwd));
+ CHECK(util::to_absolute_path_no_drive("foo") == FMT("{}/foo", cwd));
+ CHECK(util::to_absolute_path_no_drive("../foo/bar")
+ == FMT("{}/foo/bar", Util::dir_name(cwd)));
+}
+
+TEST_CASE("util::path_starts_with")
+{
+ CHECK(!util::path_starts_with("", ""));
+ CHECK(!util::path_starts_with("", "/"));
+ CHECK(util::path_starts_with("/foo/bar", "/foo"));
+ CHECK(!util::path_starts_with("/batz/bar", "/foo"));
+ CHECK(!util::path_starts_with("/foo/bar", "/foo/baz"));
+ CHECK(!util::path_starts_with("/beh/foo", "/foo"));
+#ifdef _WIN32
+ CHECK(util::path_starts_with("C:/foo/bar", "C:\\foo"));
+ CHECK(util::path_starts_with("C:/foo/bar", "C:\\\\foo"));
+ CHECK(util::path_starts_with("C:\\foo\\bar", "C:/foo"));
+ CHECK(util::path_starts_with("C:\\\\foo\\\\bar", "C:/foo"));
+ CHECK(!util::path_starts_with("C:\\foo\\bar", "/foo/baz"));
+ CHECK(!util::path_starts_with("C:\\foo\\bar", "C:/foo/baz"));
+ CHECK(!util::path_starts_with("C:\\beh\\foo", "/foo"));
+ CHECK(!util::path_starts_with("C:\\beh\\foo", "C:/foo"));
+#endif
+}
--- /dev/null
+// Copyright (C) 2021-2022 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 <util/string.hpp>
+
+#include <third_party/doctest.h>
+
+#include <ostream> // https://github.com/doctest/doctest/issues/618
+#include <vector>
+
+static bool
+operator==(std::pair<std::string_view, std::optional<std::string_view>> left,
+ std::pair<std::string_view, std::optional<std::string_view>> right)
+{
+ return left.first == right.first && left.second == right.second;
+}
+
+TEST_SUITE_BEGIN("util");
+
+TEST_CASE("util::ends_with")
+{
+ CHECK(util::ends_with("", ""));
+ CHECK(util::ends_with("x", ""));
+ CHECK(util::ends_with("x", "x"));
+ CHECK(util::ends_with("xy", ""));
+ CHECK(util::ends_with("xy", "y"));
+ CHECK(util::ends_with("xy", "xy"));
+ CHECK(util::ends_with("xyz", ""));
+ CHECK(util::ends_with("xyz", "z"));
+ CHECK(util::ends_with("xyz", "yz"));
+ CHECK(util::ends_with("xyz", "xyz"));
+
+ CHECK_FALSE(util::ends_with("", "x"));
+ CHECK_FALSE(util::ends_with("x", "y"));
+ CHECK_FALSE(util::ends_with("x", "xy"));
+}
+
+TEST_CASE("util::join")
+{
+ {
+ std::vector<std::string> v;
+ CHECK(util::join(v, "|") == "");
+ }
+ {
+ std::vector<std::string> v{"a"};
+ CHECK(util::join(v, "|") == "a");
+ }
+ {
+ std::vector<std::string> v{"a", " b ", "c|"};
+ CHECK(util::join(v, "|") == "a| b |c|");
+ CHECK(util::join(v.begin(), v.end(), "|") == "a| b |c|");
+ CHECK(util::join(v.begin() + 1, v.end(), "|") == " b |c|");
+ }
+ {
+ std::vector<std::string_view> v{"1", "2"};
+ CHECK(util::join(v, " ") == "1 2");
+ }
+}
+
+TEST_CASE("util::parse_double")
+{
+ CHECK(*util::parse_double("0") == doctest::Approx(0.0));
+ CHECK(*util::parse_double(".0") == doctest::Approx(0.0));
+ CHECK(*util::parse_double("0.") == doctest::Approx(0.0));
+ CHECK(*util::parse_double("0.0") == doctest::Approx(0.0));
+ CHECK(*util::parse_double("2.1") == doctest::Approx(2.1));
+ CHECK(*util::parse_double("-42.789") == doctest::Approx(-42.789));
+
+ CHECK(util::parse_double("").error() == "invalid floating point: \"\"");
+ CHECK(util::parse_double("x").error() == "invalid floating point: \"x\"");
+}
+
+TEST_CASE("util::parse_signed")
+{
+ CHECK(*util::parse_signed("0") == 0);
+ CHECK(*util::parse_signed("2") == 2);
+ CHECK(*util::parse_signed("-17") == -17);
+ CHECK(*util::parse_signed("42") == 42);
+ CHECK(*util::parse_signed("0666") == 666);
+ CHECK(*util::parse_signed(" 777 ") == 777);
+
+ CHECK(util::parse_signed("").error() == "invalid integer: \"\"");
+ CHECK(util::parse_signed("x").error() == "invalid integer: \"x\"");
+ CHECK(util::parse_signed("0x").error() == "invalid integer: \"0x\"");
+ CHECK(util::parse_signed("0x4").error() == "invalid integer: \"0x4\"");
+
+ // Custom description not used for invalid value.
+ CHECK(
+ util::parse_signed("apple", std::nullopt, std::nullopt, "banana").error()
+ == "invalid integer: \"apple\"");
+
+ // Boundary values.
+ CHECK(util::parse_signed("-9223372036854775809").error()
+ == "invalid integer: \"-9223372036854775809\"");
+ CHECK(*util::parse_signed("-9223372036854775808") == INT64_MIN);
+ CHECK(*util::parse_signed("9223372036854775807") == INT64_MAX);
+ CHECK(util::parse_signed("9223372036854775808").error()
+ == "invalid integer: \"9223372036854775808\"");
+
+ // Min and max values.
+ CHECK(util::parse_signed("-2", -1, 1).error()
+ == "integer must be between -1 and 1");
+ CHECK(*util::parse_signed("-1", -1, 1) == -1);
+ CHECK(*util::parse_signed("0", -1, 1) == 0);
+ CHECK(*util::parse_signed("1", -1, 1) == 1);
+ CHECK(util::parse_signed("2", -1, 1).error()
+ == "integer must be between -1 and 1");
+
+ // Custom description used for boundary violation.
+ CHECK(util::parse_signed("0", 1, 2, "banana").error()
+ == "banana must be between 1 and 2");
+}
+
+TEST_CASE("util::parse_umask")
+{
+ CHECK(util::parse_umask("1") == 1U);
+ CHECK(util::parse_umask("002") == 002U);
+ CHECK(util::parse_umask("777") == 0777U);
+ CHECK(util::parse_umask("0777") == 0777U);
+
+ CHECK(util::parse_umask("").error()
+ == "invalid unsigned octal integer: \"\"");
+ CHECK(util::parse_umask(" ").error()
+ == "invalid unsigned octal integer: \"\"");
+ CHECK(util::parse_umask("088").error()
+ == "invalid unsigned octal integer: \"088\"");
+}
+
+TEST_CASE("util::parse_unsigned")
+{
+ CHECK(*util::parse_unsigned("0") == 0);
+ CHECK(*util::parse_unsigned("2") == 2);
+ CHECK(*util::parse_unsigned("42") == 42);
+ CHECK(*util::parse_unsigned("0666") == 666);
+ CHECK(*util::parse_unsigned(" 777 ") == 777);
+
+ CHECK(util::parse_unsigned("").error() == "invalid unsigned integer: \"\"");
+ CHECK(util::parse_unsigned("x").error() == "invalid unsigned integer: \"x\"");
+ CHECK(util::parse_unsigned("0x").error()
+ == "invalid unsigned integer: \"0x\"");
+ CHECK(util::parse_unsigned("0x4").error()
+ == "invalid unsigned integer: \"0x4\"");
+
+ // Custom description not used for invalid value.
+ CHECK(
+ util::parse_unsigned("apple", std::nullopt, std::nullopt, "banana").error()
+ == "invalid unsigned integer: \"apple\"");
+
+ // Boundary values.
+ CHECK(util::parse_unsigned("-1").error()
+ == "invalid unsigned integer: \"-1\"");
+ CHECK(*util::parse_unsigned("0") == 0);
+ CHECK(*util::parse_unsigned("18446744073709551615") == UINT64_MAX);
+ CHECK(util::parse_unsigned("18446744073709551616").error()
+ == "invalid unsigned integer: \"18446744073709551616\"");
+
+ // Base
+ CHECK(*util::parse_unsigned("0666", std::nullopt, std::nullopt, "", 8)
+ == 0666);
+ CHECK(*util::parse_unsigned("0666", std::nullopt, std::nullopt, "", 10)
+ == 666);
+ CHECK(*util::parse_unsigned("0666", std::nullopt, std::nullopt, "", 16)
+ == 0x666);
+}
+
+TEST_CASE("util::percent_decode")
+{
+ CHECK(util::percent_decode("") == "");
+ CHECK(util::percent_decode("a") == "a");
+ CHECK(util::percent_decode("%61") == "a");
+ CHECK(util::percent_decode("%ab") == "\xab");
+ CHECK(util::percent_decode("%aB") == "\xab");
+ CHECK(util::percent_decode("%Ab") == "\xab");
+ CHECK(util::percent_decode("%AB") == "\xab");
+ CHECK(util::percent_decode("a%25b%7cc") == "a%b|c");
+
+ CHECK(util::percent_decode("%").error()
+ == "invalid percent-encoded string at position 0: %");
+ CHECK(util::percent_decode("%6").error()
+ == "invalid percent-encoded string at position 0: %6");
+ CHECK(util::percent_decode("%%").error()
+ == "invalid percent-encoded string at position 0: %%");
+ CHECK(util::percent_decode("a%0g").error()
+ == "invalid percent-encoded string at position 1: a%0g");
+}
+
+TEST_CASE("util::replace_all")
+{
+ CHECK(util::replace_all("", "", "") == "");
+ CHECK(util::replace_all("x", "", "") == "x");
+ CHECK(util::replace_all("", "x", "") == "");
+ CHECK(util::replace_all("", "", "x") == "");
+ CHECK(util::replace_all("x", "y", "z") == "x");
+ CHECK(util::replace_all("x", "x", "y") == "y");
+ CHECK(util::replace_all("abc", "abc", "defdef") == "defdef");
+ CHECK(util::replace_all("xabc", "abc", "defdef") == "xdefdef");
+ CHECK(util::replace_all("abcx", "abc", "defdef") == "defdefx");
+ CHECK(util::replace_all("xabcyabcz", "abc", "defdef") == "xdefdefydefdefz");
+}
+
+TEST_CASE("util::replace_first")
+{
+ CHECK(util::replace_first("", "", "") == "");
+ CHECK(util::replace_first("x", "", "") == "x");
+ CHECK(util::replace_first("", "x", "") == "");
+ CHECK(util::replace_first("", "", "x") == "");
+ CHECK(util::replace_first("x", "y", "z") == "x");
+ CHECK(util::replace_first("x", "x", "y") == "y");
+ CHECK(util::replace_first("xabcyabcz", "abc", "defdef") == "xdefdefyabcz");
+}
+
+TEST_CASE("util::split_once")
+{
+ using std::make_pair;
+ using std::nullopt;
+ using util::split_once;
+
+ CHECK(split_once("", '=') == make_pair("", nullopt));
+ CHECK(split_once("a", '=') == make_pair("a", nullopt));
+ CHECK(split_once("=a", '=') == make_pair("", "a"));
+ CHECK(split_once("a=", '=') == make_pair("a", ""));
+ CHECK(split_once("a==", '=') == make_pair("a", "="));
+ CHECK(split_once("a=b", '=') == make_pair("a", "b"));
+ CHECK(split_once("a=b=", '=') == make_pair("a", "b="));
+ CHECK(split_once("a=b=c", '=') == make_pair("a", "b=c"));
+ CHECK(split_once("x y", ' ') == make_pair("x", "y"));
+}
+
+TEST_CASE("util::starts_with")
+{
+ // starts_with(const char*, string_view)
+ CHECK(util::starts_with("", ""));
+ CHECK(util::starts_with("x", ""));
+ CHECK(util::starts_with("x", "x"));
+ CHECK(util::starts_with("xy", ""));
+ CHECK(util::starts_with("xy", "x"));
+ CHECK(util::starts_with("xy", "xy"));
+ CHECK(util::starts_with("xyz", ""));
+ CHECK(util::starts_with("xyz", "x"));
+ CHECK(util::starts_with("xyz", "xy"));
+ CHECK(util::starts_with("xyz", "xyz"));
+
+ CHECK_FALSE(util::starts_with("", "x"));
+ CHECK_FALSE(util::starts_with("x", "y"));
+ CHECK_FALSE(util::starts_with("x", "xy"));
+
+ // starts_with(string_view, string_view)
+ CHECK(util::starts_with(std::string(""), ""));
+ CHECK(util::starts_with(std::string("x"), ""));
+ CHECK(util::starts_with(std::string("x"), "x"));
+ CHECK(util::starts_with(std::string("xy"), ""));
+ CHECK(util::starts_with(std::string("xy"), "x"));
+ CHECK(util::starts_with(std::string("xy"), "xy"));
+ CHECK(util::starts_with(std::string("xyz"), ""));
+ CHECK(util::starts_with(std::string("xyz"), "x"));
+ CHECK(util::starts_with(std::string("xyz"), "xy"));
+ CHECK(util::starts_with(std::string("xyz"), "xyz"));
+
+ CHECK_FALSE(util::starts_with(std::string(""), "x"));
+ CHECK_FALSE(util::starts_with(std::string("x"), "y"));
+ CHECK_FALSE(util::starts_with(std::string("x"), "xy"));
+}
+
+TEST_CASE("util::strip_whitespace")
+{
+ CHECK(util::strip_whitespace("") == "");
+ CHECK(util::strip_whitespace("x") == "x");
+ CHECK(util::strip_whitespace(" x") == "x");
+ CHECK(util::strip_whitespace("x ") == "x");
+ CHECK(util::strip_whitespace(" x ") == "x");
+ CHECK(util::strip_whitespace(" \n\tx \n\t") == "x");
+ CHECK(util::strip_whitespace(" x y ") == "x y");
+}
+
+TEST_CASE("util::to_string")
+{
+ const uint8_t bytes[] = {'f', 'o', 'o'};
+ const char str[] = "foo";
+
+ CHECK(util::to_string(std::string(str)) == std::string(str));
+ CHECK(util::to_string(std::string_view(str)) == std::string(str));
+ CHECK(util::to_string(nonstd::span<const uint8_t>(bytes))
+ == std::string(str));
+ CHECK(util::to_string(util::Bytes(bytes, 3)) == std::string(str));
+}
+
+TEST_CASE("util::to_string_view")
+{
+ uint8_t bytes[] = {'f', 'o', 'o'};
+ char str[] = "foo";
+
+ CHECK(util::to_string_view(nonstd::span(bytes)) == std::string(str));
+}
+
+TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2019-2022 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 <TestUtil.hpp>
+#include <util/Bytes.hpp>
+#include <util/zstd.hpp>
+
+#include <third_party/doctest.h>
+
+#include <string>
+
+using TestUtil::TestContext;
+
+const util::Bytes compressed_ab{
+ 0x28, 0xb5, 0x2f, 0xfd, 0x20, 0x02, 0x11, 0x00, 0x00, 0x61, 0x62};
+
+TEST_CASE("util::zstd_compress")
+{
+ TestContext test_context;
+
+ util::Bytes output{'x'};
+ auto result = util::zstd_compress(util::Bytes{'a', 'b'}, output, 1);
+ CHECK(result);
+ CHECK(output.size() == 12);
+ util::Bytes expected{'x'};
+ expected.insert(expected.end(), compressed_ab.begin(), compressed_ab.end());
+ CHECK(output == expected);
+}
+
+TEST_CASE("util::zstd_decompress")
+{
+ TestContext test_context;
+
+ util::Bytes input = compressed_ab;
+ util::Bytes output{'x'};
+ auto result = util::zstd_decompress(input, output, 2);
+ CHECK(result);
+ CHECK(output == util::Bytes{'x', 'a', 'b'});
+}
+
+TEST_CASE("ZSTD roundtrip")
+{
+ TestContext test_context;
+
+ const util::Bytes data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ const size_t copies = 10000;
+ util::Bytes original_input;
+ for (size_t i = 0; i < copies; i++) {
+ original_input.insert(original_input.end(), data.begin(), data.end());
+ }
+
+ util::Bytes output;
+ auto result = util::zstd_compress(original_input, output, 1);
+ CHECK(result);
+ CHECK(output.size() < 100);
+
+ util::Bytes decompressed_input;
+ result =
+ util::zstd_decompress(output, decompressed_input, copies * data.size());
+ CHECK(result);
+ CHECK(decompressed_input == original_input);
+}