return Statistic::none;
}
+ if (arg.starts_with("-fmodule-file=")) {
+ // -fmodule-file=<name>=<path> or -fmodule-file=<path> for explicit C++
+ // module imports.
+ constexpr std::string_view module_file_flag = "-fmodule-file=";
+ auto value = std::string_view(arg).substr(module_file_flag.size());
+ if (auto sep = value.find('='); sep != std::string_view::npos) {
+ value = value.substr(sep + 1); // drop the optional "<name>=" prefix
+ }
+ args_info.module_files.emplace_back(value);
+ state.add_common_arg(args[i]);
+ return Statistic::none;
+ }
+
if (arg.starts_with("--sysroot=")) {
auto path = std::string_view(arg).substr(10);
auto relpath = core::make_relative_path(ctx, path);
}
}
+ // Hash the contents of explicitly imported module files (-fmodule-file=).
+ // The pcm content is not visible in the preprocessed output
+ for (const auto& module_file : ctx.args_info.module_files) {
+ LOG("Hashing module file {}", module_file);
+ hash.hash_delimiter("modulefile");
+ if (!hash_binary_file(ctx, hash, module_file)) {
+ return tl::unexpected(Statistic::error_hashing_extra_file);
+ }
+ }
+
if (!(ctx.args_info.build_session_file.empty())) {
// When using -fbuild-session-file, the actual mtime needs to be
// added to the hash to prevent false positive cache hits if the
--- /dev/null
+# Verify that the contents of explicitly imported module files (-fmodule-file=)
+# are part of the hash, so that changes to a module (e.g. template function
+# bodies) invalidate cached consumer object files.
+#
+# A precompiled module file (pcm) is not part of the preprocessed
+# output of a consumer translation unit, so without explicit hashing the consumer
+# gets a false cache hit after the module changes and silently runs stale code.
+
+SUITE_fmodule_file_PROBE() {
+ if ! $COMPILER_TYPE_CLANG || $COMPILER_USES_MSVC; then
+ echo "-fmodule-file not supported by compiler"
+ else
+ echo 'export module probe_module;' >probe_module.ixx
+ $COMPILER -std=gnu++23 -x c++-module -fmodule-output=probe_module.pcm \
+ -c probe_module.ixx -o probe_module.o 2>/dev/null \
+ || echo "compiler does not support C++ modules"
+ fi
+}
+
+SUITE_fmodule_file_SETUP() {
+ unset CCACHE_NODIRECT
+ export CCACHE_DEPEND=1
+
+ cat <<'EOF' >module.ixx
+export module somemodule;
+export template<typename T>
+int module_test() {
+ return 1;
+}
+EOF
+
+ cat <<'EOF' >main.cpp
+import somemodule;
+int main() {
+ return module_test<int>();
+}
+EOF
+}
+
+SUITE_fmodule_file() {
+ # -------------------------------------------------------------------------
+ TEST "consumer compile is cached and hits while the module is unchanged"
+
+ $COMPILER -std=gnu++23 -x c++-module -fmodule-output=somemodule.pcm -c module.ixx -o module.o
+ $CCACHE_COMPILE -std=gnu++23 -fmodule-file=somemodule=somemodule.pcm -c main.cpp -o main.o
+ expect_stat cache_miss 1
+ expect_stat direct_cache_hit 0
+
+ # Same directory, same module: the consumer must be a direct cache hit.
+ $CCACHE_COMPILE -std=gnu++23 -fmodule-file=somemodule=somemodule.pcm -c main.cpp -o main.o
+ expect_stat cache_miss 1
+ expect_stat direct_cache_hit 1
+
+ # -------------------------------------------------------------------------
+ TEST "template body change invalidates the consumer cache"
+
+ $COMPILER -std=gnu++23 -x c++-module -fmodule-output=somemodule.pcm -c module.ixx -o module.o
+ $CCACHE_COMPILE -std=gnu++23 -fmodule-file=somemodule=somemodule.pcm -c main.cpp -o main.o
+ expect_stat cache_miss 1
+ expect_stat direct_cache_hit 0
+
+ # Change a template function body in the module and regenerate the pcm.
+ cat <<'EOF' >module.ixx
+export module somemodule;
+export template<typename T>
+int module_test() {
+ return 2;
+}
+EOF
+ $COMPILER -std=gnu++23 -x c++-module -fmodule-output=somemodule.pcm -c module.ixx -o module.o
+
+ # The consumer must NOT be served from the cache: its object file would contain the old instantiated template body.
+ $CCACHE_COMPILE -std=gnu++23 -fmodule-file=somemodule=somemodule.pcm -c main.cpp -o main.o
+ expect_stat cache_miss 2
+ expect_stat direct_cache_hit 0
+}