Add modulemap generation to GN. This is the first step in adding a layering check to GN. It generates modulemap files, and adds -fmodule-map-file to the command-line. This is guarded behind a flag, so there should be no change to the change in behaviour of GN Bug: b:491925153 Change-Id: Iaadf759240de829349796c43f4deda9a6a6a6964 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/21521 Commit-Queue: Matt Stark <msta@google.com> Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/build/gen.py b/build/gen.py index 6ccd5fe..e40627c 100755 --- a/build/gen.py +++ b/build/gen.py
@@ -828,6 +828,7 @@ 'src/gn/args_unittest.cc', 'src/gn/builder_record_map_unittest.cc', 'src/gn/builder_unittest.cc', + 'src/gn/binary_target_generator_unittest.cc', 'src/gn/bundle_data_unittest.cc', 'src/gn/c_include_iterator_unittest.cc', 'src/gn/command_format_unittest.cc',
diff --git a/docs/reference.md b/docs/reference.md index 92ffbf9..0532960 100644 --- a/docs/reference.md +++ b/docs/reference.md
@@ -78,6 +78,7 @@ * [current_os: [string] The operating system of the current toolchain.](#var_current_os) * [current_toolchain: [string] Label of the current toolchain.](#var_current_toolchain) * [default_toolchain: [string] Label of the default toolchain.](#var_default_toolchain) + * [generate_modulemap: [string] Mode for generating modulemaps.](#var_generate_modulemap) * [gn_version: [number] The version of gn.](#var_gn_version) * [host_cpu: [string] The processor architecture that GN is running on.](#var_host_cpu) * [host_os: [string] The operating system that GN is running on.](#var_host_os) @@ -4615,6 +4616,14 @@ A fully-qualified label representing the default toolchain, which may not necessarily be the current one (see "current_toolchain"). ``` +### <a name="var_generate_modulemap"></a>**generate_modulemap**: [string] Mode for generating modulemaps. [Back to Top](#gn-reference) + +#### **Possible values**: +``` + "none" (default): Don't generate a modulemap file for the target. + "textual": Generate a modulemap file for the target. + All public headers will be marked as textual. +``` ### <a name="var_gn_version"></a>**gn_version**: [number] The version of gn. [Back to Top](#gn-reference) ```
diff --git a/src/gn/binary_target_generator.cc b/src/gn/binary_target_generator.cc index 1517734..39eb47c 100644 --- a/src/gn/binary_target_generator.cc +++ b/src/gn/binary_target_generator.cc
@@ -14,7 +14,9 @@ #include "gn/rust_variables.h" #include "gn/scope.h" #include "gn/settings.h" +#include "gn/source_file.h" #include "gn/swift_values_generator.h" +#include "gn/target.h" #include "gn/value_extractors.h" #include "gn/variables.h" @@ -73,6 +75,9 @@ if (!FillModuleName()) return; + if (!FillModuleType()) + return; + if (target_->source_types_used().RustSourceUsed()) { RustValuesGenerator rustgen(target_, scope_, function_call_, err_); rustgen.Run(); @@ -284,3 +289,40 @@ target_->set_module_name(value->string_value()); return true; } + +bool BinaryTargetGenerator::FillModuleType() { + // Put this first so it gets marked as used even if it's unnecessary. + const Value* generate_modulemap_val = + scope_->GetValue(variables::kGenerateModulemap, true); + + if (target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) { + target_->set_module_type(Target::EXPLICIT_MODULEMAP); + return true; + } + + if (target_->all_headers_public() + ? !target_->source_types_used().Get(SourceFile::SOURCE_H) + : target_->public_headers().empty()) { + target_->set_module_type(Target::UNNECESSARY_MODULEMAP); + return true; + } + + if (!generate_modulemap_val) { + return true; + } + + generate_modulemap_val->VerifyTypeIs(Value::STRING, err_); + if (err_->has_error()) { + return false; + } + auto value = generate_modulemap_val->string_value(); + if (value == "textual") { + target_->set_module_type(Target::GENERATED_TEXTUAL_MODULEMAP); + } else if (value != "none") { + *err_ = Err(*generate_modulemap_val, + "Invalid value for generate_modulemap. Expected \"textual\" or " + "\"none\""); + return false; + } + return true; +}
diff --git a/src/gn/binary_target_generator.h b/src/gn/binary_target_generator.h index b6d4c2e..51530d0 100644 --- a/src/gn/binary_target_generator.h +++ b/src/gn/binary_target_generator.h
@@ -33,6 +33,7 @@ bool FillPool(); bool ValidateSources(); bool FillModuleName(); + bool FillModuleType(); Target::OutputType output_type_;
diff --git a/src/gn/binary_target_generator_unittest.cc b/src/gn/binary_target_generator_unittest.cc new file mode 100644 index 0000000..c1e4215 --- /dev/null +++ b/src/gn/binary_target_generator_unittest.cc
@@ -0,0 +1,86 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gn/binary_target_generator.h" +#include "gn/err.h" +#include "gn/scheduler.h" +#include "gn/target.h" +#include "gn/test_with_scheduler.h" +#include "gn/test_with_scope.h" +#include "util/test/test.h" + +using BinaryTargetGeneratorTest = TestWithScheduler; + +TEST_F(BinaryTargetGeneratorTest, UnnecessaryModuleMapAllPublic) { + TestWithScope setup; + Scope::ItemVector items_; + setup.scope()->set_item_collector(&items_); + setup.scope()->set_source_dir(SourceDir("//test/")); + + TestParseInput input( + R"(static_library("foo") { + generate_modulemap = "textual" + sources = [ "//foo.cc" ] + })"); + ASSERT_FALSE(input.has_error()); + + Err err; + input.parsed()->Execute(setup.scope(), &err); + ASSERT_FALSE(err.has_error()) << err.message(); + + ASSERT_EQ(1u, items_.size()); + Target* target = items_[0]->AsTarget(); + ASSERT_TRUE(target); + + EXPECT_EQ(Target::UNNECESSARY_MODULEMAP, target->module_type()); +} + +TEST_F(BinaryTargetGeneratorTest, GeneratedModuleMapAllPublic) { + TestWithScope setup; + Scope::ItemVector items_; + setup.scope()->set_item_collector(&items_); + setup.scope()->set_source_dir(SourceDir("//test/")); + + TestParseInput input( + R"(static_library("foo") { + generate_modulemap = "textual" + sources = [ "//foo.cc", "//foo.h" ] + })"); + ASSERT_FALSE(input.has_error()); + + Err err; + input.parsed()->Execute(setup.scope(), &err); + ASSERT_FALSE(err.has_error()) << err.message(); + + ASSERT_EQ(1u, items_.size()); + Target* target = items_[0]->AsTarget(); + ASSERT_TRUE(target); + + EXPECT_EQ(Target::GENERATED_TEXTUAL_MODULEMAP, target->module_type()); +} + +TEST_F(BinaryTargetGeneratorTest, GeneratedModuleMap) { + TestWithScope setup; + Scope::ItemVector items_; + setup.scope()->set_item_collector(&items_); + setup.scope()->set_source_dir(SourceDir("//test/")); + + TestParseInput input( + R"(static_library("foo") { + generate_modulemap = "textual" + sources = [ "//foo.cc" ] + public = ["//foo.h"] + })"); + ASSERT_FALSE(input.has_error()); + + Err err; + input.parsed()->Execute(setup.scope(), &err); + ASSERT_FALSE(err.has_error()) << err.message(); + + ASSERT_EQ(1u, items_.size()); + Target* target = items_[0]->AsTarget(); + ASSERT_TRUE(target); + + EXPECT_EQ(Target::GENERATED_TEXTUAL_MODULEMAP, target->module_type()); +}
diff --git a/src/gn/compile_commands_writer.cc b/src/gn/compile_commands_writer.cc index bef0287..2ec76a8 100644 --- a/src/gn/compile_commands_writer.cc +++ b/src/gn/compile_commands_writer.cc
@@ -101,13 +101,12 @@ target, &ConfigValues::include_dirs, IncludeWriter(path_output)); - std::vector<ClangModuleDep> module_dep_info = + std::set<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=" << module_dep.module_name << "="; - path_output.WriteFile(module_deps_out, module_dep.pcm); + module_dep.Write(module_deps_out, path_output); } base::EscapeJSONString(module_deps_out.str(), false, &flags.clang_module_deps); @@ -115,13 +114,7 @@ std::ostringstream module_deps_no_self_out; for (const auto& module_dep : module_dep_info) { if (!module_dep.is_self) { - if (module_dep.modulemap) { - module_deps_no_self_out << " -fmodule-map-file="; - path_output.WriteFile(module_deps_no_self_out, *module_dep.modulemap); - } - module_deps_no_self_out << " -fmodule-file=" << module_dep.module_name - << "="; - path_output.WriteFile(module_deps_no_self_out, module_dep.pcm); + module_dep.Write(module_deps_no_self_out, path_output); } } base::EscapeJSONString(module_deps_no_self_out.str(), false,
diff --git a/src/gn/compile_commands_writer_unittest.cc b/src/gn/compile_commands_writer_unittest.cc index d7fcad0..c645ac6 100644 --- a/src/gn/compile_commands_writer_unittest.cc +++ b/src/gn/compile_commands_writer_unittest.cc
@@ -640,6 +640,7 @@ module_target.visibility().SetPublic(); module_target.sources().push_back(SourceFile("//foo/foo.modulemap")); module_target.source_types_used().Set(SourceFile::SOURCE_MODULEMAP); + module_target.set_module_type(Target::EXPLICIT_MODULEMAP); module_target.SetToolchain(&module_toolchain); ASSERT_TRUE(module_target.OnResolved(&err)); @@ -673,6 +674,7 @@ " \"file\": \"../../foo/dep.cc\",\r\n" " \"directory\": \"out/Debug\",\r\n" " \"command\": \"c++ ../../foo/dep.cc " + "-fmodule-map-file=../../foo/foo.modulemap " "-fmodule-file=module=withmodules/obj/foo/module.foo.pcm -o " "withmodules/obj/foo/dep.dep.o\"\r\n" " }\r\n" @@ -691,6 +693,7 @@ " \"file\": \"../../foo/dep.cc\",\n" " \"directory\": \"out/Debug\",\n" " \"command\": \"c++ ../../foo/dep.cc " + "-fmodule-map-file=../../foo/foo.modulemap " "-fmodule-file=module=withmodules/obj/foo/module.foo.pcm -o " "withmodules/obj/foo/dep.dep.o\"\n" " }\n"
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc index 412a706..13bc73d 100644 --- a/src/gn/ninja_binary_target_writer.cc +++ b/src/gn/ninja_binary_target_writer.cc
@@ -18,6 +18,7 @@ #include "gn/ninja_utils.h" #include "gn/pool.h" #include "gn/settings.h" +#include "gn/string_output_buffer.h" #include "gn/string_utils.h" #include "gn/substitution_writer.h" #include "gn/target.h" @@ -56,6 +57,28 @@ writer.Run(); } +void NinjaBinaryTargetWriter::WriteModuleMap(std::ostream& out, + const SourceDir& out_dir) { + out << "module \"" << target_->module_name() << "\" {\n"; + if (target_->all_headers_public()) { + for (const auto& header : target_->sources()) { + if (header.GetType() == SourceFile::SOURCE_H) { + out << " textual header \""; + out << RebasePath(header.value(), out_dir, + settings_->build_settings()->root_path_utf8()); + out << "\"\n"; + } + } + } + for (const auto& header : target_->public_headers()) { + out << " textual header \""; + out << RebasePath(header.value(), out_dir, + settings_->build_settings()->root_path_utf8()); + out << "\"\n"; + } + out << " export *\n}\n"; +} + std::vector<OutputFile> NinjaBinaryTargetWriter::WriteInputsStampOrPhonyAndGetDep( size_t num_output_uses) const {
diff --git a/src/gn/ninja_binary_target_writer.h b/src/gn/ninja_binary_target_writer.h index b64f884..75448c0 100644 --- a/src/gn/ninja_binary_target_writer.h +++ b/src/gn/ninja_binary_target_writer.h
@@ -21,6 +21,7 @@ ~NinjaBinaryTargetWriter() override; void Run() override; + void WriteModuleMap(std::ostream& out, const SourceDir& out_dir); protected: // Structure used to return the classified deps from |GetDeps| method.
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc index d9ee9fe..9041341 100644 --- a/src/gn/ninja_c_binary_target_writer.cc +++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -63,7 +63,7 @@ NinjaCBinaryTargetWriter::~NinjaCBinaryTargetWriter() = default; void NinjaCBinaryTargetWriter::Run() { - std::vector<ClangModuleDep> module_dep_info = + std::set<ClangModuleDep> module_dep_info = GetModuleDepsInformation(target_, resolved()); WriteCompilerVars(module_dep_info); @@ -169,7 +169,7 @@ } void NinjaCBinaryTargetWriter::WriteCompilerVars( - const std::vector<ClangModuleDep>& module_dep_info) { + const std::set<ClangModuleDep>& module_dep_info) { const SubstitutionBits& subst = target_->toolchain()->substitution_bits(); WriteCCompilerVars(subst, /*indent=*/false, @@ -204,7 +204,7 @@ void NinjaCBinaryTargetWriter::WriteModuleDepsSubstitution( const Substitution* substitution, - const std::vector<ClangModuleDep>& module_dep_info, + const std::set<ClangModuleDep>& module_dep_info, bool include_self) { if (target_->toolchain()->substitution_bits().used.count(substitution)) { EscapeOptions options; @@ -213,15 +213,7 @@ out_ << substitution->ninja_name << " ="; for (const auto& module_dep : module_dep_info) { if (!module_dep.is_self || include_self) { - if (module_dep.modulemap) { - out_ << " -fmodule-map-file="; - path_output_.WriteFile(out_, *module_dep.modulemap); - } - out_ << " "; - EscapeStringToStream(out_, "-fmodule-file=", options); - EscapeStringToStream(out_, module_dep.module_name, options); - out_ << "="; - path_output_.WriteFile(out_, module_dep.pcm); + module_dep.Write(out_, path_output_); } } @@ -390,7 +382,7 @@ const std::vector<OutputFile>& pch_deps, const std::vector<OutputFile>& input_deps, const std::vector<OutputFile>& order_only_deps, - const std::vector<ClangModuleDep>& module_dep_info, + const std::set<ClangModuleDep>& module_dep_info, std::vector<OutputFile>* object_files, std::vector<OutputFile>* extra_files, std::vector<SourceFile>* other_files) { @@ -444,8 +436,8 @@ } for (const auto& module_dep : module_dep_info) { - if (tool_outputs[0] != module_dep.pcm) - deps.push_back(module_dep.pcm); + if (module_dep.pcm && tool_outputs[0] != *module_dep.pcm) + deps.push_back(*module_dep.pcm); } WriteCompilerBuildLine({source}, deps, order_only_deps, tool,
diff --git a/src/gn/ninja_c_binary_target_writer.h b/src/gn/ninja_c_binary_target_writer.h index 59cf52d..3353a1b 100644 --- a/src/gn/ninja_c_binary_target_writer.h +++ b/src/gn/ninja_c_binary_target_writer.h
@@ -26,12 +26,12 @@ using OutputFileSet = std::set<OutputFile>; // Writes all flags for the compiler: includes, defines, cflags, etc. - void WriteCompilerVars(const std::vector<ClangModuleDep>& module_dep_info); + void WriteCompilerVars(const std::set<ClangModuleDep>& module_dep_info); // Write module_deps or module_deps_no_self flags for clang modulemaps. void WriteModuleDepsSubstitution( const Substitution* substitution, - const std::vector<ClangModuleDep>& module_dep_info, + const std::set<ClangModuleDep>& module_dep_info, bool include_self); // Writes module_name substitution for clang modulemaps. @@ -80,7 +80,7 @@ void WriteSources(const std::vector<OutputFile>& pch_deps, const std::vector<OutputFile>& input_deps, const std::vector<OutputFile>& order_only_deps, - const std::vector<ClangModuleDep>& module_dep_info, + const std::set<ClangModuleDep>& module_dep_info, std::vector<OutputFile>* object_files, std::vector<OutputFile>* extra_files, std::vector<SourceFile>* other_files);
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc index d12888d..bf81b23 100644 --- a/src/gn/ninja_c_binary_target_writer_unittest.cc +++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -2271,6 +2271,7 @@ target.sources().push_back(SourceFile("//foo/bar.modulemap")); target.source_types_used().Set(SourceFile::SOURCE_CPP); target.source_types_used().Set(SourceFile::SOURCE_MODULEMAP); + target.set_module_type(Target::EXPLICIT_MODULEMAP); ASSERT_TRUE(target.OnResolved(&err)); std::ostringstream out; @@ -2312,6 +2313,7 @@ target.sources().push_back(SourceFile("//foo/bar.modulemap")); target.source_types_used().Set(SourceFile::SOURCE_CPP); target.source_types_used().Set(SourceFile::SOURCE_MODULEMAP); + target.set_module_type(Target::EXPLICIT_MODULEMAP); ASSERT_TRUE(target.OnResolved(&err)); std::ostringstream out; @@ -2572,6 +2574,7 @@ target.sources().push_back(SourceFile("//blah/a.h")); target.source_types_used().Set(SourceFile::SOURCE_CPP); target.source_types_used().Set(SourceFile::SOURCE_MODULEMAP); + target.set_module_type(Target::EXPLICIT_MODULEMAP); target.SetToolchain(&module_toolchain); ASSERT_TRUE(target.OnResolved(&err)); @@ -2617,6 +2620,7 @@ target2.sources().push_back(SourceFile("//stuff/b.h")); target2.source_types_used().Set(SourceFile::SOURCE_CPP); target2.source_types_used().Set(SourceFile::SOURCE_MODULEMAP); + target2.set_module_type(Target::EXPLICIT_MODULEMAP); target2.public_deps().push_back(LabelTargetPair(&target)); target2.SetToolchain(&module_toolchain); ASSERT_TRUE(target2.OnResolved(&err)); @@ -2632,7 +2636,7 @@ cflags = cflags_cc = cc_module_name = b -module_deps = -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm -fmodule-map-file=../../stuff/b.modulemap -fmodule-file=b=obj/stuff/libb.b.pcm +module_deps = -fmodule-map-file=../../stuff/b.modulemap -fmodule-file=b=obj/stuff/libb.b.pcm -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm module_deps_no_self = -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm root_out_dir = withmodules target_out_dir = obj/stuff @@ -2641,7 +2645,7 @@ build obj/stuff/libb.b.pcm: cxx_module ../../stuff/b.modulemap | obj/blah/liba.a.pcm source_file_part = b.modulemap source_name_part = b -build obj/stuff/libb.b.o: cxx ../../stuff/b.cc | obj/blah/liba.a.pcm obj/stuff/libb.b.pcm +build obj/stuff/libb.b.o: cxx ../../stuff/b.cc | obj/stuff/libb.b.pcm obj/blah/liba.a.pcm source_file_part = b.cc source_name_part = b @@ -2660,6 +2664,7 @@ target3.visibility().SetPublic(); target3.sources().push_back(SourceFile("//stuff/c.modulemap")); target3.source_types_used().Set(SourceFile::SOURCE_MODULEMAP); + target3.set_module_type(Target::EXPLICIT_MODULEMAP); target3.public_deps().push_back(LabelTargetPair(&target2)); target3.SetToolchain(&module_toolchain); ASSERT_TRUE(target3.OnResolved(&err)); @@ -2676,13 +2681,13 @@ cflags = cflags_cc = cc_module_name = c -module_deps = -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm -fmodule-map-file=../../stuff/b.modulemap -fmodule-file=b=obj/stuff/libb.b.pcm -fmodule-map-file=../../stuff/c.modulemap -fmodule-file=c=obj/stuff/libc.c.pcm -module_deps_no_self = -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm -fmodule-map-file=../../stuff/b.modulemap -fmodule-file=b=obj/stuff/libb.b.pcm +module_deps = -fmodule-map-file=../../stuff/b.modulemap -fmodule-file=b=obj/stuff/libb.b.pcm -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm -fmodule-map-file=../../stuff/c.modulemap -fmodule-file=c=obj/stuff/libc.c.pcm +module_deps_no_self = -fmodule-map-file=../../stuff/b.modulemap -fmodule-file=b=obj/stuff/libb.b.pcm -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm root_out_dir = withmodules target_out_dir = obj/things target_output_name = libc -build obj/stuff/libc.c.pcm: cxx_module ../../stuff/c.modulemap | obj/blah/liba.a.pcm obj/stuff/libb.b.pcm +build obj/stuff/libc.c.pcm: cxx_module ../../stuff/c.modulemap | obj/stuff/libb.b.pcm obj/blah/liba.a.pcm source_file_part = c.modulemap source_name_part = c @@ -2716,16 +2721,16 @@ cflags = cflags_cc = cc_module_name = c -module_deps = -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm -fmodule-map-file=../../stuff/b.modulemap -fmodule-file=b=obj/stuff/libb.b.pcm -module_deps_no_self = -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm -fmodule-map-file=../../stuff/b.modulemap -fmodule-file=b=obj/stuff/libb.b.pcm +module_deps = -fmodule-map-file=../../stuff/b.modulemap -fmodule-file=b=obj/stuff/libb.b.pcm -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm +module_deps_no_self = -fmodule-map-file=../../stuff/b.modulemap -fmodule-file=b=obj/stuff/libb.b.pcm -fmodule-map-file=../../blah/a.modulemap -fmodule-file=blah_a=obj/blah/liba.a.pcm root_out_dir = withmodules target_out_dir = obj/zap target_output_name = c -build obj/zap/c.x.o: cxx ../../zap/x.cc | obj/blah/liba.a.pcm obj/stuff/libb.b.pcm +build obj/zap/c.x.o: cxx ../../zap/x.cc | obj/stuff/libb.b.pcm obj/blah/liba.a.pcm source_file_part = x.cc source_name_part = x -build obj/zap/c.y.o: cxx ../../zap/y.cc | obj/blah/liba.a.pcm obj/stuff/libb.b.pcm +build obj/zap/c.y.o: cxx ../../zap/y.cc | obj/stuff/libb.b.pcm obj/blah/liba.a.pcm source_file_part = y.cc source_name_part = y @@ -2911,3 +2916,88 @@ std::string out_str = out.str(); EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; } + +TEST_F(NinjaCBinaryTargetWriterTest, ModuleMapGeneration) { + Err err; + TestWithScope setup; + + // Let's create a target and give it public headers. + Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); + target.set_output_type(Target::SOURCE_SET); + target.visibility().SetPublic(); + target.sources().push_back(SourceFile("//foo/source1.cc")); + target.public_headers().push_back(SourceFile("//foo/public_header.h")); + target.source_types_used().Set(SourceFile::SOURCE_CPP); + target.set_module_type(Target::GENERATED_TEXTUAL_MODULEMAP); + target.SetToolchain(setup.toolchain()); + ASSERT_TRUE(target.OnResolved(&err)); + + std::ostringstream ninja_out; + NinjaCBinaryTargetWriter writer(&target, ninja_out); + + std::ostringstream modulemap_out; + SourceDir out_dir("//out/Debug/gen/"); + + writer.WriteModuleMap(modulemap_out, out_dir); + + const char expected_modulemap[] = + "module \"bar\" {\n" + " textual header \"../../../foo/public_header.h\"\n" + " export *\n" + "}\n"; + + std::string modulemap_str = modulemap_out.str(); + EXPECT_EQ(expected_modulemap, modulemap_str) << expected_modulemap << "\n" + << modulemap_str; + + const char expected_ninja[] = + "defines =\n" + "include_dirs =\n" + "cflags =\n" + "cflags_cc =\n" + "root_out_dir = .\n" + "target_gen_dir = gen/foo\n" + "target_out_dir = obj/foo\n" + "target_output_name = bar\n" + "\n" + "build obj/foo/bar.source1.o: cxx ../../foo/source1.cc\n" + " source_file_part = source1.cc\n" + " source_name_part = source1\n" + "\n" + "build phony/foo/bar: phony obj/foo/bar.source1.o\n"; + writer.Run(); + std::string ninja_str = ninja_out.str(); + EXPECT_EQ(expected_ninja, ninja_str) << expected_ninja << "\n" << ninja_str; + + // Test generation without explicit public headers (uses sources instead) + Target target_no_public(setup.settings(), + Label(SourceDir("//foo/"), "no_public")); + target_no_public.set_output_type(Target::SOURCE_SET); + target_no_public.visibility().SetPublic(); + target_no_public.sources().push_back(SourceFile("//foo/source1.cc")); + target_no_public.sources().push_back(SourceFile("//foo/header1.h")); + target_no_public.source_types_used().Set(SourceFile::SOURCE_CPP); + target_no_public.source_types_used().Set(SourceFile::SOURCE_H); + target_no_public.set_module_type(Target::GENERATED_TEXTUAL_MODULEMAP); + target_no_public.SetToolchain(setup.toolchain()); + ASSERT_TRUE(target_no_public.OnResolved(&err)); + + std::ostringstream ninja_out_no_public; + NinjaCBinaryTargetWriter writer_no_public(&target_no_public, + ninja_out_no_public); + + std::ostringstream modulemap_out_no_public; + + writer_no_public.WriteModuleMap(modulemap_out_no_public, out_dir); + + const char expected_modulemap_no_public[] = + "module \"no_public\" {\n" + " textual header \"../../../foo/header1.h\"\n" + " export *\n" + "}\n"; + + std::string modulemap_str_no_public = modulemap_out_no_public.str(); + EXPECT_EQ(expected_modulemap_no_public, modulemap_str_no_public) + << expected_modulemap_no_public << "\n" + << modulemap_str_no_public; +}
diff --git a/src/gn/ninja_module_writer_util.cc b/src/gn/ninja_module_writer_util.cc index 67b40eb..7c8b0ca 100644 --- a/src/gn/ninja_module_writer_util.cc +++ b/src/gn/ninja_module_writer_util.cc
@@ -6,56 +6,67 @@ #include <algorithm> #include <set> +#include <utility> #include "gn/resolved_target_data.h" #include "gn/substitution_writer.h" #include "gn/target.h" -namespace { - -// Returns the first source file in the target's sources that is a modulemap -// file. Returns nullptr if no modulemap file is found. -const SourceFile* GetModuleMapFromTargetSources(const Target* target) { - for (const SourceFile& sf : target->sources()) { - if (sf.IsModuleMapType()) - return &sf; - } - return nullptr; -} - -} // namespace - ClangModuleDep::ClangModuleDep(const SourceFile* modulemap, const std::string& module_name, - const OutputFile& pcm, + std::optional<OutputFile> pcm, bool is_self) : modulemap(modulemap), module_name(module_name), - pcm(pcm), + pcm(std::move(pcm)), is_self(is_self) {} -std::vector<ClangModuleDep> GetModuleDepsInformation( +std::strong_ordering ClangModuleDep::operator<=>( + const ClangModuleDep& other) const { + // Sort by (module name, modulemap path, module file path) + if (auto cmp = module_name <=> other.module_name; cmp != 0) + return cmp; + if (modulemap && other.modulemap) { + if (auto cmp = *modulemap <=> *other.modulemap; cmp != 0) + return cmp; + } else { + if (auto cmp = modulemap <=> other.modulemap; cmp != 0) + return cmp; + } + // std::optional doesn't support <=> on older versions of mac. + if (pcm.has_value() && other.pcm.has_value()) { + return *pcm <=> *other.pcm; + } else { + return pcm.has_value() <=> other.pcm.has_value(); + } +} + +std::set<ClangModuleDep> GetModuleDepsInformation( const Target* target, const ResolvedTargetData& resolved) { - std::vector<ClangModuleDep> ret; - // Use a set to keep track of added PCM files to ensure uniqueness. - std::set<OutputFile> added_pcms; + std::set<ClangModuleDep> ret; - auto add_if_new = [&added_pcms, &ret](const Target* t, bool is_self) { - const SourceFile* modulemap = GetModuleMapFromTargetSources(t); - if (!modulemap) // Not a module or no .modulemap file. - return; - - const char* tool_type; - std::vector<OutputFile> modulemap_outputs; - CHECK( - t->GetOutputFilesForSource(*modulemap, &tool_type, &modulemap_outputs)); - CHECK(modulemap_outputs.size() == 1u); // Must be only one .pcm. - const OutputFile& pcm_file = modulemap_outputs[0]; - - if (added_pcms.insert(pcm_file).second) { - // GN sets the module name to the name of the target. - ret.emplace_back(modulemap, t->module_name(), pcm_file, is_self); + auto add_if_new = [&ret](const Target* t, bool is_self) { + std::optional<OutputFile> pcm = std::nullopt; + switch (t->module_type()) { + case Target::GENERATED_TEXTUAL_MODULEMAP: + ret.emplace(t->modulemap_file(), t->module_name(), std::nullopt, + is_self); + break; + case Target::EXPLICIT_MODULEMAP: { + auto modulemap = t->modulemap_file(); + CHECK(modulemap != nullptr); + const char* tool_type; + std::vector<OutputFile> modulemap_outputs; + CHECK(t->GetOutputFilesForSource(*modulemap, &tool_type, + &modulemap_outputs)); + CHECK(modulemap_outputs.size() == 1u); + ret.emplace(modulemap, t->module_name(), + std::move(modulemap_outputs[0]), is_self); + break; + } + default: + break; } }; @@ -65,11 +76,17 @@ for (const auto& pair : resolved.GetModuleDepsInformation(target)) add_if_new(pair.target(), false); - // Sort by pcm path for deterministic output. - std::sort(ret.begin(), ret.end(), - [](const ClangModuleDep& a, const ClangModuleDep& b) { - return a.pcm < b.pcm; - }); - return ret; } + +void ClangModuleDep::Write(std::ostream& out, + const PathOutput& path_output) const { + if (modulemap) { + out << " -fmodule-map-file="; + path_output.WriteFile(out, *modulemap); + } + if (pcm) { + out << " -fmodule-file=" << module_name << "="; + path_output.WriteFile(out, *pcm); + } +} \ No newline at end of file
diff --git a/src/gn/ninja_module_writer_util.h b/src/gn/ninja_module_writer_util.h index f6d007b..911a98f 100644 --- a/src/gn/ninja_module_writer_util.h +++ b/src/gn/ninja_module_writer_util.h
@@ -5,10 +5,13 @@ #ifndef TOOLS_GN_NINJA_MODULE_WRITER_UTIL_H_ #define TOOLS_GN_NINJA_MODULE_WRITER_UTIL_H_ +#include <compare> +#include <optional> +#include <set> #include <string> -#include <vector> #include "gn/output_file.h" +#include "gn/path_output.h" class ResolvedTargetData; class SourceFile; @@ -17,9 +20,13 @@ struct ClangModuleDep { ClangModuleDep(const SourceFile* modulemap, const std::string& module_name, - const OutputFile& pcm, + std::optional<OutputFile> pcm, bool is_self); + std::strong_ordering operator<=>(const ClangModuleDep& other) const; + bool operator==(const ClangModuleDep& other) const = default; + void Write(std::ostream& out, const PathOutput& path_output) const; + // The input module.modulemap source file. const SourceFile* modulemap; @@ -27,14 +34,15 @@ std::string module_name; // The compiled version of the module. - OutputFile pcm; + // Will be nullopt if the modulemap is purely textual. + std::optional<OutputFile> pcm; // Is this the module for the current target. bool is_self; }; // Gathers information about all module dependencies for a given target. -std::vector<ClangModuleDep> GetModuleDepsInformation( +std::set<ClangModuleDep> GetModuleDepsInformation( const Target* target, const ResolvedTargetData& resolved);
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc index d6c69c7..46964ee 100644 --- a/src/gn/ninja_target_writer.cc +++ b/src/gn/ninja_target_writer.cc
@@ -166,6 +166,17 @@ NinjaBinaryTargetWriter writer(target, rules); writer.SetResolvedTargetData(resolved); writer.SetNinjaOutputs(ninja_outputs); + if (target->module_type() == Target::GENERATED_TEXTUAL_MODULEMAP) { + const SourceFile* modulemap = target->modulemap_file(); + CHECK(modulemap); + StringOutputBuffer modulemap_storage; + std::ostream os(&modulemap_storage); + writer.WriteModuleMap(os, modulemap->GetDir()); + + base::FilePath file_path = + settings->build_settings()->GetFullPath(*modulemap); + modulemap_storage.WriteToFileIfChanged(file_path, nullptr); + } writer.Run(); } else { CHECK(0) << "Output type of target not handled.";
diff --git a/src/gn/output_file.h b/src/gn/output_file.h index d8d69ff..e3e79a8 100644 --- a/src/gn/output_file.h +++ b/src/gn/output_file.h
@@ -34,15 +34,10 @@ SourceFile AsSourceFile(const BuildSettings* build_settings) const; SourceDir AsSourceDir(const BuildSettings* build_settings) const; - bool operator==(const OutputFile& other) const { - return value_ == other.value_; - } - bool operator!=(const OutputFile& other) const { - return value_ != other.value_; - } - bool operator<(const OutputFile& other) const { - return value_ < other.value_; - } + bool operator==(const OutputFile& other) const = default; + bool operator!=(const OutputFile& other) const = default; + bool operator<(const OutputFile& other) const = default; + std::strong_ordering operator<=>(const OutputFile& other) const = default; private: std::string value_;
diff --git a/src/gn/source_file.h b/src/gn/source_file.h index 467c119..f682f0d 100644 --- a/src/gn/source_file.h +++ b/src/gn/source_file.h
@@ -104,6 +104,13 @@ bool operator<(const SourceFile& other) const { return value_ < other.value_; } + // Needs to be overridden because == has custom logic. + std::strong_ordering operator<=>(const SourceFile& other) const { + if (*this == other) + return std::strong_ordering::equal; + return *this < other ? std::strong_ordering::less + : std::strong_ordering::greater; + } struct PtrCompare { bool operator()(const SourceFile& a, const SourceFile& b) const noexcept {
diff --git a/src/gn/target.cc b/src/gn/target.cc index b66c636..f2130e6 100644 --- a/src/gn/target.cc +++ b/src/gn/target.cc
@@ -1392,3 +1392,36 @@ std::make_move_iterator(current_result.end())); return true; } + +void Target::set_module_type(ModuleType type) { + module_type_ = type; + switch (type) { + case GENERATED_TEXTUAL_MODULEMAP: { + auto source_dir = + GetBuildDirForTargetAsOutputFile(this, BuildDirType::GEN) + .AsSourceDir(settings()->build_settings()); + + generated_modulemap_file_ = SourceFile( + base::StringPrintf("%s%s.modulemap", source_dir.value().c_str(), + label().name().c_str())); + break; + } + default: + break; + } +} + +const SourceFile* Target::modulemap_file() const { + switch (module_type_) { + case GENERATED_TEXTUAL_MODULEMAP: + return &generated_modulemap_file_; + case EXPLICIT_MODULEMAP: + for (const SourceFile& sf : sources_) { + if (sf.IsModuleMapType()) { + return &sf; + } + } + default: + return nullptr; + } +}
diff --git a/src/gn/target.h b/src/gn/target.h index eb9e0b9..f219402 100644 --- a/src/gn/target.h +++ b/src/gn/target.h
@@ -59,6 +59,14 @@ DEPS_LINKED, // Iterates through all non-data dependencies. }; + enum ModuleType { + NO_MODULEMAP, + EXPLICIT_MODULEMAP, + // The target didn't have any public headers, so no modulemap is needed. + UNNECESSARY_MODULEMAP, + GENERATED_TEXTUAL_MODULEMAP, + }; + using FileList = std::vector<SourceFile>; using StringVector = std::vector<std::string>; @@ -348,6 +356,10 @@ return assert_no_deps_; } + ModuleType module_type() const { return module_type_; } + void set_module_type(ModuleType type); + const SourceFile* modulemap_file() const; + // The toolchain is only known once this target is resolved (all if its // dependencies are known). They will be null until then. Generally, this can // only be used during target writing. @@ -526,6 +538,9 @@ bool output_extension_set_ = false; std::string module_name_override_; + ModuleType module_type_ = NO_MODULEMAP; + // Only filled if the module type is GENERATED_* + SourceFile generated_modulemap_file_; FileList sources_; SourceFileTypeSet source_types_used_;
diff --git a/src/gn/variables.cc b/src/gn/variables.cc index 5090e3a..ad9657f 100644 --- a/src/gn/variables.cc +++ b/src/gn/variables.cc
@@ -405,6 +405,18 @@ } )"; +const char kGenerateModulemap[] = "generate_modulemap"; +const char kGenerateModulemap_HelpShort[] = + "generate_modulemap: [string] Mode for generating modulemaps."; +const char kGenerateModulemap_Help[] = + R"(generate_modulemap: [string] Mode for generating modulemaps. + +Possible values: + "none" (default): Don't generate a modulemap file for the target. + "textual": Generate a modulemap file for the target. + All public headers will be marked as textual. +)"; + // Target variables ------------------------------------------------------------ #define COMMON_ORDERING_HELP \ @@ -2429,6 +2441,7 @@ INSERT_VARIABLE(CurrentOs) INSERT_VARIABLE(CurrentToolchain) INSERT_VARIABLE(DefaultToolchain) + INSERT_VARIABLE(GenerateModulemap) INSERT_VARIABLE(GnVersion) INSERT_VARIABLE(HostCpu) INSERT_VARIABLE(HostOs)
diff --git a/src/gn/variables.h b/src/gn/variables.h index 1d953af..6820121 100644 --- a/src/gn/variables.h +++ b/src/gn/variables.h
@@ -80,6 +80,10 @@ extern const char kTargetOutDir_HelpShort[]; extern const char kTargetOutDir_Help[]; +extern const char kGenerateModulemap[]; +extern const char kGenerateModulemap_HelpShort[]; +extern const char kGenerateModulemap_Help[]; + // Target vars ----------------------------------------------------------------- extern const char kAllDependentConfigs[];