# The MIT License (MIT) # # Copyright (c) 2017 Mateusz Pusz # # 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. cmake_minimum_required(VERSION 3.15) macro(_enable_ccache_failed) if(NOT _enable_ccache_QUIET) message(STATUS "Enabling ccache - failed") endif() return() endmacro() # # enable_ccache([PROGRAM] # ccache by default # [QUIET] [REQUIRED] # [MODE DIRECT_PREPROCESSOR|DIRECT_DEPEND|PREPROCESSOR|DEPEND] # DIRECT_PREPROCESSOR by default # [BASE_DIR dir] # [ACCOUNT_FOR_COMPILE_TIME_HEADER_CHANGES] # [ACCOUNT_FOR_PCH] # [ACCOUNT_FOR_MODULES] # [PREFIXES prefixes...] # ) # # BASE_DIR # Set this option to ${CMAKE_BINARY_DIR} if you use FetchContent a lot for many projects with the same build options. # Otherwise, if most of the sources come from the project itself then the default ${CMAKE_SOURCE_DIR} may be # a better choice. # # ACCOUNT_FOR_COMPILE_TIME_HEADER_CHANGES # Use it if some header files are being generated by the compilation process. # # ACCOUNT_FOR_PCH # Use it if precompiled headers are enabled in your project. Automatically includes uses # ACCOUNT_FOR_COMPILE_TIME_HEADER_CHANGES as well. # See here for details: https://ccache.dev/manual/4.2.1.html#_precompiled_headers # # ACCOUNT_FOR_MODULES # Use it for projects with C++20 modules. Requires DIRECT_DEPEND mode. # # PREFIXES # A list of other tools that should be used together with ccache as a compiler launcher # (i.e. distcc, icecc, sccache-dist, ...). # function(enable_ccache) if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) message(FATAL_ERROR "'enable_ccache' function should be called from the top-level CMakeLists.txt file!") # otherwise, it will not work for XCode endif() set(_options QUIET REQUIRED ACCOUNT_FOR_COMPILE_TIME_HEADER_CHANGES ACCOUNT_FOR_PCH ACCOUNT_FOR_MODULES) set(_one_value_args PROGRAM MODE BASE_DIR) set(_multi_value_args PREFIXES) cmake_parse_arguments(PARSE_ARGV 0 _enable_ccache "${_options}" "${_one_value_args}" "${_multi_value_args}") # validate and process arguments if(_enable_ccache_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Invalid arguments '${_enable_ccache_UNPARSED_ARGUMENTS}'") endif() if(_enable_ccache_KEYWORDS_MISSING_VALUES) message(FATAL_ERROR "No value provided for '${_enable_ccache_KEYWORDS_MISSING_VALUES}'") endif() if(_enable_ccache_MODE) set(_valid_mode_values DIRECT_PREPROCESSOR DIRECT_DEPEND PREPROCESSOR DEPEND) if(NOT _enable_ccache_MODE IN_LIST _valid_mode_values) message(FATAL_ERROR "'MODE' should be one of ${_valid_mode_values}") endif() endif() if(NOT _enable_ccache_QUIET) message(STATUS "Enabling ccache") endif() if(${_enable_ccache_REQUIRED}) set(_error_log_level FATAL_ERROR) elseif(NOT _enable_ccache_QUIET) set(_error_log_level STATUS) endif() if(NOT CMAKE_GENERATOR MATCHES "Ninja|Makefiles|Xcode") if(DEFINED _error_log_level) message(${_error_log_level} "ccache support not enabled: unsupported generator '${CMAKE_GENERATOR}'") endif() _enable_ccache_failed() endif() if(NOT DEFINED _enable_ccache_PROGRAM) set(_enable_ccache_PROGRAM ccache) endif() find_program(CCACHE_PATH ${_enable_ccache_PROGRAM}) if(CCACHE_PATH) if(NOT _enable_ccache_QUIET) message(STATUS " Executable: ${CCACHE_PATH}") endif() # get version number execute_process(COMMAND "${CCACHE_PATH}" --version OUTPUT_VARIABLE _output) string(REPLACE "\n" ";" _output "${_output}") foreach(_line ${_output}) string(REGEX REPLACE "ccache version ([\\.0-9]+)$" "\\1" _ccache_version "${_line}") if(_ccache_version) if(NOT _enable_ccache_QUIET) message(STATUS " Version: ${_ccache_version}") endif() break() endif() endforeach() else() if(DEFINED _error_log_level) message(${_error_log_level} " '${_enable_ccache_PROGRAM}' executable was not found") endif() _enable_ccache_failed() endif() if("${_ccache_version}" VERSION_LESS 3.3.0) list(APPEND _ccacheEnv CCACHE_CPP2=1) # avoids spurious warnings with some compilers for ccache older than 3.3 endif() if(_enable_ccache_MODE STREQUAL DIRECT_DEPEND) list(APPEND _ccacheEnv CCACHE_DIRECT=1 CCACHE_DEPEND=1) elseif(_enable_ccache_MODE STREQUAL PREPROCESSOR) list(APPEND _ccacheEnv CCACHE_NO_DIRECT=1 CCACHE_NO_DEPEND=1) elseif(_enable_ccache_MODE STREQUAL DEPEND) list(APPEND _ccacheEnv CCACHE_NO_DIRECT=1 CCACHE_DEPEND=1) else() set(_enable_ccache_MODE DIRECT_PREPROCESSOR) list(APPEND _ccacheEnv CCACHE_DIRECT=1 CCACHE_NO_DEPEND=1) endif() if(_enable_ccache_BASE_DIR) # CCACHE_ABSSTDERR=1 # reverts absolute paths after applying CCACHE_BASEDIR if(NOT EXISTS ${_enable_ccache_BASE_DIR}) message(FATAL_ERROR "Base directory '${_enable_ccache_BASE_DIR}' does not exist") endif() list(APPEND _ccacheEnv "CCACHE_BASEDIR=${_enable_ccache_BASE_DIR}") endif() if(_enable_ccache_PREFIXES) string(REPLACE ";" " " _prefixes_txt "${_enable_ccache_PREFIXES}") list(APPEND _ccacheEnv "CCACHE_PREFIX=${_prefixes_txt}") endif() if(_enable_ccache_ACCOUNT_FOR_COMPILE_TIME_HEADER_CHANGES) list(APPEND _sloppiness include_file_mtime include_file_ctime) endif() if(_enable_ccache_ACCOUNT_FOR_PCH) list(APPEND _sloppiness pch_defines time_macros include_file_mtime include_file_ctime) endif() if(_enable_ccache_ACCOUNT_FOR_MODULES) if(NOT _enable_ccache_MODE STREQUAL DIRECT_DEPEND) message(FATAL_ERROR "DIRECT_DEPEND mode required with ACCOUNT_FOR_MODULES option") endif() list(APPEND _sloppiness modules) endif() if(_sloppiness) list(REMOVE_DUPLICATES _sloppiness) string(REPLACE ";" "," _sloppiness_txt "${_sloppiness}") list(APPEND _ccacheEnv "CCACHE_SLOPPINESS=${_sloppiness_txt}") endif() if(NOT _enable_ccache_QUIET) message(STATUS " Environment: ${_ccacheEnv}") endif() if(CMAKE_GENERATOR MATCHES "Ninja|Makefiles") foreach(_lang IN ITEMS C CXX OBJC OBJCXX CUDA) set(CMAKE_${_lang}_COMPILER_LAUNCHER ${CMAKE_COMMAND} -E env ${_ccacheEnv} ${CCACHE_PATH} PARENT_SCOPE) endforeach() elseif(CMAKE_GENERATOR STREQUAL Xcode) # Each of the Xcode project variables allow specifying only a single value, but the ccache command line needs to have multiple options. # A separate launch script needs to be written out and the project variables pointed at them. foreach(_lang IN ITEMS C CXX) set(launch${_lang} ${CMAKE_BINARY_DIR}/launch-${_lang}) file(WRITE ${launch${_lang}} "#!/bin/bash\n\n") foreach(keyVal IN LISTS _ccacheEnv) file(APPEND ${launch${_lang}} "export ${keyVal}\n") endforeach() file(APPEND ${launch${_lang}} "exec \"${CCACHE_PROGRAM}\" " "\"${CMAKE_${_lang}_COMPILER}\" \"$@\"\n") execute_process(COMMAND chmod a+rx ${launch${_lang}}) endforeach() set(CMAKE_XCODE_ATTRIBUTE_CC ${launchC} PARENT_SCOPE) set(CMAKE_XCODE_ATTRIBUTE_CXX ${launchCXX} PARENT_SCOPE) set(CMAKE_XCODE_ATTRIBUTE_LD ${launchC} PARENT_SCOPE) set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS ${launchCXX} PARENT_SCOPE) endif() if(NOT _enable_ccache_QUIET) message(STATUS "Enabling ccache - done") endif() endfunction()