cmake_minimum_required(VERSION 3.20) file(STRINGS "${LIST_FILE}" entries) set(sources) set(guard) set(submodules) set(includeDirectories) set(reviewed) foreach(entry IN LISTS entries) string(FIND "${entry}" " " split) string(SUBSTRING "${entry}" 0 ${split} section) math(EXPR split "${split} + 1") string(SUBSTRING "${entry}" ${split} -1 value) if(section STREQUAL "sources") list(APPEND sources "${value}") elseif(section STREQUAL "guard") list(APPEND guard "${value}") elseif(section STREQUAL "submodules") list(APPEND submodules "${value}") elseif(section STREQUAL "includes") list(APPEND includeDirectories "${value}") elseif(section STREQUAL "reviewed") list(APPEND reviewed "${value}") endif() endforeach() list(SORT sources) list(SORT guard) get_filename_component(rootReal "${ROOT}" REALPATH) get_filename_component(recompilerReal "${RECOMPILER_DIR}" REALPATH) set(sourcesReal) foreach(source IN LISTS sources) get_filename_component(real "${source}" REALPATH) list(APPEND sourcesReal "${real}") endforeach() set(failures) set(manifest "${EXTRA}\n") set(switches) foreach(source IN LISTS sources) if(NOT EXISTS "${source}") continue() endif() file(SHA256 "${source}" digest) file(RELATIVE_PATH relative "${ROOT}" "${source}") string(APPEND manifest "${relative} ${digest}\n") if(NOT source MATCHES "\\.(cpp|hpp|h)$") continue() endif() file(STRINGS "${source}" lines REGEX "\"APS5_[A-Z0-9_]+\"") foreach(line IN LISTS lines) string(REGEX MATCHALL "\"APS5_[A-Z0-9_]+\"" names "${line}") list(APPEND switches ${names}) endforeach() string(FIND "${source}" "${RECOMPILER_DIR}/" inRecompiler) if(NOT inRecompiler EQUAL 0) continue() endif() get_filename_component(sourceDirectory "${source}" DIRECTORY) file(STRINGS "${source}" lines REGEX "^[ \t]*#[ \t]*include[ \t]*\"[^\"]+\"") foreach(line IN LISTS lines) string(REGEX REPLACE "^[ \t]*#[ \t]*include[ \t]*\"([^\"]+)\".*$" "\\1" included "${line}") set(resolved) foreach(directory IN ITEMS "${sourceDirectory}" ${includeDirectories}) if(EXISTS "${directory}/${included}" AND NOT IS_DIRECTORY "${directory}/${included}") get_filename_component(resolved "${directory}/${included}" REALPATH) break() endif() endforeach() file(RELATIVE_PATH relativeSource "${ROOT}" "${source}") if(NOT resolved) continue() endif() string(FIND "${resolved}" "${recompilerReal}/" resolvedInRecompiler) string(FIND "${resolved}" "${rootReal}/3rdparty/" resolvedInThirdParty) if(resolvedInRecompiler EQUAL 0 OR resolvedInThirdParty EQUAL 0 OR resolved IN_LIST sourcesReal) continue() endif() file(RELATIVE_PATH relativeResolved "${rootReal}" "${resolved}") list(APPEND failures "${relativeSource} includes ${relativeResolved}, which the shader disk cache key does not hash: add it to shaderCacheKeySources in core/shader/recompiler/CMakeLists.txt") endforeach() endforeach() foreach(submodule IN LISTS submodules) file(RELATIVE_PATH relative "${ROOT}" "${submodule}") set(commit) if(GIT) execute_process(COMMAND "${GIT}" -C "${submodule}" rev-parse --show-toplevel HEAD OUTPUT_VARIABLE output OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE status ERROR_QUIET) if(status EQUAL 0) string(REPLACE "\n" ";" output "${output}") list(GET output 0 topLevel) list(GET output 1 head) get_filename_component(topLevel "${topLevel}" REALPATH) get_filename_component(submoduleReal "${submodule}" REALPATH) if(topLevel STREQUAL submoduleReal) set(commit "${head}") endif() endif() endif() if(NOT commit AND EXISTS "${submodule}/CMakeLists.txt") file(SHA256 "${submodule}/CMakeLists.txt" digest) set(commit "unversioned ${digest}") endif() string(APPEND manifest "submodule ${relative} ${commit}\n") endforeach() foreach(source IN LISTS guard) if(NOT EXISTS "${source}") continue() endif() file(RELATIVE_PATH relative "${ROOT}" "${source}") file(STRINGS "${source}" lines REGEX "ShaderRecompiler|setenv|putenv") foreach(line IN LISTS lines) if(line MATCHES "using[ \t]+namespace[ \t]+ShaderRecompiler" OR line MATCHES "namespace[ \t]+[A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*(::)?ShaderRecompiler[ \t]*;") list(APPEND failures "${relative} brings the ShaderRecompiler namespace in whole, which hides its calls from the shader disk cache key review: qualify them") endif() if(line MATCHES "(^|[^A-Za-z0-9_])(setenv|putenv|_putenv_s)[ \t]*\\(") list(APPEND failures "${relative} sets an environment variable, which can change the recompiler's APS5_* switches behind the shader disk cache key") endif() string(REGEX MATCHALL "ShaderRecompiler::[A-Za-z_][A-Za-z0-9_]*(::[A-Za-z_][A-Za-z0-9_]*)*[ \t]*\\(" calls "${line}") string(REGEX MATCHALL "using[ \t]+ShaderRecompiler::[A-Za-z_][A-Za-z0-9_]*(::[A-Za-z_][A-Za-z0-9_]*)*" declarations "${line}") foreach(use IN LISTS calls declarations) string(REGEX REPLACE "^(using[ \t]+)?ShaderRecompiler::([A-Za-z0-9_:]+).*$" "\\2" name "${use}") if(NOT name IN_LIST reviewed) list(APPEND failures "${relative} uses ShaderRecompiler::${name}, which has not been reviewed for the shader disk cache key: if it can change what a recompile produces other than through the RecompileRequest, key that input in ShaderDiskCache::BuildKey, then add the name to shaderCacheReviewedNames in core/shader/recompiler/CMakeLists.txt") endif() endforeach() endforeach() endforeach() if(failures) list(REMOVE_DUPLICATES failures) list(JOIN failures "\n " failureText) message(FATAL_ERROR "Shader disk cache key check failed:\n ${failureText}") endif() string(SHA256 version "${manifest}") string(SUBSTRING "${version}" 0 16 version) list(REMOVE_DUPLICATES switches) list(SORT switches) list(JOIN switches ",\n " switchList) set(content "#ifndef CORE_SHADER_RECOMPILER_SHADERCACHEVERSION_HPP #define CORE_SHADER_RECOMPILER_SHADERCACHEVERSION_HPP #include #include namespace ShaderRecompiler::ShaderDiskCache::Generated { inline constexpr std::uint64_t SourceVersion = 0x${version}ull; inline constexpr std::string_view RecompilerSwitches[] = { ${switchList} }; } #endif ") if(EXISTS "${OUTPUT}") file(READ "${OUTPUT}" previous) if(previous STREQUAL content) return() endif() endif() file(WRITE "${OUTPUT}" "${content}")