Include -fmodule-file flags in compile_commands.json
This change updates GN to include the `-fmodule-file` flags for all
module dependencies in the `compile_commands.json` file.
To achieve this, the module dependency gathering logic has been
refactored from `ninja_c_binary_target_writer.cc` into a new, shared
utility, `ninja_module_writer_util.cc` in
https://gn-review.googlesource.com/c/gn/+/19880 . The
`compile_commands_writer` now uses this utility to append the correct
module flags to the compilation commands, ensuring that tools have the
information they need to correctly process C++ modules.
Bug: 443228626
Change-Id: I74c2f7c01b3d8f0cd468594a2c8632275382549b
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/19840
Reviewed-by: David Turner <digit@google.com>
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/compile_commands_writer.cc b/src/gn/compile_commands_writer.cc
index 23d6029..c0c9ae7 100644
--- a/src/gn/compile_commands_writer.cc
+++ b/src/gn/compile_commands_writer.cc
@@ -15,9 +15,12 @@
#include "gn/config_values_extractors.h"
#include "gn/deps_iterator.h"
#include "gn/escape.h"
+#include "gn/ninja_module_writer_util.h"
#include "gn/ninja_target_command_util.h"
#include "gn/path_output.h"
+#include "gn/resolved_target_data.h"
#include "gn/string_output_buffer.h"
+#include "gn/substitution_list.h"
#include "gn/substitution_writer.h"
// Structure of JSON output file
@@ -49,6 +52,8 @@
std::string cflags_objcc;
std::string framework_dirs;
std::string frameworks;
+ std::string clang_module_deps;
+ std::string clang_module_deps_no_self;
};
// Helper template function to call RecursiveTargetConfigToStream<std::string>
@@ -71,6 +76,7 @@
void SetupCompileFlags(const Target* target,
PathOutput& path_output,
EscapeOptions opts,
+ const ResolvedTargetData& resolved,
CompileFlags& flags) {
bool has_precompiled_headers =
target->config_values().has_precompiled_headers();
@@ -94,6 +100,28 @@
target, &ConfigValues::include_dirs,
IncludeWriter(path_output));
+ std::vector<ClangModuleDep> module_dep_info =
+ GetModuleDepsInformation(target, resolved);
+ if (!module_dep_info.empty()) {
+ std::ostringstream module_deps_out;
+ for (const auto& module_dep : module_dep_info) {
+ module_deps_out << " -fmodule-file=";
+ path_output.WriteFile(module_deps_out, module_dep.pcm);
+ }
+ base::EscapeJSONString(module_deps_out.str(), false,
+ &flags.clang_module_deps);
+
+ std::ostringstream module_deps_no_self_out;
+ for (const auto& module_dep : module_dep_info) {
+ if (!module_dep.is_self) {
+ module_deps_no_self_out << " -fmodule-file=";
+ path_output.WriteFile(module_deps_no_self_out, module_dep.pcm);
+ }
+ }
+ base::EscapeJSONString(module_deps_no_self_out.str(), false,
+ &flags.clang_module_deps_no_self);
+ }
+
// Helper lambda to call WriteOneFlag() and return the resulting
// escaped JSON string.
auto one_flag = [&](RecursiveWriterConfig config,
@@ -177,6 +205,10 @@
out << flags.frameworks;
} else if (range.type == &CSubstitutionIncludeDirs) {
out << flags.includes;
+ } else if (range.type == &CSubstitutionModuleDeps) {
+ out << flags.clang_module_deps;
+ } else if (range.type == &CSubstitutionModuleDepsNoSelf) {
+ out << flags.clang_module_deps_no_self;
} else if (range.type == &CSubstitutionCFlags) {
out << flags.cflags;
} else if (range.type == &CSubstitutionCFlagsC) {
@@ -233,6 +265,7 @@
EscapeOptions opts;
opts.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND;
+ ResolvedTargetData resolved;
for (const auto* target : all_targets) {
if (!target->IsBinary())
@@ -247,7 +280,7 @@
ESCAPE_NINJA_COMMAND);
CompileFlags flags;
- SetupCompileFlags(target, path_output, opts, flags);
+ SetupCompileFlags(target, path_output, opts, resolved, flags);
for (const auto& source : target->sources()) {
// If this source is not a C/C++/ObjC/ObjC++ source (not header) file,
diff --git a/src/gn/compile_commands_writer_unittest.cc b/src/gn/compile_commands_writer_unittest.cc
index 2f0294c..badd100 100644
--- a/src/gn/compile_commands_writer_unittest.cc
+++ b/src/gn/compile_commands_writer_unittest.cc
@@ -7,6 +7,7 @@
#include <memory>
#include <sstream>
#include <utility>
+#include <vector>
#include "gn/config.h"
#include "gn/ninja_target_command_util.h"
@@ -599,6 +600,91 @@
EXPECT_EQ(expected, out);
}
+TEST_F(CompileCommandsTest, ModuleMap) {
+ Err err;
+
+ // This setup's toolchain does not have precompiled headers defined.
+ // A precompiled header toolchain.
+ Settings module_settings(build_settings(), "withmodules/");
+ Toolchain module_toolchain(&module_settings,
+ Label(SourceDir("//toolchain/"), "withmodules"));
+ module_settings.set_toolchain_label(module_toolchain.label());
+ module_settings.set_default_toolchain_label(toolchain()->label());
+
+ // Declare a C++ compiler that supports PCH.
+ std::unique_ptr<Tool> cxx = Tool::CreateTool(CTool::kCToolCxx);
+ CTool* cxx_tool = cxx->AsC();
+ TestWithScope::SetCommandForTool(
+ "c++ {{source}} {{cflags}} {{cflags_cc}} {{module_deps}} "
+ "{{defines}} {{include_dirs}} -o {{output}}",
+ cxx_tool);
+ cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ module_toolchain.SetTool(std::move(cxx));
+
+ std::unique_ptr<Tool> cxx_module_tool =
+ Tool::CreateTool(CTool::kCToolCxxModule);
+ TestWithScope::SetCommandForTool(
+ "c++ {{source}} {{cflags}} {{cflags_cc}} {{module_deps_no_self}} "
+ "{{defines}} {{include_dirs}} -fmodule-name={{label}} -c -x c++ "
+ "-Xclang -emit-module -o {{output}}",
+ cxx_module_tool.get());
+ cxx_module_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.pcm"));
+ module_toolchain.SetTool(std::move(cxx_module_tool));
+
+ module_toolchain.ToolchainSetupComplete();
+
+ Target module_target(&module_settings, Label(SourceDir("//foo/"), "module"));
+ module_target.set_output_type(Target::SOURCE_SET);
+ module_target.visibility().SetPublic();
+ module_target.sources().push_back(SourceFile("//foo/foo.modulemap"));
+ module_target.source_types_used().Set(SourceFile::SOURCE_MODULEMAP);
+ module_target.SetToolchain(&module_toolchain);
+ ASSERT_TRUE(module_target.OnResolved(&err));
+
+ Target dep_target(&module_settings, Label(SourceDir("//foo/"), "dep"));
+ dep_target.set_output_type(Target::SOURCE_SET);
+ dep_target.visibility().SetPublic();
+ dep_target.sources().push_back(SourceFile("//foo/dep.cc"));
+ dep_target.source_types_used().Set(SourceFile::SOURCE_CPP);
+ dep_target.public_deps().push_back(LabelTargetPair(&module_target));
+ dep_target.SetToolchain(&module_toolchain);
+ ASSERT_TRUE(dep_target.OnResolved(&err));
+
+ std::vector<const Target*> targets;
+ targets.push_back(&module_target);
+ targets.push_back(&dep_target);
+
+ CompileCommandsWriter writer;
+ std::string out = writer.RenderJSON(build_settings(), targets);
+
+#if defined(OS_WIN)
+ const char expected[] =
+ "[\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/dep.cc\",\r\n"
+ " \"directory\": \"out/Debug\",\r\n"
+ " \"command\": \"c++ ../../foo/dep.cc "
+ "-fmodule-file=withmodules/obj/foo/module.foo.pcm -o "
+ "withmodules/obj/foo/dep.dep.o\"\r\n"
+ " }\r\n"
+ "]\r\n";
+#else
+ const char expected[] =
+ "[\n"
+ " {\n"
+ " \"file\": \"../../foo/dep.cc\",\n"
+ " \"directory\": \"out/Debug\",\n"
+ " \"command\": \"c++ ../../foo/dep.cc "
+ "-fmodule-file=withmodules/obj/foo/module.foo.pcm -o "
+ "withmodules/obj/foo/dep.dep.o\"\n"
+ " }\n"
+ "]\n";
+#endif
+ EXPECT_EQ(expected, out) << expected << "\n" << out;
+}
+
TEST_F(CompileCommandsTest, CollectTargets) {
// Contruct the dependency tree:
//