Write to ninja files via a standard API This paves the way to have multiple targets in a single ninja file, which in turn should allow us to shard targets without degrading build performance. Benchmarking reveals a statistically insignificant impact on performance, and `siso dump` (crrev.com/c/8275910) reveals that the build graph is identical to before. Change-Id: I188fd8b075bb2f5cec9ff253c9220ae26a6a6964 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/25740 Reviewed-by: Takuto Ikuta <tikuta@google.com> Commit-Queue: Matt Stark <msta@google.com>
diff --git a/build/gen.py b/build/gen.py index e4e377e..428c713 100755 --- a/build/gen.py +++ b/build/gen.py
@@ -861,6 +861,7 @@ 'src/gn/ninja_c_binary_target_writer.cc', 'src/gn/ninja_copy_target_writer.cc', 'src/gn/ninja_create_bundle_target_writer.cc', + 'src/gn/ninja_file.cc', 'src/gn/ninja_generated_file_target_writer.cc', 'src/gn/ninja_group_target_writer.cc', 'src/gn/ninja_module_writer_util.cc',
diff --git a/integration_tests/simple/goldens/obj/hello.ninja b/integration_tests/simple/goldens/obj/hello.ninja index b5f77f8..9ad97cf 100644 --- a/integration_tests/simple/goldens/obj/hello.ninja +++ b/integration_tests/simple/goldens/obj/hello.ninja
@@ -4,6 +4,7 @@ target_out_dir = obj target_output_name = hello + build obj/hello.hello.o: cxx ../hello.cc source_file_part = hello.cc source_name_part = hello
diff --git a/integration_tests/simple/goldens/obj/hello_shared.ninja b/integration_tests/simple/goldens/obj/hello_shared.ninja index 998ee5e..21ddb24 100644 --- a/integration_tests/simple/goldens/obj/hello_shared.ninja +++ b/integration_tests/simple/goldens/obj/hello_shared.ninja
@@ -4,6 +4,7 @@ target_out_dir = obj target_output_name = libhello_shared + build obj/libhello_shared.hello_shared.o: cxx ../hello_shared.cc source_file_part = hello_shared.cc source_name_part = hello_shared
diff --git a/integration_tests/simple/goldens/obj/hello_static.ninja b/integration_tests/simple/goldens/obj/hello_static.ninja index c554743..c2821dd 100644 --- a/integration_tests/simple/goldens/obj/hello_static.ninja +++ b/integration_tests/simple/goldens/obj/hello_static.ninja
@@ -4,6 +4,7 @@ target_out_dir = obj target_output_name = libhello_static + build obj/libhello_static.hello_static.o: cxx ../hello_static.cc source_file_part = hello_static.cc source_name_part = hello_static
diff --git a/src/gn/ninja_action_target_writer.cc b/src/gn/ninja_action_target_writer.cc index 0d01d07..b5490d3 100644 --- a/src/gn/ninja_action_target_writer.cc +++ b/src/gn/ninja_action_target_writer.cc
@@ -26,7 +26,7 @@ NinjaActionTargetWriter::~NinjaActionTargetWriter() = default; -void NinjaActionTargetWriter::Run() { +void NinjaActionTargetWriter::GenerateRules() { std::string custom_rule_name = WriteRuleDefinition(); // Collect our deps to pass as additional "hard dependencies" for input deps. @@ -78,7 +78,6 @@ std::vector<OutputFile> input_deps = stamp_deps.implicit; input_deps.insert(input_deps.end(), stamp_deps.order_only.begin(), stamp_deps.order_only.end()); - out_ << std::endl; // Collects all output files for writing below. std::vector<OutputFile> output_files; @@ -92,47 +91,33 @@ // Write a rule that invokes the script once with the outputs as outputs, // and the data as inputs. It does not depend on the sources. - out_ << "build"; SubstitutionWriter::GetListAsOutputFiles( settings_, target_->action_values().outputs(), &output_files); - WriteOutputs(output_files); - out_ << ": " << custom_rule_name; - if (!input_deps.empty()) { - // As in WriteSourceRules, we want to force this target to rebuild any - // time any of its dependencies change. - out_ << " |"; - path_output_.WriteFiles(out_, input_deps); - } - if (!order_only_deps.empty()) { - // Write any order-only deps out for actions just like they are for - // binaries. - out_ << " ||"; - path_output_.WriteFiles(out_, order_only_deps); - } + NinjaBuildEdge edge{ + .rule = custom_rule_name, + .outputs = output_files, + .implicit_inputs = input_deps, + .order_only_inputs = order_only_deps, + }; + AddValidationInputs(edge); - WriteValidations(); - out_ << std::endl; + WriteNinjaVariablesForAction(edge.edge_vars); - if (target_->action_values().has_depfile()) { - WriteDepfile(SourceFile()); - } - - WriteNinjaVariablesForAction(); + if (target_->action_values().has_depfile()) + WriteDepfile(SourceFile(), edge.edge_vars); if (target_->pool().ptr) { - out_ << " pool = "; - out_ << target_->pool().ptr->GetNinjaName( - settings_->default_toolchain_label()); - out_ << std::endl; + edge.edge_vars.emplace_back("pool", + target_->pool().ptr->GetNinjaName( + settings_->default_toolchain_label())); } + + AddEdge(std::move(edge)); } - out_ << std::endl; // Write the phony, which doesn't need to depend on the data deps because they // have been added as order-only deps of the action output itself. - // - // TODO(thakis): If the action has just a single output, make things depend std::vector<OutputFile> stamp_file_order_only_deps; WriteStampOrPhonyForTarget(output_files, stamp_file_order_only_deps); } @@ -152,7 +137,8 @@ EscapeOptions args_escape_options; args_escape_options.mode = ESCAPE_NINJA_COMMAND; - out_ << "rule " << custom_rule_name << std::endl; + std::ostringstream rule_out; + rule_out << "rule " << custom_rule_name << std::endl; if (target_->action_values().uses_rsp_file()) { // Needs a response file. The unique_name part is for action_foreach so @@ -163,17 +149,17 @@ if (!target_->sources().empty()) rspfile += ".$unique_name"; rspfile += ".rsp"; - out_ << " rspfile = " << rspfile << std::endl; + rule_out << " rspfile = " << rspfile << std::endl; // Response file contents. - out_ << " rspfile_content ="; + rule_out << " rspfile_content ="; for (const auto& arg : target_->action_values().rsp_file_contents().list()) { - out_ << " "; + rule_out << " "; SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, - out_); + rule_out); } - out_ << std::endl; + rule_out << std::endl; } // The command line requires shell escaping to properly handle filenames @@ -182,29 +168,33 @@ settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND); - out_ << " command = "; - command_output.WriteFile(out_, settings_->build_settings()->python_path()); - out_ << " "; - command_output.WriteFile(out_, target_->action_values().script()); + rule_out << " command = "; + command_output.WriteFile(rule_out, + settings_->build_settings()->python_path()); + rule_out << " "; + command_output.WriteFile(rule_out, target_->action_values().script()); for (const auto& arg : args.list()) { - out_ << " "; - SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, out_); + rule_out << " "; + SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, + rule_out); } - out_ << std::endl; + rule_out << std::endl; auto mnemonic = target_->action_values().mnemonic(); if (mnemonic.empty()) mnemonic = "ACTION"; - out_ << " description = " << mnemonic << " " << target_label << std::endl; - out_ << " restat = 1" << std::endl; + rule_out << " description = " << mnemonic << " " << target_label + << std::endl; + rule_out << " restat = 1" << std::endl; const Tool* tool = target_->toolchain()->GetTool(GeneralTool::kGeneralToolAction); if (tool && tool->pool().ptr) { - out_ << " pool = "; - out_ << tool->pool().ptr->GetNinjaName( + rule_out << " pool = "; + rule_out << tool->pool().ptr->GetNinjaName( settings_->default_toolchain_label()); - out_ << std::endl; + rule_out << std::endl; } + target_group_.custom_rules.push_back(rule_out.str()); return custom_rule_name; } @@ -221,31 +211,32 @@ const Target::FileList& sources = target_->sources(); for (size_t i = 0; i < sources.size(); i++) { - out_ << "build"; - WriteOutputFilesForBuildLine(sources[i], output_files); + std::vector<OutputFile> cur_outputs; + SubstitutionWriter::ApplyListToSourceAsOutputFile( + target_, settings_, target_->action_values().outputs(), sources[i], + &cur_outputs); + output_files->insert(output_files->end(), cur_outputs.begin(), + cur_outputs.end()); - out_ << ": " << custom_rule_name << " "; - path_output_.WriteFile(out_, sources[i]); - if (!input_deps.empty()) { - // Using "|" for the dependencies forces all implicit dependencies to be - // fully up to date before running the action, and will re-run this - // action if any input dependencies change. This is important because - // this action may consume the outputs of previous steps. - out_ << " |"; - path_output_.WriteFiles(out_, input_deps); - } - if (!order_only_deps.empty()) { - // Write any order-only deps out for actions just like they are written - // out for binaries. - out_ << " ||"; - path_output_.WriteFiles(out_, order_only_deps); - } - WriteValidations(); - out_ << std::endl; + // Using "|" for the dependencies forces all implicit dependencies to be + // fully up to date before running the action, and will re-run this + // action if any input dependencies change. This is important because + // this action may consume the outputs of previous steps. + // Write any order-only deps out for actions just like they are written + // out for binaries. + NinjaBuildEdge edge{ + .rule = custom_rule_name, + .outputs = std::move(cur_outputs), + .explicit_inputs = {OutputFile(settings_->build_settings(), + sources[i])}, + .implicit_inputs = input_deps, + .order_only_inputs = order_only_deps, + }; + AddValidationInputs(edge); // Response files require a unique name be defined. if (target_->action_values().uses_rsp_file()) - out_ << " unique_name = " << i << std::endl; + edge.edge_vars.emplace_back("unique_name", std::to_string(i)); // The required types is the union of the args and response file. This // might theoretically duplicate a definition if the same substitution is @@ -255,60 +246,48 @@ SubstitutionWriter::WriteNinjaVariablesForSource( target_, settings_, sources[i], target_->action_values().args().required_types(), args_escape_options, - out_); + edge.edge_vars); SubstitutionWriter::WriteNinjaVariablesForSource( target_, settings_, sources[i], target_->action_values().rsp_file_contents().required_types(), - args_escape_options, out_); - WriteNinjaVariablesForAction(); + args_escape_options, edge.edge_vars); + WriteNinjaVariablesForAction(edge.edge_vars); - if (target_->action_values().has_depfile()) { - WriteDepfile(sources[i]); - } + if (target_->action_values().has_depfile()) + WriteDepfile(sources[i], edge.edge_vars); + if (target_->pool().ptr) { - out_ << " pool = "; - out_ << target_->pool().ptr->GetNinjaName( - settings_->default_toolchain_label()); - out_ << std::endl; + edge.edge_vars.emplace_back("pool", + target_->pool().ptr->GetNinjaName( + settings_->default_toolchain_label())); } + + AddEdge(std::move(edge)); } } -void NinjaActionTargetWriter::WriteOutputFilesForBuildLine( +void NinjaActionTargetWriter::WriteDepfile( const SourceFile& source, - std::vector<OutputFile>* output_files) { - size_t first_output_index = output_files->size(); - - SubstitutionWriter::ApplyListToSourceAsOutputFile( - target_, settings_, target_->action_values().outputs(), source, - output_files); - - for (size_t i = first_output_index; i < output_files->size(); i++) { - out_ << " "; - WriteOutput((*output_files)[i]); - } -} - -void NinjaActionTargetWriter::WriteDepfile(const SourceFile& source) { - out_ << " depfile = "; + std::vector<NinjaVariable>& edge_vars) { + std::ostringstream ss; path_output_.WriteFile( - out_, - SubstitutionWriter::ApplyPatternToSourceAsOutputFile( - target_, settings_, target_->action_values().depfile(), source)); - out_ << std::endl; + ss, SubstitutionWriter::ApplyPatternToSourceAsOutputFile( + target_, settings_, target_->action_values().depfile(), source)); + edge_vars.emplace_back("depfile", ss.str()); // Using "deps = gcc" allows Ninja to read and store the depfile content in // its internal database which improves performance, especially for large // depfiles. The use of this feature with depfiles that contain multiple // outputs require Ninja version 1.9.0 or newer. if (settings_->build_settings()->ninja_required_version() >= Version{1, 9, 0}) { - out_ << " deps = gcc" << std::endl; + edge_vars.emplace_back("deps", "gcc"); } } -void NinjaActionTargetWriter::WriteNinjaVariablesForAction() { +void NinjaActionTargetWriter::WriteNinjaVariablesForAction( + std::vector<NinjaVariable>& edge_vars) { SubstitutionBits subst; target_->action_values().args().FillRequiredTypes(&subst); - WriteRustCompilerVars(subst, /*indent=*/true, /*always_write=*/false); - WriteCCompilerVars(subst, /*indent=*/true, /*respect_source_types=*/false); + WriteRustCompilerVars(subst, /*always_write=*/false, edge_vars); + WriteCCompilerVars(subst, /*respect_source_used=*/false, edge_vars); }
diff --git a/src/gn/ninja_action_target_writer.h b/src/gn/ninja_action_target_writer.h index eff087b..930a6df 100644 --- a/src/gn/ninja_action_target_writer.h +++ b/src/gn/ninja_action_target_writer.h
@@ -18,13 +18,9 @@ NinjaActionTargetWriter(const Target* target, std::ostream& out); ~NinjaActionTargetWriter() override; - void Run() override; + void GenerateRules() override; private: - FRIEND_TEST_ALL_PREFIXES(NinjaActionTargetWriter, - WriteOutputFilesForBuildLine); - FRIEND_TEST_ALL_PREFIXES(NinjaActionTargetWriter, - WriteOutputFilesForBuildLineWithDepfile); FRIEND_TEST_ALL_PREFIXES(NinjaActionTargetWriter, WriteArgsSubstitutions); // Writes the Ninja rule for invoking the script. @@ -43,17 +39,12 @@ const std::vector<OutputFile>& order_only_deps, std::vector<OutputFile>* output_files); - // Writes the output files generated by the output template for the given - // source file. This will start with a space and will not include a newline. - // Appends the output files to the given vector. - void WriteOutputFilesForBuildLine(const SourceFile& source, - std::vector<OutputFile>* output_files); - - void WriteDepfile(const SourceFile& source); + void WriteDepfile(const SourceFile& source, + std::vector<NinjaVariable>& edge_vars); // Writes variables that we make available to all actions, irrespective // of whether they're associated with a specific source file. - void WriteNinjaVariablesForAction(); + void WriteNinjaVariablesForAction(std::vector<NinjaVariable>& edge_vars); // Path output writer that doesn't do any escaping or quoting. It does, // however, convert slashes. Used for
diff --git a/src/gn/ninja_action_target_writer_unittest.cc b/src/gn/ninja_action_target_writer_unittest.cc index e855582..cd3b0e7 100644 --- a/src/gn/ninja_action_target_writer_unittest.cc +++ b/src/gn/ninja_action_target_writer_unittest.cc
@@ -14,29 +14,6 @@ #include "util/build_config.h" #include "util/test/test.h" -TEST(NinjaActionTargetWriter, WriteOutputFilesForBuildLine) { - Err err; - TestWithScope setup; - - Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); - target.set_output_type(Target::ACTION_FOREACH); - target.action_values().outputs() = - SubstitutionList::MakeForTest("//out/Debug/gen/a b{{source_name_part}}.h", - "//out/Debug/gen/{{source_name_part}}.cc"); - - target.SetToolchain(setup.toolchain()); - ASSERT_TRUE(target.OnResolved(&err)); - - std::ostringstream out; - NinjaActionTargetWriter writer(&target, out); - - SourceFile source("//foo/bar.in"); - std::vector<OutputFile> output_files; - writer.WriteOutputFilesForBuildLine(source, &output_files); - - EXPECT_EQ(" gen/a$ bbar.h gen/bar.cc", out.str()); -} - // Tests an action with no sources. TEST(NinjaActionTargetWriter, ActionNoSources) { Err err; @@ -66,6 +43,7 @@ description = ACTION //foo++:bar() restat = 1 + build foo.out: __foo___bar___rule | ../../foo++/script.py ../../foo++/included.txt build phony/foo++/bar: phony foo.out @@ -110,6 +88,7 @@ description = ACTION //foo:bar() restat = 1 + build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt pool = console @@ -151,6 +130,7 @@ " description = ACTION //foo:bar()\n" " restat = 1\n" "\n" + "\n" "build foo.out: __foo_bar___rule | ../../foo/script.py " "../../foo/included.txt ../../foo/source.txt\n" "\n" @@ -208,6 +188,7 @@ " description = ACTION //foo:bar()\n" " restat = 1\n" "\n" + "\n" "build foo.out: __foo_bar___rule | ../../foo/script.py " "../../foo/included.txt ../../foo/source.txt phony/foo/dep || " "phony/foo/datadep\n" @@ -284,6 +265,8 @@ #endif " description = ACTION //foo:bar()\n" " restat = 1\n" + "\n" + "\n" "build phony/foo/bar.inputdeps: phony ../../foo/script.py " "../../foo/included.txt phony/foo/dep\n" "\n" @@ -291,6 +274,7 @@ "phony/foo/bar.inputdeps || phony/foo/bundle_data_dep " "phony/foo/datadep\n" " source_name_part = input1\n" + "\n" "build input2.out: __foo_bar___rule ../../foo/input2.txt | " "phony/foo/bar.inputdeps || phony/foo/bundle_data_dep " "phony/foo/datadep\n" @@ -351,6 +335,8 @@ #endif " description = ACTION //foo:bar()\n" " restat = 1\n" + "\n" + "\n" "build phony/foo/bar.inputdeps: phony ../../foo/script.py " "../../foo/included.txt\n" "\n" @@ -359,6 +345,7 @@ " source_name_part = input1\n" " depfile = gen/input1.d\n" " deps = gcc\n" + "\n" "build input2.out: __foo_bar___rule ../../foo/input2.txt" " | phony/foo/bar.inputdeps\n" " source_name_part = input2\n" @@ -410,6 +397,7 @@ " description = ACTION //foo:bar()\n" " restat = 1\n" "\n" + "\n" "build input1.out: __foo_bar___rule ../../foo/input1.txt" " | ../../foo/script.py\n" // Necessary for the rspfile defined in the rule. @@ -464,6 +452,7 @@ " description = ACTION //foo:bar()\n" " restat = 1\n" "\n" + "\n" "build input1.out: __foo_bar___rule ../../foo/input1.txt" " | ../../foo/script.py\n" // Substitution for the args. @@ -510,6 +499,7 @@ " description = ACTION //foo:foo()\n" " restat = 1\n" "\n" + "\n" "build foo.out: __foo_foo___rule | ../../foo/script.py" " ../../foo/input1.txt phony/foo/dep\n" "\n" @@ -539,6 +529,7 @@ " description = ACTION //bar:bar()\n" " restat = 1\n" "\n" + "\n" // Do not have obj/foo/dep.stamp as dependency. "build bar.out: __bar_bar___rule | ../../bar/script.py" " ../../bar/input1.txt phony/foo/foo\n" @@ -595,6 +586,7 @@ " description = ACTION //foo:foo()\n" " restat = 1\n" "\n" + "\n" "build foo.out: __foo_foo___rule | ../../foo/script.py" " ../../foo/input1.txt\n" " rustenv = my_rustenv\n" @@ -647,6 +639,7 @@ R"( description = ACTION //foo:bar() restat = 1 + build foo.out: __foo_bar___rule | ../../foo/my$ script.py ../../foo/input$ file.txt build phony/foo/bar: phony foo.out @@ -691,6 +684,7 @@ " description = ACTION //foo:bar()\n" " restat = 1\n" "\n" + "\n" "build foo.out: __foo_bar___rule | ../../foo/script.py |@ phony/foo/val\n" "\n" "build phony/foo/bar: phony foo.out |@ phony/foo/val\n";
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc index 8c8d2a0..3e465bc 100644 --- a/src/gn/ninja_binary_target_writer.cc +++ b/src/gn/ninja_binary_target_writer.cc
@@ -99,19 +99,17 @@ NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() = default; -void NinjaBinaryTargetWriter::Run() { +void NinjaBinaryTargetWriter::GenerateRules() { if (target_->source_types_used().RustSourceUsed()) { NinjaRustBinaryTargetWriter writer(target_, out_); writer.SetResolvedTargetData(GetResolvedTargetData()); - writer.SetNinjaOutputs(ninja_outputs_); - writer.Run(); + target_group_ = writer.GenerateTargetGroup(); return; } NinjaCBinaryTargetWriter writer(target_, out_); writer.SetResolvedTargetData(GetResolvedTargetData()); - writer.SetNinjaOutputs(ninja_outputs_); - writer.Run(); + target_group_ = writer.GenerateTargetGroup(); } void NinjaBinaryTargetWriter::WritePublicModuleMap(std::ostream& out, @@ -160,7 +158,7 @@ std::vector<OutputFile> NinjaBinaryTargetWriter::WriteInputsStampOrPhonyAndGetDep( - size_t num_output_uses) const { + size_t num_output_uses) { CHECK(target_->toolchain()) << "Toolchain not set on target " << target_->label().GetUserVisibleName(true); @@ -183,7 +181,7 @@ std::vector<OutputFile> outs; for (const SourceFile* source : inputs) - outs.push_back(OutputFile(settings_->build_settings(), *source)); + outs.emplace_back(settings_->build_settings(), *source); // If there are multiple inputs, but the phony target would be referenced only // once, don't write it but depend on the inputs directly. @@ -207,17 +205,13 @@ GeneralTool::kGeneralToolStamp; } - out_ << "build "; - WriteOutput(stamp_or_phony); - out_ << ": " << tool; + AddEdge(NinjaBuildEdge{ + .rule = tool, + .outputs = {stamp_or_phony}, + .explicit_inputs = outs, + .is_target_output = false, + }); - // File inputs. - for (const auto* input : inputs) { - out_ << " "; - path_output_.WriteFile(out_, *input); - } - - out_ << std::endl; return {stamp_or_phony}; } @@ -372,46 +366,44 @@ void NinjaBinaryTargetWriter::WriteCompilerBuildLine( const std::vector<SourceFile>& sources, - const std::vector<OutputFile>& extra_deps, - const std::vector<OutputFile>& order_only_deps, + std::vector<OutputFile> extra_deps, + std::vector<OutputFile> order_only_deps, const Tool* tool, - const std::vector<OutputFile>& outputs, + std::vector<OutputFile> outputs, bool can_write_source_info, bool restat_output_allowed) { - out_ << "build"; - WriteOutputs(outputs); - - out_ << ": " << rule_prefix_ << tool->name(); - path_output_.WriteFiles(out_, sources); - - if (!extra_deps.empty() || !tool->inputs().empty()) { - out_ << " |"; - path_output_.WriteFiles(out_, extra_deps); - if (auto phony = tool->inputs_phony_or_file(rule_prefix_, - *settings_->build_settings())) { - out_ << " "; - path_output_.WriteFile(out_, *phony); - } + if (auto phony = tool->inputs_phony_or_file(rule_prefix_, + *settings_->build_settings())) { + extra_deps.push_back(*phony); } - if (!order_only_deps.empty()) { - out_ << " ||"; - path_output_.WriteFiles(out_, order_only_deps); - } - WriteValidations(); - out_ << std::endl; + NinjaBuildEdge edge{ + .rule = rule_prefix_ + tool->name(), + .outputs = std::move(outputs), + .explicit_inputs = ToOutputFiles(sources), + .implicit_inputs = std::move(extra_deps), + .order_only_inputs = std::move(order_only_deps), + }; + AddValidationInputs(edge); if (!sources.empty() && can_write_source_info) { - out_ << " " << "source_file_part = " << sources[0].GetName(); - out_ << std::endl; - out_ << " " << "source_name_part = " - << FindFilenameNoExtension(&sources[0].value()); - out_ << std::endl; + edge.edge_vars.emplace_back("source_file_part", sources[0].GetName()); + edge.edge_vars.emplace_back( + "source_name_part", + std::string(FindFilenameNoExtension(&sources[0].value()))); } if (restat_output_allowed) { - out_ << " restat = 1" << std::endl; + edge.edge_vars.emplace_back("restat", "1"); } + + if (target_->pool().ptr) { + edge.edge_vars.emplace_back("pool", + target_->pool().ptr->GetNinjaName( + settings_->default_toolchain_label())); + } + + AddEdge(std::move(edge)); } void NinjaBinaryTargetWriter::WriteCustomLinkerFlags(std::ostream& out, @@ -467,7 +459,7 @@ WriteLibrarySearchPath(out, tool); if (optional_def_file) { - out_ << " /DEF:"; + out << " /DEF:"; path_output_.WriteFile(out, *optional_def_file); } }
diff --git a/src/gn/ninja_binary_target_writer.h b/src/gn/ninja_binary_target_writer.h index 99b2b72..93774a6 100644 --- a/src/gn/ninja_binary_target_writer.h +++ b/src/gn/ninja_binary_target_writer.h
@@ -20,7 +20,7 @@ NinjaBinaryTargetWriter(const Target* target, std::ostream& out); ~NinjaBinaryTargetWriter() override; - void Run() override; + void GenerateRules() override; void WritePublicModuleMap(std::ostream& out, const SourceDir& out_dir); void WritePrivateModuleMap(std::ostream& out, const SourceDir& out_dir); @@ -42,7 +42,7 @@ // If there are no implicit dependencies and no extra target dependencies // are passed in, this returns an empty vector. std::vector<OutputFile> WriteInputsStampOrPhonyAndGetDep( - size_t num_phony_uses) const; + size_t num_phony_uses); // Gets all target dependencies and classifies them, as well as accumulates // object files from source sets we need to link. @@ -60,10 +60,10 @@ ClassifiedDeps* classified_deps) const; void WriteCompilerBuildLine(const std::vector<SourceFile>& sources, - const std::vector<OutputFile>& extra_deps, - const std::vector<OutputFile>& order_only_deps, + std::vector<OutputFile> extra_deps, + std::vector<OutputFile> order_only_deps, const Tool* tool, - const std::vector<OutputFile>& outputs, + std::vector<OutputFile> outputs, bool can_write_source_info = true, bool restat_output_allowed = false);
diff --git a/src/gn/ninja_binary_target_writer_unittest.cc b/src/gn/ninja_binary_target_writer_unittest.cc index d8e12bc..1c34ff0 100644 --- a/src/gn/ninja_binary_target_writer_unittest.cc +++ b/src/gn/ninja_binary_target_writer_unittest.cc
@@ -42,15 +42,18 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc\n" " source_file_part = input2.cc\n" " source_name_part = input2\n" "\n" "build phony/foo/bar.linkdeps: phony obj/foo/bar.input1.o " "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -143,12 +146,14 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build obj/foo/bar.source1.o: cxx ../../foo/source1.cc | " "../../foo/input1 ../../foo/input2\n" " source_file_part = source1.cc\n" " source_name_part = source1\n" "\n" "build phony/foo/bar.linkdeps: phony obj/foo/bar.source1.o\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -180,12 +185,15 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build phony/foo/bar.inputs: phony " "../../foo/input1 ../../foo/input2\n" + "\n" "build obj/foo/bar.source1.o: cxx ../../foo/source1.cc | " "phony/foo/bar.inputs\n" " source_file_part = source1.cc\n" " source_name_part = source1\n" + "\n" "build obj/foo/bar.source2.o: cxx ../../foo/source2.cc | " "phony/foo/bar.inputs\n" " source_file_part = source2.cc\n" @@ -193,6 +201,7 @@ "\n" "build phony/foo/bar.linkdeps: phony obj/foo/bar.source1.o " "obj/foo/bar.source2.o\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -240,12 +249,14 @@ "target_out_dir = obj/foo\n" "target_output_name = b\n" "\n" + "\n" "build obj/foo/b.source1.o: cxx ../../foo/source1.cc | " "phony/foo/a.public_inputs || phony/foo/a\n" " source_file_part = source1.cc\n" " source_name_part = source1\n" "\n" "build phony/foo/b.linkdeps: phony obj/foo/b.source1.o || phony/foo/a\n" + "\n" "build phony/foo/b: phony phony/foo/b.linkdeps\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str);
diff --git a/src/gn/ninja_bundle_data_target_writer.cc b/src/gn/ninja_bundle_data_target_writer.cc index d6c2e56..01373b1 100644 --- a/src/gn/ninja_bundle_data_target_writer.cc +++ b/src/gn/ninja_bundle_data_target_writer.cc
@@ -14,7 +14,7 @@ NinjaBundleDataTargetWriter::~NinjaBundleDataTargetWriter() = default; -void NinjaBundleDataTargetWriter::Run() { +void NinjaBundleDataTargetWriter::GenerateRules() { std::vector<OutputFile> output_files; for (const SourceFile& source_file : target_->sources()) { output_files.push_back(
diff --git a/src/gn/ninja_bundle_data_target_writer.h b/src/gn/ninja_bundle_data_target_writer.h index 720c593..be87dd8 100644 --- a/src/gn/ninja_bundle_data_target_writer.h +++ b/src/gn/ninja_bundle_data_target_writer.h
@@ -13,7 +13,7 @@ NinjaBundleDataTargetWriter(const Target* target, std::ostream& out); ~NinjaBundleDataTargetWriter() override; - void Run() override; + void GenerateRules() override; private: NinjaBundleDataTargetWriter(const NinjaBundleDataTargetWriter&) = delete;
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc index a56f587..155d0de 100644 --- a/src/gn/ninja_c_binary_target_writer.cc +++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -11,15 +11,12 @@ #include <set> #include <sstream> -#include "base/strings/string_util.h" #include "gn/builtin_tool.h" #include "gn/c_substitution_type.h" #include "gn/config_values_extractors.h" -#include "gn/deps_iterator.h" #include "gn/err.h" #include "gn/escape.h" #include "gn/filesystem_utils.h" -#include "gn/general_tool.h" #include "gn/ninja_module_writer_util.h" #include "gn/ninja_target_command_util.h" #include "gn/ninja_utils.h" @@ -63,7 +60,7 @@ NinjaCBinaryTargetWriter::~NinjaCBinaryTargetWriter() = default; -void NinjaCBinaryTargetWriter::Run() { +void NinjaCBinaryTargetWriter::GenerateRules() { std::set<ClangModuleDep> module_dep_info = GetModuleDepsInformation(target_, resolved()); @@ -176,8 +173,7 @@ const std::set<ClangModuleDep>& module_dep_info) { const SubstitutionBits& subst = target_->toolchain()->substitution_bits(); - WriteCCompilerVars(subst, /*indent=*/false, - /*respect_source_types_used=*/true); + WriteCCompilerVars(subst, /*respect_source_used=*/true); WriteModuleNameSubstitution(); @@ -198,11 +194,12 @@ void NinjaCBinaryTargetWriter::WriteModuleNameSubstitution() { if (target_->toolchain()->substitution_bits().used.count( &CSubstitutionModuleName)) { - out_ << CSubstitutionModuleName.ninja_name << " = "; EscapeOptions options; options.mode = ESCAPE_NINJA; - EscapeStringToStream(out_, target_->module_name(), options); - out_ << std::endl; + std::ostringstream val; + EscapeStringToStream(val, target_->module_name(), options); + target_group_.target_vars.emplace_back(CSubstitutionModuleName.ninja_name, + val.str()); } } @@ -211,15 +208,11 @@ const std::set<ClangModuleDep>& module_dep_info, bool include_self) { if (target_->toolchain()->substitution_bits().used.count(substitution)) { - EscapeOptions options; - options.mode = ESCAPE_NINJA_COMMAND; - - out_ << substitution->ninja_name << " ="; + std::ostringstream val; for (const auto& module_dep : module_dep_info) { - module_dep.Write(out_, path_output_, include_self); + module_dep.Write(val, path_output_, include_self); } - - out_ << std::endl; + target_group_.target_vars.emplace_back(substitution->ninja_name, val.str()); } } @@ -312,7 +305,7 @@ // This build line needs a custom language-specific flags value. Rule-specific // variables are just indented underneath the rule line. - out_ << " " << flag_type->ninja_name << " ="; + std::ostringstream flag_val; // Each substitution flag is overwritten in the target rule to replace the // implicitly generated -include flag with the -x <header lang> flag required @@ -321,27 +314,26 @@ if (tool_name == CTool::kCToolCc) { RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, target_, &ConfigValues::cflags_c, opts, - out_); + flag_val); } else if (tool_name == CTool::kCToolCxx) { RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, target_, &ConfigValues::cflags_cc, - opts, out_); + opts, flag_val); } else if (tool_name == CTool::kCToolObjC) { RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, target_, &ConfigValues::cflags_objc, - opts, out_); + opts, flag_val); } else if (tool_name == CTool::kCToolObjCxx) { RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, target_, &ConfigValues::cflags_objcc, - opts, out_); + opts, flag_val); } // Append the command to specify the language of the .gch file. - out_ << " -x " << GetPCHLangForToolType(tool_name); + flag_val << " -x " << GetPCHLangForToolType(tool_name); - // Write two blank lines to help separate the PCH build lines from the - // regular source build lines. - out_ << std::endl << std::endl; + target_group_.edges.back().edge_vars.emplace_back(flag_type->ninja_name, + flag_val.str()); } void NinjaCBinaryTargetWriter::WriteWindowsPCHCommand( @@ -368,16 +360,15 @@ // This build line needs a custom language-specific flags value. Rule-specific // variables are just indented underneath the rule line. - out_ << " " << flag_type->ninja_name << " ="; + std::ostringstream flag_val; // Append the command to generate the .pch file. // This adds the value to the existing flag instead of overwriting it. - out_ << " ${" << flag_type->ninja_name << "}"; - out_ << " /Yc" << target_->config_values().precompiled_header(); + flag_val << " ${" << flag_type->ninja_name << "} /Yc" + << target_->config_values().precompiled_header(); - // Write two blank lines to help separate the PCH build lines from the - // regular source build lines. - out_ << std::endl << std::endl; + target_group_.edges.back().edge_vars.emplace_back(flag_type->ninja_name, + flag_val.str()); } void NinjaCBinaryTargetWriter::WriteSources( @@ -475,7 +466,6 @@ WriteCompilerBuildLine({source}, deps, order_only_deps, tool, tool_outputs); - WritePool(out_); } // It's theoretically possible for a compiler to produce more than one @@ -492,8 +482,6 @@ extra_files->push_back(tool_outputs[i]); } } - - out_ << std::endl; } void NinjaCBinaryTargetWriter::WriteSwiftSources( @@ -529,8 +517,6 @@ swift_order_only_deps.vector(), tool, *output_files, /*can_write_source_info=*/false, /*restat_output_allowed=*/true); - - out_ << std::endl; } void NinjaCBinaryTargetWriter::WriteSourceSetStamp( @@ -569,53 +555,53 @@ path.append(".linkdeps"); link_phony = OutputFile(std::move(path)); - out_ << "build "; - path_output_.WriteFile(out_, link_phony); - out_ << ": " << BuiltinTool::kBuiltinToolPhony; - path_output_.WriteFiles(out_, link_files); - if (!order_only_deps.empty()) { - out_ << " ||"; - path_output_.WriteFiles(out_, order_only_deps); - } - out_ << std::endl; + NinjaBuildEdge link_edge{ + .rule = BuiltinTool::kBuiltinToolPhony, + .outputs = {link_phony}, + .explicit_inputs = link_files, + .order_only_inputs = order_only_deps, + .is_target_output = false, + }; + AddEdge(std::move(link_edge)); // 2. Default phony target containing all files (including additional // outputs). Depend on the .link target to avoid duplicating object files. - out_ << "build "; - path_output_.WriteFile(out_, target_->dependency_output()); - out_ << ": " << BuiltinTool::kBuiltinToolPhony; - out_ << " "; - path_output_.WriteFile(out_, link_phony); + NinjaBuildEdge default_edge{ + .rule = BuiltinTool::kBuiltinToolPhony, + .outputs = {target_->dependency_output()}, + .explicit_inputs = {link_phony}, + .is_target_output = false, + }; // Collect non-object files (additional outputs) to add here. - std::vector<OutputFile> non_object_files; for (const auto& file : object_files) { if (!file.AsSourceFile(build_settings).IsObjectType()) { - non_object_files.push_back(file); + default_edge.explicit_inputs.push_back(file); } } - path_output_.WriteFiles(out_, non_object_files); - out_ << std::endl; + AddEdge(std::move(default_edge)); } void NinjaCBinaryTargetWriter::WriteLinkerStuff( - const std::vector<OutputFile>& object_files, + std::vector<OutputFile> object_files, const std::vector<SourceFile>& other_files, const std::vector<OutputFile>& input_deps) { std::vector<OutputFile> output_files; SubstitutionWriter::ApplyListToLinkerAsOutputFile( target_, tool_, tool_->outputs(), &output_files); - out_ << "build"; - WriteOutputs(output_files); - - out_ << ": " << rule_prefix_ << tool_->name(); + NinjaBuildEdge edge{ + .rule = rule_prefix_ + tool_->name(), + .outputs = std::move(output_files), + }; ClassifiedDeps classified_deps = GetClassifiedDeps(); // Object files. - path_output_.WriteFiles(out_, object_files); - path_output_.WriteFiles(out_, classified_deps.extra_object_files); + edge.explicit_inputs = std::move(object_files); + edge.explicit_inputs.insert(edge.explicit_inputs.end(), + classified_deps.extra_object_files.begin(), + classified_deps.extra_object_files.end()); // Dependencies. std::vector<OutputFile> implicit_deps; @@ -638,8 +624,7 @@ solibs.push_back(cur->link_output_file()); } else { // Normal case, just link to this target. - out_ << " "; - path_output_.WriteFile(out_, cur->link_output_file()); + edge.explicit_inputs.push_back(cur->link_output_file()); } } @@ -712,11 +697,7 @@ } } - // Append implicit dependencies collected above. - if (!implicit_deps.empty()) { - out_ << " |"; - path_output_.WriteFiles(out_, implicit_deps); - } + edge.implicit_inputs = std::move(implicit_deps); // Append data dependencies as order-only dependencies. // @@ -731,87 +712,77 @@ // on the sources, there is already an implicit order-only dependency. // However, it's extra work to separate these out and there's no disadvantage // to listing them again. - WriteOrderOnlyDependencies(classified_deps.non_linkable_deps); + edge.order_only_inputs = + GetOrderOnlyDepsFromNonLinkableDeps(classified_deps.non_linkable_deps); - WriteValidations(); - - // End of the link "build" line. - out_ << std::endl; + AddValidationInputs(edge); // The remaining things go in the inner scope of the link line. if (target_->output_type() == Target::EXECUTABLE || target_->output_type() == Target::SHARED_LIBRARY || target_->output_type() == Target::LOADABLE_MODULE) { - out_ << " ldflags ="; - WriteLinkerFlags(out_, tool_, optional_def_file); - out_ << std::endl; - out_ << " libs ="; - WriteLibs(out_, tool_); - out_ << std::endl; - out_ << " frameworks ="; - WriteFrameworks(out_, tool_); - out_ << std::endl; - out_ << " swiftmodules ="; - WriteSwiftModules(out_, tool_, swiftmodules); - out_ << std::endl; + { + std::ostringstream ss; + WriteLinkerFlags(ss, tool_, optional_def_file); + edge.edge_vars.emplace_back("ldflags", ss.str()); + } + { + std::ostringstream ss; + WriteLibs(ss, tool_); + edge.edge_vars.emplace_back("libs", ss.str()); + } + { + std::ostringstream ss; + WriteFrameworks(ss, tool_); + edge.edge_vars.emplace_back("frameworks", ss.str()); + } + { + std::ostringstream ss; + WriteSwiftModules(ss, tool_, swiftmodules); + edge.edge_vars.emplace_back("swiftmodules", ss.str()); + } } else if (target_->output_type() == Target::STATIC_LIBRARY) { - out_ << " arflags ="; + std::ostringstream ss; RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, target_, &ConfigValues::arflags, - GetFlagOptions(), out_); - out_ << std::endl; + GetFlagOptions(), ss); + edge.edge_vars.emplace_back("arflags", ss.str()); } - WriteOutputSubstitutions(); - WriteLibsList("solibs", solibs); - WriteLibsList("rlibs", transitive_rustlibs); - WritePool(out_); -} -void NinjaCBinaryTargetWriter::WriteOutputSubstitutions() { const std::string output_extension = SubstitutionWriter::GetLinkerSubstitution(target_, tool_, &SubstitutionOutputExtension); - out_ << " output_extension ="; - if (!output_extension.empty()) { - out_ << " " << output_extension; - } - out_ << std::endl; + edge.edge_vars.emplace_back("output_extension", output_extension); const std::string output_dir = SubstitutionWriter::GetLinkerSubstitution( target_, tool_, &SubstitutionOutputDir); - out_ << " output_dir ="; - if (!output_dir.empty()) { - out_ << " " << output_dir; + edge.edge_vars.emplace_back("output_dir", output_dir); + + if (!solibs.empty()) { + std::ostringstream ss; + PathOutput output(path_output_.current_dir(), + settings_->build_settings()->root_path_utf8(), + ESCAPE_NINJA_COMMAND); + output.WriteFiles(ss, solibs); + edge.edge_vars.emplace_back("solibs", ss.str()); } - out_ << std::endl; -} -void NinjaCBinaryTargetWriter::WriteLibsList( - const std::string& label, - const std::vector<OutputFile>& libs) { - if (libs.empty()) - return; - - out_ << " " << label << " ="; - PathOutput output(path_output_.current_dir(), - settings_->build_settings()->root_path_utf8(), - ESCAPE_NINJA_COMMAND); - output.WriteFiles(out_, libs); - out_ << std::endl; -} - -void NinjaCBinaryTargetWriter::WriteOrderOnlyDependencies( - const UniqueVector<const Target*>& non_linkable_deps) { - std::vector<OutputFile> outputs_to_write = - GetOrderOnlyDepsFromNonLinkableDeps(non_linkable_deps); - - if (!outputs_to_write.empty()) { - out_ << " ||"; - for (const auto& output : outputs_to_write) { - out_ << " "; - path_output_.WriteFile(out_, output); - } + if (!transitive_rustlibs.empty()) { + std::ostringstream ss; + PathOutput output(path_output_.current_dir(), + settings_->build_settings()->root_path_utf8(), + ESCAPE_NINJA_COMMAND); + output.WriteFiles(ss, transitive_rustlibs); + edge.edge_vars.emplace_back("rlibs", ss.str()); } + + if (target_->pool().ptr) { + edge.edge_vars.emplace_back("pool", + target_->pool().ptr->GetNinjaName( + settings_->default_toolchain_label())); + } + + AddEdge(std::move(edge)); } bool NinjaCBinaryTargetWriter::CheckForDuplicateObjectFiles(
diff --git a/src/gn/ninja_c_binary_target_writer.h b/src/gn/ninja_c_binary_target_writer.h index 3353a1b..7ba7840 100644 --- a/src/gn/ninja_c_binary_target_writer.h +++ b/src/gn/ninja_c_binary_target_writer.h
@@ -20,7 +20,7 @@ NinjaCBinaryTargetWriter(const Target* target, std::ostream& out); ~NinjaCBinaryTargetWriter() override; - void Run() override; + void GenerateRules() override; private: using OutputFileSet = std::set<OutputFile>; @@ -92,20 +92,9 @@ // Writes the stamp line for a source set. These are not linked. void WriteSourceSetStamp(const std::vector<OutputFile>& object_files); - void WriteLinkerStuff(const std::vector<OutputFile>& object_files, + void WriteLinkerStuff(std::vector<OutputFile> object_files, const std::vector<SourceFile>& other_files, const std::vector<OutputFile>& input_deps); - void WriteOutputSubstitutions(); - void WriteLibsList(const std::string& label, - const std::vector<OutputFile>& libs); - - // Writes the implicit dependencies for the link or stamp line. This is - // the "||" and everything following it on the ninja line. - // - // The order-only dependencies are the non-linkable deps passed in as an - // argument, plus the data file dependencies in the target. - void WriteOrderOnlyDependencies( - const UniqueVector<const Target*>& non_linkable_deps); // Checks for duplicates in the given list of output files. If any duplicates // are found, throws an error and return false.
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc index 34a473f..3d051ee 100644 --- a/src/gn/ninja_c_binary_target_writer_unittest.cc +++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -60,15 +60,18 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc\n" " source_file_part = input2.cc\n" " source_name_part = input2\n" "\n" "build phony/foo/bar.linkdeps: phony obj/foo/bar.input1.o " "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -215,12 +218,14 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build obj/foo/bar.input1.o obj/foo/input1.dwo: cxx " "../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" "\n" "build phony/foo/bar.linkdeps: phony obj/foo/bar.input1.o\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps obj/foo/input1.dwo\n"; EXPECT_EQ(expected, out.str()); @@ -269,12 +274,14 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build obj/foo/bar.input1.o obj/foo/input1.dwo: cxx " "../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" "\n" "build phony/foo/bar.linkdeps: phony obj/foo/bar.input1.o\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps obj/foo/input1.dwo\n"; EXPECT_EQ(expected, out.str()); @@ -337,14 +344,17 @@ "target_out_dir = obj/foo\n" "target_output_name = b\n" "\n" + "\n" "build obj/foo/b.b.o obj/foo/b.dwo: cxx ../../foo/b.cc\n" " source_file_part = b.cc\n" " source_name_part = b\n" + "\n" "build obj/foo/b.b2.o obj/foo/b2.dwo: cxx ../../foo/b2.cc\n" " source_file_part = b2.cc\n" " source_name_part = b2\n" "\n" "build phony/foo/b.linkdeps: phony obj/foo/b.b.o obj/foo/b.b2.o\n" + "\n" "build phony/foo/b: phony phony/foo/b.linkdeps obj/foo/b.dwo " "obj/foo/b2.dwo\n"; @@ -367,6 +377,7 @@ "target_out_dir = obj/foo\n" "target_output_name = liba\n" "\n" + "\n" "build obj/foo/liba.a.o: cxx ../../foo/a.cc\n" " source_file_part = a.cc\n" " source_name_part = a\n" @@ -459,6 +470,7 @@ "target_out_dir = obj/foo\n" "target_output_name = libf\n" "\n" + "\n" "build obj/foo/libf.f.o obj/foo/f.dwo: cxx ../../foo/f.cc\n" " source_file_part = f.cc\n" " source_name_part = f\n" @@ -520,6 +532,7 @@ "target_out_dir = obj/foo\n" "target_output_name = libbar\n" "\n" + "\n" "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" @@ -569,6 +582,7 @@ "target_out_dir = obj/foo\n" "target_output_name = libbar\n" "\n" + "\n" "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" @@ -601,6 +615,7 @@ "target_out_dir = obj/foo\n" "target_output_name = libbar\n" "\n" + "\n" "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" @@ -652,6 +667,7 @@ "target_out_dir = obj/foo\n" "target_output_name = libbar\n" "\n" + "\n" "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" @@ -713,6 +729,7 @@ "target_out_dir = obj/foo\n" "target_output_name = main\n" "\n" + "\n" "build obj/foo/main.main.o: cxx ../../foo/main.cc\n" " source_file_part = main.cc\n" " source_name_part = main\n" @@ -769,10 +786,12 @@ "target_out_dir = obj/foo\n" "target_output_name = libshlib\n" "\n" + "\n" "build obj/foo/libshlib.input1.o: cxx ../../foo/input1.cc" " || phony/foo/action\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build obj/foo/libshlib.input2.o: cxx ../../foo/input2.cc" " || phony/foo/action\n" " source_file_part = input2.cc\n" @@ -837,6 +856,7 @@ "target_out_dir = obj/foo\n" "target_output_name = gen_obj\n" "\n" + "\n" "build obj/BUILD_DIR/gen_obj.generated.o: cxx generated.cc" " || phony/foo/generate\n" " source_file_part = generated.cc\n" @@ -847,6 +867,7 @@ // The order-only dependency here is strictly unnecessary since the // sources list this as an order-only dep. " || phony/foo/generate\n" + "\n" "build phony/foo/gen_obj: phony phony/foo/gen_obj.linkdeps\n"; std::string obj_str = obj_out.str(); @@ -918,6 +939,7 @@ "target_out_dir = obj/foo\n" "target_output_name = final_target\n" "\n" + "\n" "build obj/foo/final_target.main.o: cxx ../../foo/main.cc\n" " source_file_part = main.cc\n" " source_name_part = main\n" @@ -1080,9 +1102,11 @@ "target_out_dir = obj/foo\n" "target_output_name = shlib\n" "\n" + "\n" "build obj/foo/shlib.input1.o: cxx ../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build obj/foo/shlib.input2.o: cxx ../../foo/input2.cc\n" " source_file_part = input2.cc\n" " source_name_part = input2\n" @@ -1139,12 +1163,14 @@ "target_out_dir = obj/foo\n" "target_output_name = inter\n" "\n" + "\n" "build obj/foo/inter.inter.o: cxx ../../foo/inter.cc\n" " source_file_part = inter.cc\n" " source_name_part = inter\n" "\n" "build phony/foo/inter.linkdeps: phony obj/foo/inter.inter.o || " "./data_target\n" + "\n" "build phony/foo/inter: phony phony/foo/inter.linkdeps\n"; EXPECT_EQ(inter_expected, inter_out.str()); @@ -1176,6 +1202,7 @@ "target_out_dir = obj/foo\n" "target_output_name = exe\n" "\n" + "\n" "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n" " source_file_part = final.cc\n" " source_name_part = final\n" @@ -1218,6 +1245,7 @@ "target_out_dir = obj/foo\n" "target_output_name = libbar\n" "\n" + "\n" "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n" " source_file_part = sources.cc\n" " source_name_part = sources\n" @@ -1258,6 +1286,7 @@ "target_out_dir = obj/foo\n" "target_output_name = libbar\n" "\n" + "\n" "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n" " source_file_part = sources.cc\n" " source_name_part = sources\n" @@ -1296,6 +1325,7 @@ "target_out_dir = obj/foo\n" "target_output_name = exe\n" "\n" + "\n" "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n" " source_file_part = final.cc\n" " source_name_part = final\n" @@ -1374,10 +1404,12 @@ "cflags_cc =\n" "target_output_name = no_pch_target\n" "\n" + "\n" "build withpch/obj/foo/no_pch_target.input1.o: " "withpch_cxx ../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build withpch/obj/foo/no_pch_target.input2.o: " "withpch_cc ../../foo/input2.c\n" " source_file_part = input2.c\n" @@ -1386,6 +1418,7 @@ "build withpch/phony/foo/no_pch_target.linkdeps: " "phony withpch/obj/foo/no_pch_target.input1.o " "withpch/obj/foo/no_pch_target.input2.o\n" + "\n" "build withpch/phony/foo/no_pch_target: " "phony withpch/phony/foo/no_pch_target.linkdeps\n"; EXPECT_EQ(no_pch_expected, out.str()); @@ -1421,6 +1454,7 @@ "/Yubuild/precompile.h\n" "target_output_name = pch_target\n" "\n" + "\n" // Compile the precompiled source files with /Yc. "build withpch/obj/build/pch_target.precompile.c.o: " "withpch_cc ../../build/precompile.cc\n" @@ -1440,6 +1474,7 @@ "withpch/obj/build/pch_target.precompile.cc.o\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build withpch/obj/foo/pch_target.input2.o: " "withpch_cc ../../foo/input2.c | " // Explicit dependency on the PCH build step. @@ -1453,6 +1488,7 @@ // The precompiled object files were added to the outputs. "withpch/obj/build/pch_target.precompile.c.o " "withpch/obj/build/pch_target.precompile.cc.o\n" + "\n" "build withpch/phony/foo/pch_target: phony " "withpch/phony/foo/pch_target.linkdeps\n"; EXPECT_EQ(pch_win_expected, out.str()); @@ -1524,10 +1560,12 @@ "cflags_cc =\n" "target_output_name = no_pch_target\n" "\n" + "\n" "build withpch/obj/foo/no_pch_target.input1.o: " "withpch_cxx ../../foo/input1.cc\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build withpch/obj/foo/no_pch_target.input2.o: " "withpch_cc ../../foo/input2.c\n" " source_file_part = input2.c\n" @@ -1536,6 +1574,7 @@ "build withpch/phony/foo/no_pch_target.linkdeps: " "phony withpch/obj/foo/no_pch_target.input1.o " "withpch/obj/foo/no_pch_target.input2.o\n" + "\n" "build withpch/phony/foo/no_pch_target: " "phony withpch/phony/foo/no_pch_target.linkdeps\n"; EXPECT_EQ(no_pch_expected, out.str()); @@ -1569,6 +1608,7 @@ "cflags_cc = -include withpch/obj/build/pch_target.precompile.h-cc\n" "target_output_name = pch_target\n" "\n" + "\n" // Compile the precompiled sources with -x <lang>. "build withpch/obj/build/pch_target.precompile.h-c.gch: " "withpch_cc ../../build/precompile.h\n" @@ -1588,6 +1628,7 @@ "withpch/obj/build/pch_target.precompile.h-cc.gch\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build withpch/obj/foo/pch_target.input2.o: " "withpch_cc ../../foo/input2.c | " // Explicit dependency on the PCH build step. @@ -1598,6 +1639,7 @@ "build withpch/phony/foo/pch_target.linkdeps: " "phony withpch/obj/foo/pch_target.input1.o " "withpch/obj/foo/pch_target.input2.o\n" + "\n" "build withpch/phony/foo/pch_target: " "phony withpch/phony/foo/pch_target.linkdeps\n"; EXPECT_EQ(pch_gcc_expected, out.str()); @@ -1659,10 +1701,12 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc" " | ../../foo/input.data\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc" " | ../../foo/input.data\n" " source_file_part = input2.cc\n" @@ -1670,6 +1714,7 @@ "\n" "build phony/foo/bar.linkdeps: phony obj/foo/bar.input1.o " "obj/foo/bar.input2.o\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps\n"; EXPECT_EQ(expected, out.str()); @@ -1735,12 +1780,15 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build phony/foo/bar.inputs: phony" " ../../foo/input1.data ../../foo/input2.data\n" + "\n" "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc" " | phony/foo/bar.inputs\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc" " | phony/foo/bar.inputs\n" " source_file_part = input2.cc\n" @@ -1748,6 +1796,7 @@ "\n" "build phony/foo/bar.linkdeps: phony obj/foo/bar.input1.o " "obj/foo/bar.input2.o\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps\n"; EXPECT_EQ(expected, out.str()); @@ -1791,12 +1840,15 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build phony/foo/bar.inputs: phony" " ../../foo/input1.data ../../foo/input2.data ../../foo/input3.data\n" + "\n" "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc" " | phony/foo/bar.inputs\n" " source_file_part = input1.cc\n" " source_name_part = input1\n" + "\n" "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc" " | phony/foo/bar.inputs\n" " source_file_part = input2.cc\n" @@ -1804,6 +1856,7 @@ "\n" "build phony/foo/bar.linkdeps: phony obj/foo/bar.input1.o " "obj/foo/bar.input2.o\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps\n"; EXPECT_EQ(expected, out.str()); @@ -1849,6 +1902,7 @@ "target_out_dir = obj/bar\n" "target_output_name = bar\n" "\n" + "\n" "build obj/bar/bar.bar.o: cxx ../../bar/bar.cc\n" " source_file_part = bar.cc\n" " source_name_part = bar\n" @@ -2026,6 +2080,7 @@ "target_out_dir = obj/exe\n" "target_output_name = exe\n" "\n" + "\n" "build obj/exe/exe.main.o: cxx ../../exe/main.cc\n" " source_file_part = main.cc\n" " source_name_part = main\n" @@ -2194,6 +2249,7 @@ "target_out_dir = obj/exe\n" "target_output_name = exe\n" "\n" + "\n" "build obj/exe/exe.main.o: cxx ../../exe/main.cc\n" " source_file_part = main.cc\n" " source_name_part = main\n" @@ -2271,6 +2327,7 @@ "target_out_dir = obj/bar\n" "target_output_name = bar\n" "\n" + "\n" "build obj/bar/bar.bar.o: cxx ../../bar/bar.cc\n" " source_file_part = bar.cc\n" " source_name_part = bar\n" @@ -2374,6 +2431,7 @@ "target_out_dir = obj/exe\n" "target_output_name = binary\n" "\n" + "\n" "build obj/exe/binary.main.o: cxx ../../exe/main.cc\n" " source_file_part = main.cc\n" " source_name_part = main\n" @@ -2470,6 +2528,7 @@ "target_out_dir = obj/exe\n" "target_output_name = binary\n" "\n" + "\n" "build obj/exe/binary.main.o: cxx ../../exe/main.cc\n" " source_file_part = main.cc\n" " source_name_part = main\n" @@ -2568,6 +2627,7 @@ "target_out_dir = obj/exe\n" "target_output_name = binary\n" "\n" + "\n" "build obj/exe/binary.main.o: cxx ../../exe/main.cc\n" " source_file_part = main.cc\n" " source_name_part = main\n" @@ -2678,6 +2738,7 @@ "target_out_dir = obj/exe\n" "target_output_name = binary\n" "\n" + "\n" "build obj/exe/binary.main.o: cxx ../../exe/main.cc\n" " source_file_part = main.cc\n" " source_name_part = main\n" @@ -2726,10 +2787,12 @@ "target_out_dir = obj/foo\n" "target_output_name = libbar\n" "\n" + "\n" "build obj/foo/libbar.bar.o: cxx ../../foo/bar.cc | " "obj/foo/libbar.bar.pcm\n" " source_file_part = bar.cc\n" " source_name_part = bar\n" + "\n" "build obj/foo/libbar.bar.pcm: cxx_module ../../foo/bar.modulemap\n" " source_file_part = bar.modulemap\n" " source_name_part = bar\n" @@ -2771,15 +2834,18 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build obj/foo/bar.bar.o: cxx ../../foo/bar.cc | " "obj/foo/bar.bar.pcm\n" " source_file_part = bar.cc\n" " source_name_part = bar\n" + "\n" "build obj/foo/bar.bar.pcm: cxx_module ../../foo/bar.modulemap\n" " source_file_part = bar.modulemap\n" " source_name_part = bar\n" "\n" "build phony/foo/bar.linkdeps: phony obj/foo/bar.bar.o\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps obj/foo/bar.bar.pcm\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -2816,11 +2882,13 @@ "target_out_dir = obj/foo\n" "target_output_name = foo\n" "\n" + "\n" "build gen/foo/foo.h obj/foo/Foo.swiftmodule obj/foo/file1.o " "obj/foo/file2.o: swift ../../foo/file1.swift ../../foo/file2.swift\n" " restat = 1\n" "\n" "build phony/foo/foo.linkdeps: phony obj/foo/file1.o obj/foo/file2.o\n" + "\n" "build phony/foo/foo: phony phony/foo/foo.linkdeps gen/foo/foo.h " "obj/foo/Foo.swiftmodule\n"; @@ -2854,12 +2922,14 @@ "target_out_dir = obj/bar\n" "target_output_name = bar\n" "\n" + "\n" "build gen/bar/bar.h obj/bar/Bar.swiftmodule obj/bar/bar.o: swift " "../../bar/bar.swift || phony/foo/foo\n" " restat = 1\n" "\n" "build phony/bar/bar.linkdeps: phony obj/bar/bar.o || " "phony/foo/foo.linkdeps\n" + "\n" "build phony/bar/bar: phony phony/bar/bar.linkdeps gen/bar/bar.h " "obj/bar/Bar.swiftmodule\n"; @@ -2901,12 +2971,14 @@ "target_out_dir = obj/bar\n" "target_output_name = bar\n" "\n" + "\n" "build gen/bar/bar.h obj/bar/Bar.swiftmodule obj/bar/bar.o: swift " "../../bar/bar.swift || phony/bar/group phony/foo/foo\n" " restat = 1\n" "\n" "build phony/bar/bar.linkdeps: phony obj/bar/bar.o || " "phony/foo/foo.linkdeps\n" + "\n" "build phony/bar/bar: phony phony/bar/bar.linkdeps gen/bar/bar.h " "obj/bar/Bar.swiftmodule\n"; @@ -3039,9 +3111,11 @@ target_out_dir = obj/blah target_output_name = liba + build obj/blah/liba.a.pcm: cxx_module ../../blah/a.modulemap source_file_part = a.modulemap source_name_part = a + build obj/blah/liba.a.o: cxx ../../blah/a.cc | obj/blah/liba.a.pcm source_file_part = a.cc source_name_part = a @@ -3088,9 +3162,11 @@ target_out_dir = obj/stuff target_output_name = libb + 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/stuff/libb.b.pcm obj/blah/liba.a.pcm source_file_part = b.cc source_name_part = b @@ -3135,6 +3211,7 @@ target_out_dir = obj/things target_output_name = libc + 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 @@ -3177,9 +3254,11 @@ target_out_dir = obj/zap target_output_name = c + 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/stuff/libb.b.pcm obj/blah/liba.a.pcm source_file_part = y.cc source_name_part = y @@ -3235,6 +3314,7 @@ target_out_dir = obj/launchpad target_output_name = main + build obj/launchpad/main.main.o: cxx ../../launchpad/main.cc source_file_part = main.cc source_name_part = main @@ -3286,6 +3366,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build obj/foo/bar.source.o: cxx ../../foo/source.cc\n" " source_file_part = source.cc\n" " source_name_part = source\n" @@ -3350,6 +3431,7 @@ "root_out_dir = .\n" "target_output_name = bar\n" "\n" + "\n" "build obj/foo/bar.source.o: cxx ../../foo/source.cc | " "../../bin/clang++\n" " source_file_part = source.cc\n" @@ -3443,11 +3525,13 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\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.linkdeps: phony obj/foo/bar.source1.o\n" + "\n" "build phony/foo/bar: phony phony/foo/bar.linkdeps\n"; writer.Run(); std::string ninja_str = ninja_out.str(); @@ -3590,11 +3674,13 @@ "target_out_dir = obj/foo\n" "target_output_name = root\n" "\n" + "\n" "build obj/foo/root.root.o: cxx ../../foo/root.cc\n" " source_file_part = root.cc\n" " source_name_part = root\n" "\n" "build phony/foo/root.linkdeps: phony obj/foo/root.root.o\n" + "\n" "build phony/foo/root: phony phony/foo/root.linkdeps\n"; EXPECT_EQ(expected_root_ninja, root_ninja_str); @@ -3664,12 +3750,14 @@ "target_out_dir = obj/foo\n" "target_output_name = a\n" "\n" + "\n" "build obj/foo/a.a.o: cxx ../../foo/a.cc\n" " source_file_part = a.cc\n" " source_name_part = a\n" "\n" "build phony/foo/a.linkdeps: phony obj/foo/a.a.o" " || phony/foo/b.linkdeps\n" + "\n" "build phony/foo/a: phony phony/foo/a.linkdeps\n"; EXPECT_EQ(expected, out.str());
diff --git a/src/gn/ninja_copy_target_writer.cc b/src/gn/ninja_copy_target_writer.cc index 16fca06..3c26e78 100644 --- a/src/gn/ninja_copy_target_writer.cc +++ b/src/gn/ninja_copy_target_writer.cc
@@ -4,7 +4,6 @@ #include "gn/ninja_copy_target_writer.h" -#include "base/strings/string_util.h" #include "gn/general_tool.h" #include "gn/ninja_utils.h" #include "gn/output_file.h" @@ -21,7 +20,7 @@ NinjaCopyTargetWriter::~NinjaCopyTargetWriter() = default; -void NinjaCopyTargetWriter::Run() { +void NinjaCopyTargetWriter::GenerateRules() { const Tool* copy_tool = target_->toolchain()->GetTool(GeneralTool::kGeneralToolCopy); if (!copy_tool) { @@ -55,7 +54,6 @@ std::vector<OutputFile> output_files; WriteCopyRules(&output_files); - out_ << std::endl; WriteStampOrPhonyForTarget(output_files, std::vector<OutputFile>()); } @@ -115,20 +113,15 @@ target_, target_->settings(), output_subst, input_file); output_files->push_back(output_file); - out_ << "build "; - WriteOutput(std::move(output_file)); - - out_ << ": " << tool_name << " "; - path_output_.WriteFile(out_, input_file); - if (!implicit_deps.empty()) { - out_ << " |"; - path_output_.WriteFiles(out_, implicit_deps); - } - if (!order_only_deps.empty()) { - out_ << " ||"; - path_output_.WriteFiles(out_, order_only_deps); - } - WriteValidations(); - out_ << std::endl; + NinjaBuildEdge edge{ + .rule = tool_name, + .outputs = {output_file}, + .explicit_inputs = {OutputFile(settings_->build_settings(), + input_file)}, + .implicit_inputs = implicit_deps, + .order_only_inputs = order_only_deps, + }; + AddValidationInputs(edge); + AddEdge(std::move(edge)); } }
diff --git a/src/gn/ninja_copy_target_writer.h b/src/gn/ninja_copy_target_writer.h index 95d50b4..1016f2c 100644 --- a/src/gn/ninja_copy_target_writer.h +++ b/src/gn/ninja_copy_target_writer.h
@@ -13,7 +13,7 @@ NinjaCopyTargetWriter(const Target* target, std::ostream& out); ~NinjaCopyTargetWriter() override; - void Run() override; + void GenerateRules() override; private: // Writes the rules top copy the file(s), putting the computed output file
diff --git a/src/gn/ninja_copy_target_writer_unittest.cc b/src/gn/ninja_copy_target_writer_unittest.cc index 6fc5e43..0199b50 100644 --- a/src/gn/ninja_copy_target_writer_unittest.cc +++ b/src/gn/ninja_copy_target_writer_unittest.cc
@@ -34,6 +34,7 @@ const char expected_linux[] = "build input1.out: copy ../../foo/input1.txt\n" + "\n" "build input2.out: copy ../../foo/input2.txt\n" "\n" "build phony/foo/bar: phony input1.out input2.out\n"; @@ -210,7 +211,9 @@ const char expected_linux[] = "build phony/foo/bar.inputdeps: phony || phony/foo/action1 " "phony/foo/action2\n" + "\n" "build action1.copy: copy action1.out | phony/foo/bar.inputdeps\n" + "\n" "build action2.copy: copy action2.out | phony/foo/bar.inputdeps\n" "\n" "build phony/foo/bar: phony action1.copy action2.copy\n";
diff --git a/src/gn/ninja_create_bundle_target_writer.cc b/src/gn/ninja_create_bundle_target_writer.cc index c91d41c..bf45ad4 100644 --- a/src/gn/ninja_create_bundle_target_writer.cc +++ b/src/gn/ninja_create_bundle_target_writer.cc
@@ -74,7 +74,7 @@ NinjaCreateBundleTargetWriter::~NinjaCreateBundleTargetWriter() = default; -void NinjaCreateBundleTargetWriter::Run() { +void NinjaCreateBundleTargetWriter::GenerateRules() { if (!EnsureAllToolsAvailable(target_)) return; @@ -109,14 +109,13 @@ // Write a phony target for the outer bundle directory. This allows other // targets to treat the entire bundle as a single unit, even though it is // a directory, so that it can be depended upon as a discrete build edge. - out_ << "build "; - - WriteOutput( - OutputFile(settings_->build_settings(), - target_->bundle_data().GetBundleRootDirOutput(settings_))); - out_ << ": " << BuiltinTool::kBuiltinToolPhony << " "; - out_ << target_->dependency_output().value(); - out_ << std::endl; + AddEdge(NinjaBuildEdge{ + .rule = BuiltinTool::kBuiltinToolPhony, + .outputs = {OutputFile( + settings_->build_settings(), + target_->bundle_data().GetBundleRootDirOutput(settings_))}, + .explicit_inputs = {target_->dependency_output()}, + }); } std::string NinjaCreateBundleTargetWriter::WritePostProcessingRuleDefinition() { @@ -128,24 +127,28 @@ base::ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name); custom_rule_name.append("_post_processing_rule"); - out_ << "rule " << custom_rule_name << std::endl; - out_ << " command = "; - path_output_.WriteFile(out_, settings_->build_settings()->python_path()); - out_ << " "; - path_output_.WriteFile(out_, target_->bundle_data().post_processing_script()); + std::ostringstream rule_out; + rule_out << "rule " << custom_rule_name << std::endl; + rule_out << " command = "; + path_output_.WriteFile(rule_out, settings_->build_settings()->python_path()); + rule_out << " "; + path_output_.WriteFile(rule_out, + target_->bundle_data().post_processing_script()); const SubstitutionList& args = target_->bundle_data().post_processing_args(); EscapeOptions args_escape_options; args_escape_options.mode = ESCAPE_NINJA_COMMAND; for (const auto& arg : args.list()) { - out_ << " "; - SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, out_); + rule_out << " "; + SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, + rule_out); } - out_ << std::endl; - out_ << " description = POST PROCESSING " << target_label << std::endl; - out_ << " restat = 1" << std::endl; - out_ << std::endl; + rule_out << std::endl; + rule_out << " description = POST PROCESSING " << target_label << std::endl; + rule_out << " restat = 1" << std::endl; + + target_group_.custom_rules.push_back(rule_out.str()); WritePostProcessingManifestFile(); return custom_rule_name; @@ -208,22 +211,15 @@ /*err=*/nullptr); output_files->push_back(expanded_output_file); - out_ << "build "; - WriteOutput(std::move(expanded_output_file)); - out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) - << GeneralTool::kGeneralToolCopyBundleData << " "; - path_output_.WriteFile(out_, source_file); - - if (!implicit_deps.empty()) { - out_ << " |"; - path_output_.WriteFiles(out_, implicit_deps); - } - if (!order_only_deps.empty()) { - out_ << " ||"; - path_output_.WriteFiles(out_, order_only_deps); - } - - out_ << std::endl; + AddEdge(NinjaBuildEdge{ + .rule = GetNinjaRulePrefixForToolchain(settings_) + + GeneralTool::kGeneralToolCopyBundleData, + .outputs = {expanded_output_file}, + .explicit_inputs = {OutputFile(settings_->build_settings(), + source_file)}, + .implicit_inputs = implicit_deps, + .order_only_inputs = order_only_deps, + }); } } @@ -257,80 +253,68 @@ if (target_->bundle_data().assets_catalog_sources().empty()) { DCHECK(!target_->bundle_data().partial_info_plist().is_null()); - out_ << "build "; - WriteOutput(partial_info_plist); - out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) - << GeneralTool::kGeneralToolStamp; - if (!implicit_deps.empty()) { - out_ << " |"; - path_output_.WriteFiles(out_, implicit_deps); - } - if (!order_only_deps.empty()) { - out_ << " ||"; - path_output_.WriteFiles(out_, order_only_deps); - } - out_ << std::endl; + AddEdge(NinjaBuildEdge{ + .rule = GetNinjaRulePrefixForToolchain(settings_) + + GeneralTool::kGeneralToolStamp, + .outputs = {partial_info_plist}, + .implicit_inputs = implicit_deps, + .order_only_inputs = order_only_deps, + }); return; } OutputFile input_dep = WriteCompileAssetsCatalogInputDepsStampOrPhony( target_->bundle_data().assets_catalog_deps()); - DCHECK(!input_dep.value().empty()); - out_ << "build "; - WriteOutput(std::move(compiled_catalog)); + std::vector<OutputFile> implicit_outputs; if (partial_info_plist != OutputFile()) { // If "partial_info_plist" is non-empty, then add it to list of implicit // outputs of the asset catalog compilation, so that target can use it // without getting the ninja error "'foo', needed by 'bar', missing and // no known rule to make it". - out_ << " | "; - WriteOutput(partial_info_plist); + implicit_outputs.push_back(partial_info_plist); } - out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) - << GeneralTool::kGeneralToolCompileXCAssets; + std::vector<OutputFile> implicit_inputs; + implicit_inputs.push_back(input_dep); + implicit_inputs.insert(implicit_inputs.end(), implicit_deps.begin(), + implicit_deps.end()); - SourceFileSet asset_catalog_bundles; - for (const auto& source : target_->bundle_data().assets_catalog_sources()) { - out_ << " "; - path_output_.WriteFile(out_, source); - asset_catalog_bundles.insert(source); - } - - out_ << " | "; - path_output_.WriteFile(out_, input_dep); - path_output_.WriteFiles(out_, implicit_deps); - - if (!order_only_deps.empty()) { - out_ << " ||"; - path_output_.WriteFiles(out_, order_only_deps); - } - - out_ << std::endl; - - out_ << " product_type = " << target_->bundle_data().product_type() - << std::endl; + std::vector<NinjaVariable> edge_vars; + edge_vars.emplace_back("product_type", target_->bundle_data().product_type()); if (partial_info_plist != OutputFile()) { - out_ << " partial_info_plist = "; - path_output_.WriteFile(out_, partial_info_plist); - out_ << std::endl; + std::ostringstream ss; + path_output_.WriteFile(ss, partial_info_plist); + edge_vars.emplace_back("partial_info_plist", ss.str()); } const std::vector<SubstitutionPattern>& flags = target_->bundle_data().xcasset_compiler_flags().list(); if (!flags.empty()) { - out_ << " " << SubstitutionXcassetsCompilerFlags.ninja_name << " ="; + std::ostringstream ss; EscapeOptions args_escape_options; args_escape_options.mode = ESCAPE_NINJA_COMMAND; for (const auto& flag : flags) { - out_ << " "; + ss << " "; SubstitutionWriter::WriteWithNinjaVariables(flag, args_escape_options, - out_); + ss); } - out_ << std::endl; + edge_vars.emplace_back(SubstitutionXcassetsCompilerFlags.ninja_name, + ss.str()); } + + AddEdge(NinjaBuildEdge{ + .rule = GetNinjaRulePrefixForToolchain(settings_) + + GeneralTool::kGeneralToolCompileXCAssets, + .outputs = {compiled_catalog}, + .implicit_outputs = std::move(implicit_outputs), + .explicit_inputs = + ToOutputFiles(target_->bundle_data().assets_catalog_sources()), + .implicit_inputs = std::move(implicit_inputs), + .order_only_inputs = order_only_deps, + .edge_vars = std::move(edge_vars), + }); } OutputFile @@ -358,17 +342,18 @@ GeneralTool::kGeneralToolStamp; } - out_ << "build "; - WriteOutput(xcassets_input_stamp_or_phony); - out_ << ": " << tool; - + std::vector<OutputFile> explicit_inputs; for (const Target* target : dependencies) { if (target->has_dependency_output()) { - out_ << " "; - path_output_.WriteFile(out_, target->dependency_output()); + explicit_inputs.push_back(target->dependency_output()); } } - out_ << std::endl; + + AddEdge(NinjaBuildEdge{ + .rule = tool, + .outputs = {xcassets_input_stamp_or_phony}, + .explicit_inputs = std::move(explicit_inputs), + }); return xcassets_input_stamp_or_phony; } @@ -385,22 +370,21 @@ output_files); DCHECK(!post_processing_input_stamp_file.value().empty()); - out_ << "build"; std::vector<OutputFile> post_processing_output_files; SubstitutionWriter::GetListAsOutputFiles( settings_, target_->bundle_data().post_processing_outputs(), &post_processing_output_files); - WriteOutputs(post_processing_output_files); // Since the post-processing step depends on all the files from the bundle, // the create_bundle stamp can just depends on the output of the signature // script (dependencies are transitive). - *output_files = std::move(post_processing_output_files); + *output_files = post_processing_output_files; - out_ << ": " << post_processing_rule_name; - out_ << " | "; - path_output_.WriteFile(out_, post_processing_input_stamp_file); - out_ << std::endl; + AddEdge(NinjaBuildEdge{ + .rule = post_processing_rule_name, + .outputs = std::move(post_processing_output_files), + .implicit_inputs = {post_processing_input_stamp_file}, + }); } OutputFile @@ -444,22 +428,12 @@ GeneralTool::kGeneralToolStamp; } - out_ << "build "; - WriteOutput(stamp_or_phony); - out_ << ": " << tool; - - for (const SourceFile& source : post_processing_input_files) { - out_ << " "; - path_output_.WriteFile(out_, source); - } - if (!implicit_deps.empty()) { - out_ << " |"; - path_output_.WriteFiles(out_, implicit_deps); - } - if (!order_only_deps.empty()) { - out_ << " ||"; - path_output_.WriteFiles(out_, order_only_deps); - } - out_ << std::endl; + AddEdge(NinjaBuildEdge{ + .rule = tool, + .outputs = {stamp_or_phony}, + .explicit_inputs = ToOutputFiles(post_processing_input_files), + .implicit_inputs = implicit_deps, + .order_only_inputs = order_only_deps, + }); return stamp_or_phony; }
diff --git a/src/gn/ninja_create_bundle_target_writer.h b/src/gn/ninja_create_bundle_target_writer.h index 7d603a0..6c4d746 100644 --- a/src/gn/ninja_create_bundle_target_writer.h +++ b/src/gn/ninja_create_bundle_target_writer.h
@@ -15,7 +15,7 @@ NinjaCreateBundleTargetWriter(const Target* target, std::ostream& out); ~NinjaCreateBundleTargetWriter() override; - void Run() override; + void GenerateRules() override; private: // Writes the Ninja rule for invoking the post-processing script.
diff --git a/src/gn/ninja_create_bundle_target_writer_unittest.cc b/src/gn/ninja_create_bundle_target_writer_unittest.cc index 2d23969..d0b9d02 100644 --- a/src/gn/ninja_create_bundle_target_writer_unittest.cc +++ b/src/gn/ninja_create_bundle_target_writer_unittest.cc
@@ -77,13 +77,17 @@ const char expected[] = "build phony/baz/bar.inputdeps: phony || phony/foo/bar " "phony/foo/data\n" + "\n" "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data " "../../foo/input1.txt | phony/baz/bar.inputdeps\n" + "\n" "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data " "../../foo/input2.txt | phony/baz/bar.inputdeps\n" + "\n" "build phony/baz/bar: phony " "bar.bundle/Contents/Resources/input1.txt " "bar.bundle/Contents/Resources/input2.txt\n" + "\n" "build bar.bundle: phony phony/baz/bar\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -125,13 +129,17 @@ const char expected[] = "build phony/baz/bar.inputdeps: phony || phony/foo/bar " "phony/foo/data\n" + "\n" "build gen/bar.bundle/Contents/Resources/input1.txt: copy_bundle_data " "../../foo/input1.txt | phony/baz/bar.inputdeps\n" + "\n" "build gen/bar.bundle/Contents/Resources/input2.txt: copy_bundle_data " "../../foo/input2.txt | phony/baz/bar.inputdeps\n" + "\n" "build phony/baz/bar: phony " "gen/bar.bundle/Contents/Resources/input1.txt " "gen/bar.bundle/Contents/Resources/input2.txt\n" + "\n" "build gen/bar.bundle: phony phony/baz/bar\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -164,8 +172,10 @@ const char expected[] = "build baz/bar/bar_partial_info.plist: stamp || phony/foo/bar\n" + "\n" "build phony/baz/bar: phony " "baz/bar/bar_partial_info.plist || phony/foo/bar\n" + "\n" "build bar.bundle: phony phony/baz/bar\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -229,12 +239,15 @@ const char expected[] = "build phony/baz/bar.inputdeps: phony || phony/foo/bar " "phony/foo/data\n" + "\n" "build bar.bundle/Contents/Resources/Assets.car: compile_xcassets " "../../foo/Foo.xcassets | phony/foo/data phony/baz/bar.inputdeps\n" " product_type = com.apple.product-type\n" " xcasset_compiler_flags = --app-icon foo\n" + "\n" "build phony/baz/bar: phony " "bar.bundle/Contents/Resources/Assets.car\n" + "\n" "build bar.bundle: phony phony/baz/bar\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -270,6 +283,7 @@ const char expected[] = "build phony/baz/bar: phony || phony/foo/action\n" + "\n" "build bar.bundle: phony phony/baz/bar\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -392,15 +406,20 @@ "build phony/baz/bar.inputdeps: phony || phony/biz/assets " "phony/foo/assets phony/foo/bar phony/foo/data " "phony/qux/info_plist phony/quz/assets\n" + "\n" "build bar.bundle/Contents/Info.plist: copy_bundle_data " "../../qux/qux-Info.plist | phony/baz/bar.inputdeps\n" + "\n" "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data " "../../foo/input1.txt | phony/baz/bar.inputdeps\n" + "\n" "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data " "../../foo/input2.txt | phony/baz/bar.inputdeps\n" + "\n" "build phony/baz/bar.xcassets.inputdeps: phony " "phony/foo/assets " "phony/quz/assets phony/biz/assets\n" + "\n" "build bar.bundle/Contents/Resources/Assets.car | " "baz/bar/bar_partial_info.plist: compile_xcassets " "../../foo/Foo.xcassets ../../quz/Quz.xcassets " @@ -408,12 +427,14 @@ "phony/baz/bar.inputdeps\n" " product_type = com.apple.product-type\n" " partial_info_plist = baz/bar/bar_partial_info.plist\n" + "\n" "build phony/baz/bar: phony " "bar.bundle/Contents/Info.plist " "bar.bundle/Contents/Resources/input1.txt " "bar.bundle/Contents/Resources/input2.txt " "bar.bundle/Contents/Resources/Assets.car " "baz/bar/bar_partial_info.plist\n" + "\n" "build bar.bundle: phony phony/baz/bar\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -471,29 +492,36 @@ writer.Run(); const char expected[] = - "build phony/baz/bar.inputdeps: phony || ./quz phony/foo/bar " - "phony/foo/data\n" "rule __baz_bar___toolchain_default__post_processing_rule\n" " command = ../../build/codesign.py -b=quz bar.bundle\n" " description = POST PROCESSING //baz:bar(//toolchain:default)\n" " restat = 1\n" "\n" + "\n" + "build phony/baz/bar.inputdeps: phony || ./quz phony/foo/bar " + "phony/foo/data\n" + "\n" "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data " "../../foo/input1.txt | phony/baz/bar.inputdeps\n" + "\n" "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data " "../../foo/input2.txt | phony/baz/bar.inputdeps\n" + "\n" "build phony/baz/bar.postprocessing.inputdeps: phony " "../../build/codesign.py " "quz " "bar.bundle/Contents/Resources/input1.txt " "bar.bundle/Contents/Resources/input2.txt | " "phony/baz/bar.inputdeps\n" + "\n" "build bar.bundle/Contents/quz bar.bundle/_CodeSignature/CodeResources: " "__baz_bar___toolchain_default__post_processing_rule " "| phony/baz/bar.postprocessing.inputdeps\n" + "\n" "build phony/baz/bar: phony " "bar.bundle/Contents/quz " "bar.bundle/_CodeSignature/CodeResources ./quz\n" + "\n" "build bar.bundle: phony phony/baz/bar\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); @@ -554,32 +582,39 @@ writer.Run(); const char expected[] = - "build toolchain/phony/baz/bar.inputdeps: phony || ./quz " - "toolchain/phony/foo/bar " - "toolchain/phony/foo/data\n" "rule __baz_bar___toolchain_default__post_processing_rule\n" " command = ../../build/codesign.py -b=quz bar.bundle\n" " description = POST PROCESSING //baz:bar(//toolchain:default)\n" " restat = 1\n" "\n" + "\n" + "build toolchain/phony/baz/bar.inputdeps: phony || ./quz " + "toolchain/phony/foo/bar " + "toolchain/phony/foo/data\n" + "\n" "build bar.bundle/Contents/Resources/input1.txt: " "toolchain_copy_bundle_data " "../../foo/input1.txt | toolchain/phony/baz/bar.inputdeps\n" + "\n" "build bar.bundle/Contents/Resources/input2.txt: " "toolchain_copy_bundle_data " "../../foo/input2.txt | toolchain/phony/baz/bar.inputdeps\n" + "\n" "build toolchain/phony/baz/bar.postprocessing.inputdeps: phony " "../../build/codesign.py " "quz " "bar.bundle/Contents/Resources/input1.txt " "bar.bundle/Contents/Resources/input2.txt | " "toolchain/phony/baz/bar.inputdeps\n" + "\n" "build bar.bundle/Contents/quz bar.bundle/_CodeSignature/CodeResources: " "__baz_bar___toolchain_default__post_processing_rule " "| toolchain/phony/baz/bar.postprocessing.inputdeps\n" + "\n" "build toolchain/phony/baz/bar: phony " "bar.bundle/Contents/quz " "bar.bundle/_CodeSignature/CodeResources ./quz\n" + "\n" "build bar.bundle: phony toolchain/phony/baz/bar\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str);
diff --git a/src/gn/ninja_file.cc b/src/gn/ninja_file.cc new file mode 100644 index 0000000..cacb7f6 --- /dev/null +++ b/src/gn/ninja_file.cc
@@ -0,0 +1,127 @@ +// Copyright 2026 The GN 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/ninja_file.h" + +#include <ostream> +#include <string> + +#include "base/logging.h" +#include "gn/escape.h" +#include "gn/string_output_buffer.h" + +namespace { + +void WriteOutputFile(std::ostream& out, const OutputFile& file) { + EscapeOptions opts; + opts.mode = ESCAPE_NINJA; + out << " "; + EscapeStringToStream(out, file.value(), opts); +} + +void WriteVariable(std::ostream& out, + std::string_view name, + const std::string& value, + bool indent = false) { + if (indent) + out << " "; + out << name << " ="; + if (!value.empty()) { + if (!value.starts_with(' ')) + out << " "; + out << value; + } + out << '\n'; +} + +void WriteRules(std::ostream& out, const std::vector<std::string>& rules) { + if (!rules.empty()) { + for (const auto& rule : rules) { + out << rule << '\n'; + } + out << '\n'; + } +} + +} // namespace + +void NinjaFile::AddTargetGroup(NinjaTargetGroup group) { + targets.push_back(std::move(group)); +} + +void NinjaFile::Hoist() { + if (targets.empty()) + return; + + if (targets.size() == 1) { + file_vars = std::move(targets.front().target_vars); + targets.front().target_vars.clear(); + return; + } + + CHECK(false) << "Multiple targets per ninja file not yet implemented"; +} + +void NinjaFile::Serialize(std::ostream& out) { + Hoist(); + + for (const auto& var : file_vars) { + WriteVariable(out, var.name, var.value, /*indent=*/false); + } + if (!file_vars.empty()) + out << "\n\n"; + + WriteRules(out, custom_rules); + + for (size_t i = 0; i < targets.size(); ++i) { + if (i > 0) + out << "\n\n"; + const auto& target = targets[i]; + WriteRules(out, target.custom_rules); + for (size_t e = 0; e < target.edges.size(); ++e) { + if (e > 0) + out << "\n"; + const auto& edge = target.edges[e]; + out << "build"; + for (const auto& output : edge.outputs) + WriteOutputFile(out, output); + if (!edge.implicit_outputs.empty()) { + out << " |"; + for (const auto& out_file : edge.implicit_outputs) + WriteOutputFile(out, out_file); + } + out << ": " << edge.rule; + for (const auto& input : edge.explicit_inputs) + WriteOutputFile(out, input); + if (!edge.implicit_inputs.empty()) { + out << " |"; + for (const auto& in_file : edge.implicit_inputs) + WriteOutputFile(out, in_file); + } + if (!edge.order_only_inputs.empty()) { + out << " ||"; + for (const auto& in_file : edge.order_only_inputs) + WriteOutputFile(out, in_file); + } + if (!edge.validation_inputs.empty()) { + out << " |@"; + for (const auto& in_file : edge.validation_inputs) + WriteOutputFile(out, in_file); + } + out << "\n"; + + for (const auto& var : target.target_vars) { + WriteVariable(out, var.name, var.value, /*indent=*/true); + } + for (const auto& var : edge.edge_vars) { + WriteVariable(out, var.name, var.value, /*indent=*/true); + } + } + } +} + +void NinjaFile::Serialize(StringOutputBuffer& out) { + std::ostream os(&out); + Serialize(os); +}
diff --git a/src/gn/ninja_file.h b/src/gn/ninja_file.h new file mode 100644 index 0000000..c2b3de9 --- /dev/null +++ b/src/gn/ninja_file.h
@@ -0,0 +1,85 @@ +// Copyright 2026 The GN Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TOOLS_GN_NINJA_FILE_H_ +#define TOOLS_GN_NINJA_FILE_H_ + +#include <iosfwd> +#include <string> +#include <string_view> +#include <vector> + +#include "gn/output_file.h" + +class StringOutputBuffer; +class Target; + +// A single variable assignment in Ninja syntax (e.g. cflags = -fPIC). +struct NinjaVariable { + std::string_view name; + std::string value; + + bool operator==(const NinjaVariable& other) const = default; +}; + +// Represents a single 'build <outputs>: <rule> <inputs>' edge. +struct NinjaBuildEdge { + std::string rule; + std::vector<OutputFile> outputs; + std::vector<OutputFile> implicit_outputs = {}; + + std::vector<OutputFile> explicit_inputs = {}; + std::vector<OutputFile> implicit_inputs = {}; + std::vector<OutputFile> order_only_inputs = {}; + std::vector<OutputFile> validation_inputs = {}; + + // Variables specific to this edge (e.g. source_file_part, source_name_part). + std::vector<NinjaVariable> edge_vars = {}; + + // Whether the outputs of this edge are target outputs for + // --ide=ninja_outputs. Intermediate input dependency stamps/phonies are not + // target outputs. See https://gn.issues.chromium.org/448860851. + bool is_target_output = true; +}; + +// Represents a group of build edges and shared variables for a target. +struct NinjaTargetGroup { + const Target* target = nullptr; + + // Custom rule definitions specific to this target (e.g. for action targets). + std::vector<std::string> custom_rules; + + // Variables shared across all edges in this target (e.g. cflags, defines, + // target_out_dir). + std::vector<NinjaVariable> target_vars; + + // Compilation, link, or action build edges for this target. + std::vector<NinjaBuildEdge> edges; +}; + +// Represents a complete Ninja build file. +class NinjaFile { + public: + // Top-level / hoisted variables. + std::vector<NinjaVariable> file_vars; + + // Custom rule definitions. + std::vector<std::string> custom_rules; + + // Target groups contained in this file. + std::vector<NinjaTargetGroup> targets; + + // Adds a target group to this file. + void AddTargetGroup(NinjaTargetGroup group); + + // Serializes the Ninja AST to an output stream or StringOutputBuffer. + void Serialize(std::ostream& out); + void Serialize(StringOutputBuffer& out); + + private: + // Hoists identical target variables across all targets into file_vars. + void Hoist(); +}; + +#endif // TOOLS_GN_NINJA_FILE_H_
diff --git a/src/gn/ninja_generated_file_target_writer.cc b/src/gn/ninja_generated_file_target_writer.cc index 6de8207..9fd07f2 100644 --- a/src/gn/ninja_generated_file_target_writer.cc +++ b/src/gn/ninja_generated_file_target_writer.cc
@@ -20,7 +20,7 @@ NinjaGeneratedFileTargetWriter::~NinjaGeneratedFileTargetWriter() = default; -void NinjaGeneratedFileTargetWriter::Run() { +void NinjaGeneratedFileTargetWriter::GenerateRules() { // Write the file. GenerateFile();
diff --git a/src/gn/ninja_generated_file_target_writer.h b/src/gn/ninja_generated_file_target_writer.h index 3103388..dc18fe2 100644 --- a/src/gn/ninja_generated_file_target_writer.h +++ b/src/gn/ninja_generated_file_target_writer.h
@@ -13,7 +13,7 @@ NinjaGeneratedFileTargetWriter(const Target* target, std::ostream& out); ~NinjaGeneratedFileTargetWriter() override; - void Run() override; + void GenerateRules() override; private: void GenerateFile();
diff --git a/src/gn/ninja_group_target_writer.cc b/src/gn/ninja_group_target_writer.cc index 7db1a3a..c936a12 100644 --- a/src/gn/ninja_group_target_writer.cc +++ b/src/gn/ninja_group_target_writer.cc
@@ -16,7 +16,7 @@ NinjaGroupTargetWriter::~NinjaGroupTargetWriter() = default; -void NinjaGroupTargetWriter::Run() { +void NinjaGroupTargetWriter::GenerateRules() { // A group rule just generates a phony target with dependencies on each of // the deps and data_deps in the group. std::vector<OutputFile> output_files;
diff --git a/src/gn/ninja_group_target_writer.h b/src/gn/ninja_group_target_writer.h index 7a3f211..663cc45 100644 --- a/src/gn/ninja_group_target_writer.h +++ b/src/gn/ninja_group_target_writer.h
@@ -13,7 +13,7 @@ NinjaGroupTargetWriter(const Target* target, std::ostream& out); ~NinjaGroupTargetWriter() override; - void Run() override; + void GenerateRules() override; private: NinjaGroupTargetWriter(const NinjaGroupTargetWriter&) = delete;
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc index a8bea8f..d0651bf 100644 --- a/src/gn/ninja_rust_binary_target_writer.cc +++ b/src/gn/ninja_rust_binary_target_writer.cc
@@ -27,21 +27,21 @@ return opts; } -void WriteVar(const char* name, +void WriteVar(std::string_view name, const std::string& value, EscapeOptions opts, - std::ostream& out) { - out << name << " = "; - EscapeStringToStream(out, value, opts); - out << std::endl; + std::vector<NinjaVariable>& target_vars) { + std::ostringstream ss; + EscapeStringToStream(ss, value, opts); + target_vars.emplace_back(name, ss.str()); } void WriteCrateVars(const Target* target, const Tool* tool, EscapeOptions opts, - std::ostream& out) { + std::vector<NinjaVariable>& target_vars) { WriteVar(kRustSubstitutionCrateName.ninja_name, - target->rust_values().crate_name(), opts, out); + target->rust_values().crate_name(), opts, target_vars); std::string crate_type; switch (target->rust_values().crate_type()) { @@ -86,16 +86,17 @@ default: NOTREACHED(); } - WriteVar(kRustSubstitutionCrateType.ninja_name, crate_type, opts, out); + WriteVar(kRustSubstitutionCrateType.ninja_name, crate_type, opts, + target_vars); WriteVar(SubstitutionOutputExtension.ninja_name, SubstitutionWriter::GetLinkerSubstitution( target, tool, &SubstitutionOutputExtension), - opts, out); + opts, target_vars); WriteVar(SubstitutionOutputDir.ninja_name, SubstitutionWriter::GetLinkerSubstitution(target, tool, &SubstitutionOutputDir), - opts, out); + opts, target_vars); } } // namespace @@ -109,7 +110,7 @@ // TODO(juliehockett): add inherited library support? and IsLinkable support? // for c-cross-compat -void NinjaRustBinaryTargetWriter::Run() { +void NinjaRustBinaryTargetWriter::GenerateRules() { DCHECK(target_->output_type() != Target::SOURCE_SET); size_t num_output_uses = target_->sources().size(); @@ -290,16 +291,15 @@ WriteExternsAndDeps(extern_deps, transitive_crates, rustdeps, nonrustdeps, swiftmodules); WriteSourcesAndInputs(); - WritePool(out_); } void NinjaRustBinaryTargetWriter::WriteCompilerVars() { const SubstitutionBits& subst = target_->toolchain()->substitution_bits(); EscapeOptions opts = GetFlagOptions(); - WriteCrateVars(target_, tool_, opts, out_); + WriteCrateVars(target_, tool_, opts, target_group_.target_vars); - WriteRustCompilerVars(subst, /*indent=*/false, /*always_write=*/true); + WriteRustCompilerVars(subst, /*always_write=*/true); WriteSharedVars(subst); } @@ -312,25 +312,25 @@ // because it is handled sufficiently by crate_root and the generation // of depfiles by rustc. But for those which do... for (const auto& source : target_->sources()) { - deps->push_back(OutputFile(settings_->build_settings(), source)); + deps->emplace_back(settings_->build_settings(), source); } for (const auto& data : target_->config_values().inputs()) { - deps->push_back(OutputFile(settings_->build_settings(), data)); + deps->emplace_back(settings_->build_settings(), data); } } void NinjaRustBinaryTargetWriter::WriteSourcesAndInputs() { - out_ << " sources ="; + std::ostringstream val; for (const auto& source : target_->sources()) { - out_ << " "; - path_output_.WriteFile(out_, + val << " "; + path_output_.WriteFile(val, OutputFile(settings_->build_settings(), source)); } for (const auto& data : target_->config_values().inputs()) { - out_ << " "; - path_output_.WriteFile(out_, OutputFile(settings_->build_settings(), data)); + val << " "; + path_output_.WriteFile(val, OutputFile(settings_->build_settings(), data)); } - out_ << std::endl; + target_group_.edges.back().edge_vars.emplace_back("sources", val.str()); } void NinjaRustBinaryTargetWriter::WriteExternsAndDeps( @@ -339,24 +339,25 @@ const std::vector<OutputFile>& rustdeps, const std::vector<OutputFile>& nonrustdeps, const std::vector<OutputFile>& swiftmodules) { + std::ostringstream externs_val; // Writes a external LibFile which comes from user-specified externs, and may // be either a string or a SourceFile. - auto write_extern_lib_file = [this](std::string_view crate_name, - LibFile lib_file) { - out_ << " --extern "; - out_ << crate_name; - out_ << "="; + auto write_extern_lib_file = [this, &externs_val](std::string_view crate_name, + LibFile lib_file) { + externs_val << " --extern "; + externs_val << crate_name; + externs_val << "="; if (lib_file.is_source_file()) { - path_output_.WriteFile(out_, lib_file.source_file()); + path_output_.WriteFile(externs_val, lib_file.source_file()); } else { EscapeOptions escape_opts_command; escape_opts_command.mode = ESCAPE_NINJA_COMMAND; - EscapeStringToStream(out_, lib_file.value(), escape_opts_command); + EscapeStringToStream(externs_val, lib_file.value(), escape_opts_command); } }; // Writes an external OutputFile which comes from a dependency of the current // target. - auto write_extern_target = [this](const Target& dep) { + auto write_extern_target = [this, &externs_val](const Target& dep) { std::string_view crate_name; const auto& aliased_deps = target_->rust_values().aliased_deps(); if (auto it = aliased_deps.find(dep.label()); it != aliased_deps.end()) { @@ -365,15 +366,12 @@ crate_name = dep.rust_values().crate_name(); } - out_ << " --extern "; - out_ << crate_name; - out_ << "="; - path_output_.WriteFile(out_, dep.dependency_output_file()); + externs_val << " --extern "; + externs_val << crate_name; + externs_val << "="; + path_output_.WriteFile(externs_val, dep.dependency_output_file()); }; - // Write accessible crates with `--extern` to add them to the extern prelude. - out_ << " externs ="; - // Tracking to avoid emitted the same lib twice. We track it instead of // pre-emptively constructing a UniqueVector since we would have to also store // the crate name, and in the future the public-ness. @@ -418,13 +416,14 @@ } } - out_ << std::endl; - out_ << " rustdeps ="; + target_group_.edges.back().edge_vars.emplace_back("externs", + externs_val.str()); + std::ostringstream rustdeps_val; for (const SourceDir& dir : private_extern_dirs) { // TODO: switch to using `--extern priv:name` after stabilization. - out_ << " -Ldependency="; - path_output_.WriteDir(out_, dir, PathOutput::DIR_NO_LAST_SLASH); + rustdeps_val << " -Ldependency="; + path_output_.WriteDir(rustdeps_val, dir, PathOutput::DIR_NO_LAST_SLASH); } // If rustc will invoke a linker, then pass linker arguments to include those @@ -434,27 +433,30 @@ // that allows dynamic linking, as rustc may have previously put it into // static-only mode. if (nonrustdeps.size() > 0) { - out_ << " " << tool_->dynamic_link_switch(); + rustdeps_val << " " << tool_->dynamic_link_switch(); } for (const auto& nonrustdep : nonrustdeps) { - out_ << " -Clink-arg="; - path_output_.WriteFile(out_, nonrustdep); + rustdeps_val << " -Clink-arg="; + path_output_.WriteFile(rustdeps_val, nonrustdep); } // Library search paths are required to find system libraries named in #[link] // directives, which will not be specified in non-Rust native dependencies. - WriteLibrarySearchPath(out_, tool_); + WriteLibrarySearchPath(rustdeps_val, tool_); // If rustc will invoke a linker, all libraries need the passed through to the // linker. - WriteLibs(out_, tool_); - WriteFrameworks(out_, tool_); - WriteSwiftModules(out_, tool_, swiftmodules); + WriteLibs(rustdeps_val, tool_); + WriteFrameworks(rustdeps_val, tool_); + WriteSwiftModules(rustdeps_val, tool_, swiftmodules); - out_ << std::endl; - out_ << " ldflags ="; + target_group_.edges.back().edge_vars.emplace_back("rustdeps", + rustdeps_val.str()); + + std::ostringstream ldflags_val; // If rustc will invoke a linker, linker flags need to be forwarded through to // the linker. - WriteCustomLinkerFlags(out_, tool_); + WriteCustomLinkerFlags(ldflags_val, tool_); - out_ << std::endl; + target_group_.edges.back().edge_vars.emplace_back("ldflags", + ldflags_val.str()); }
diff --git a/src/gn/ninja_rust_binary_target_writer.h b/src/gn/ninja_rust_binary_target_writer.h index 83e1203..0b08b0d 100644 --- a/src/gn/ninja_rust_binary_target_writer.h +++ b/src/gn/ninja_rust_binary_target_writer.h
@@ -17,7 +17,7 @@ NinjaRustBinaryTargetWriter(const Target* target, std::ostream& out); ~NinjaRustBinaryTargetWriter() override; - void Run() override; + void GenerateRules() override; private: struct ExternCrate {
diff --git a/src/gn/ninja_rust_binary_target_writer_unittest.cc b/src/gn/ninja_rust_binary_target_writer_unittest.cc index 3f20c5a..201e1e7 100644 --- a/src/gn/ninja_rust_binary_target_writer_unittest.cc +++ b/src/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -56,8 +56,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -65,6 +65,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/input3.rs " "../../foo/main.rs\n" " source_file_part = main.rs\n" @@ -112,7 +113,7 @@ "crate_name = privatecrate\n" "crate_type = rlib\n" "output_extension = .rlib\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -120,6 +121,7 @@ "target_out_dir = obj/baz\n" "target_output_name = libprivatelib\n" "\n" + "\n" "build obj/baz/libprivatelib.rlib: rust_rlib ../../baz/lib.rs | " "../../baz/privatelib.rs ../../baz/lib.rs\n" " source_file_part = lib.rs\n" @@ -154,7 +156,7 @@ "crate_name = farcrate\n" "crate_type = rlib\n" "output_extension = .rlib\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -162,6 +164,7 @@ "target_out_dir = obj/far\n" "target_output_name = libfarlib\n" "\n" + "\n" "build obj/far/libfarlib.rlib: rust_rlib ../../far/lib.rs | " "../../far/farlib.rs ../../far/lib.rs\n" " source_file_part = lib.rs\n" @@ -196,7 +199,7 @@ "crate_name = publiccrate\n" "crate_type = rlib\n" "output_extension = .rlib\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -204,6 +207,7 @@ "target_out_dir = obj/bar\n" "target_output_name = libpubliclib\n" "\n" + "\n" "build obj/bar/libpubliclib.rlib: rust_rlib ../../bar/lib.rs | " "../../bar/publiclib.rs ../../bar/lib.rs obj/far/libfarlib.rlib\n" " source_file_part = lib.rs\n" @@ -251,8 +255,8 @@ const char expected[] = "crate_name = main_crate\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -260,6 +264,7 @@ "target_out_dir = obj/main\n" "target_output_name = main\n" "\n" + "\n" "build ./main_crate: rust_bin ../../main/main.rs | " "../../main/source.rs ../../main/main.rs obj/foo/libdirect.rlib " "obj/bar/libpubliclib.rlib obj/far/libfarlib.rlib " @@ -305,7 +310,7 @@ "crate_name = private_inside\n" "crate_type = rlib\n" "output_extension = .rlib\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -313,6 +318,7 @@ "target_out_dir = obj/faz\n" "target_output_name = libprivate_inside\n" "\n" + "\n" "build obj/faz/libprivate_inside.rlib: rust_rlib ../../faz/lib.rs | " "../../faz/private_inside.rs ../../faz/lib.rs\n" " source_file_part = lib.rs\n" @@ -346,7 +352,7 @@ "crate_name = inside\n" "crate_type = rlib\n" "output_extension = .rlib\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -354,6 +360,7 @@ "target_out_dir = obj/baz\n" "target_output_name = libinside\n" "\n" + "\n" "build obj/baz/libinside.rlib: rust_rlib ../../baz/lib.rs | " "../../baz/inside.rs ../../baz/lib.rs\n" " source_file_part = lib.rs\n" @@ -390,7 +397,7 @@ "crate_name = mylib\n" "crate_type = dylib\n" "output_extension = .so\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -398,6 +405,7 @@ "target_out_dir = obj/bar\n" "target_output_name = libmylib\n" "\n" + "\n" "build obj/bar/libmylib.so: rust_dylib ../../bar/lib.rs | " "../../bar/mylib.rs ../../bar/lib.rs " "obj/baz/libinside.rlib obj/faz/libprivate_inside.rlib\n" @@ -462,8 +470,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -471,6 +479,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs " "../../foo/main.rs obj/foo/libdirect.so obj/bar/libmylib.so " "obj/baz/libinside.rlib\n" @@ -515,7 +524,7 @@ "crate_name = mymacro\n" "crate_type = proc-macro\n" "output_extension = .so\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -523,6 +532,7 @@ "target_out_dir = obj/bar\n" "target_output_name = libmymacro\n" "\n" + "\n" "build obj/bar/libmymacro.so: rust_macro ../../bar/lib.rs | " "../../bar/mylib.rs ../../bar/lib.rs\n" " source_file_part = lib.rs\n" @@ -575,7 +585,7 @@ "crate_name = mylib\n" "crate_type = rlib\n" "output_extension = .rlib\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -583,6 +593,7 @@ "target_out_dir = obj/bar\n" "target_output_name = libmylib\n" "\n" + "\n" "build obj/bar/libmylib.rlib: rust_rlib ../../bar/lib.rs | " "../../bar/mylib.rs ../../bar/lib.rs obj/bar/libmymacro.so || " "obj/bar/libmymacro.so\n" @@ -617,8 +628,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -626,6 +637,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | " "../../foo/source.rs ../../foo/main.rs " "obj/bar/libmylib.rlib obj/bar/libmymacro.so\n" @@ -702,8 +714,8 @@ const char expected[] = "crate_name = f_crate\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -711,6 +723,7 @@ "target_out_dir = obj/foo\n" "target_output_name = f\n" "\n" + "\n" "build ./f_crate: rust_bin ../../foo/main.rs | ../../foo/main.rs " "obj/foo/b.b.o || phony/foo/b.linkdeps\n" " source_file_part = main.rs\n" @@ -792,8 +805,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -801,6 +814,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs " "../../foo/main.rs obj/bar/libdirect.rlib obj/baz/libmylib.rlib " "obj/faz/libtransitive.rlib\n" @@ -895,8 +909,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -904,6 +918,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs " "../../foo/main.rs obj/baz/sourceset.csourceset.o " "obj/bar/libmylib.rlib " @@ -942,8 +957,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -951,6 +966,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs " "../../foo/main.rs obj/foo/libstatic.a\n" " source_file_part = main.rs\n" @@ -984,7 +1000,7 @@ "crate_name = baz\n" "crate_type = staticlib\n" "output_extension = .a\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -992,6 +1008,7 @@ "target_out_dir = obj/baz\n" "target_output_name = libbaz\n" "\n" + "\n" "build obj/baz/libbaz.a: rust_staticlib ../../baz/lib.rs | " "../../baz/lib.rs " "obj/foo/libstatic.a\n" @@ -1155,8 +1172,8 @@ const char expected[] = "crate_name = exe\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1164,6 +1181,7 @@ "target_out_dir = obj/exe\n" "target_output_name = exe\n" "\n" + "\n" "build ./exe: rust_bin ../../exe/main.rs | " "../../exe/main.rs " "obj/pub_sset_in_staticlib/pub_sset_in_staticlib.lib.o " @@ -1231,6 +1249,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar.exe: rust_bin ../../foo/main.rs | ../../foo/input3.rs " "../../foo/main.rs\n" " source_file_part = main.rs\n" @@ -1289,7 +1308,7 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" + "output_extension =\n" "output_dir = foo\n" "rustflags =\n" "rustenv =\n" @@ -1298,6 +1317,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/input.rs " "../../foo/main.rs obj/bar/librlib.rlib\n" " source_file_part = main.rs\n" @@ -1390,7 +1410,7 @@ "crate_name = rlibcrate\n" "crate_type = rlib\n" "output_extension = .rlib\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1398,6 +1418,7 @@ "target_out_dir = obj/foo\n" "target_output_name = librlibcrate\n" "\n" + "\n" "build obj/foo/librlibcrate.rlib: rust_rlib ../../foo/input.rs | " "../../foo/input.rs obj/bar/libpubliclib.rlib obj/clib/libstatic.a\n" " source_file_part = input.rs\n" @@ -1474,7 +1495,7 @@ "crate_name = mymacro\n" "crate_type = proc-macro\n" "output_extension = .so\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1482,6 +1503,7 @@ "target_out_dir = obj/bar\n" "target_output_name = libmymacro\n" "\n" + "\n" "build obj/bar/libmymacro.so: rust_macro ../../bar/lib.rs | " "../../bar/mylib.rs ../../bar/lib.rs " "obj/baz/public/libmymacropublicdep.rlib " @@ -1520,8 +1542,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1529,6 +1551,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs " "../../foo/main.rs obj/bar/libmymacro.so\n" " source_file_part = main.rs\n" @@ -1567,7 +1590,7 @@ "crate_name = mylib\n" "crate_type = rlib\n" "output_extension = .rlib\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1575,6 +1598,7 @@ "target_out_dir = obj/bar\n" "target_output_name = libmylib\n" "\n" + "\n" "build obj/bar/libmylib.rlib: rust_rlib ../../bar/lib.rs | " "../../bar/mylib.rs ../../bar/lib.rs\n" " source_file_part = lib.rs\n" @@ -1615,8 +1639,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1624,6 +1648,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | " "../../foo/source.rs ../../foo/main.rs obj/bar/libmylib.rlib || " "obj/bar/libmylib.rlib\n" @@ -1670,8 +1695,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1679,6 +1704,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs " "../../foo/main.rs ../../foo/lib1.rlib\n" " source_file_part = main.rs\n" @@ -1717,12 +1743,10 @@ writer.Run(); const char expected[] = - "build phony/foo/bar.inputs: phony ../../foo/config.json " - "../../foo/template.h\n" "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1730,6 +1754,10 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" + "build phony/foo/bar.inputs: phony ../../foo/config.json " + "../../foo/template.h\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs " "../../foo/main.rs ../../foo/config.json ../../foo/template.h " "|| phony/foo/bar.inputs\n" @@ -1767,7 +1795,7 @@ "crate_name = mylib\n" "crate_type = cdylib\n" "output_extension = .so\n" - "output_dir = \n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1775,6 +1803,7 @@ "target_out_dir = obj/bar\n" "target_output_name = libmylib\n" "\n" + "\n" "build obj/bar/libmylib.so: rust_cdylib ../../bar/lib.rs | " "../../bar/lib.rs\n" " source_file_part = lib.rs\n" @@ -1807,8 +1836,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1816,6 +1845,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs " "../../foo/main.rs obj/bar/libmylib.so\n" " source_file_part = main.rs\n" @@ -1884,8 +1914,8 @@ const char expected[] = "crate_name = foo_bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1893,6 +1923,7 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/main.rs " "obj/bar/libmylib.rlib ./libshared.so ./libimplicit.so\n" " source_file_part = main.rs\n" @@ -1978,8 +2009,8 @@ const char expected[] = "crate_name = exe\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -1987,6 +2018,7 @@ "target_out_dir = obj/linked\n" "target_output_name = exe\n" "\n" + "\n" "build ./exe: rust_bin ../../linked/exe.rs | ../../linked/exe.rs " "obj/sset/bar.input1.o obj/public/libbehind_sourceset_public.rlib " "obj/private/libbehind_sourceset_private.rlib || " @@ -2035,8 +2067,8 @@ const char expected[] = "crate_name = bar\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -2044,14 +2076,15 @@ "target_out_dir = obj/foo\n" "target_output_name = bar\n" "\n" + "\n" "build ./bar: rust_bin ../../foo/source.rs | ../../foo/source.rs\n" " source_file_part = source.rs\n" " source_name_part = source\n" + " pool = foo_pool\n" " externs =\n" " rustdeps =\n" " ldflags =\n" - " sources = ../../foo/source.rs\n" - " pool = foo_pool\n"; + " sources = ../../foo/source.rs\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str); } @@ -2108,8 +2141,8 @@ const char expected[] = "crate_name = exe\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -2117,6 +2150,7 @@ "target_out_dir = obj/linked\n" "target_output_name = exe\n" "\n" + "\n" "build ./exe: rust_bin ../../linked/exe.rs | ../../linked/exe.rs || " "phony/bar/framework\n" " source_file_part = exe.rs\n" @@ -2166,8 +2200,8 @@ const char expected[] = "crate_name = exe\n" "crate_type = bin\n" - "output_extension = \n" - "output_dir = \n" + "output_extension =\n" + "output_dir =\n" "rustflags =\n" "rustenv =\n" "root_out_dir = .\n" @@ -2175,6 +2209,7 @@ "target_out_dir = obj/linked\n" "target_output_name = exe\n" "\n" + "\n" "build ./exe: rust_bin ../../linked/exe.rs | ../../linked/exe.rs " "obj/foo/file1.o obj/foo/file2.o || " "phony/foo/foo obj/foo/Foo.swiftmodule phony/foo/foo.linkdeps\n"
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc index d3d1eb1..e35d39a 100644 --- a/src/gn/ninja_target_writer.cc +++ b/src/gn/ninja_target_writer.cc
@@ -7,12 +7,10 @@ #include <algorithm> #include <sstream> -#include "base/files/file_util.h" #include "base/strings/string_util.h" #include "gn/builtin_tool.h" #include "gn/c_substitution_type.h" #include "gn/config_values_extractors.h" -#include "gn/err.h" #include "gn/escape.h" #include "gn/filesystem_utils.h" #include "gn/general_tool.h" @@ -29,7 +27,6 @@ #include "gn/rust_substitution_type.h" #include "gn/scheduler.h" #include "gn/string_output_buffer.h" -#include "gn/string_utils.h" #include "gn/substitution_writer.h" #include "gn/target.h" #include "gn/trace.h" @@ -40,7 +37,55 @@ out_(out), path_output_(settings_->build_settings()->build_dir(), settings_->build_settings()->root_path_utf8(), - ESCAPE_NINJA) {} + ESCAPE_NINJA) { + target_group_.target = target; +} + +void NinjaTargetWriter::AddTargetVar(std::string_view name, std::string value) { + target_group_.target_vars.emplace_back(name, std::move(value)); +} + +void NinjaTargetWriter::AddEdge(NinjaBuildEdge edge) { + target_group_.edges.push_back(std::move(edge)); +} + +NinjaTargetGroup NinjaTargetWriter::GenerateTargetGroup() { + GenerateRules(); + return std::move(target_group_); +} + +void NinjaTargetWriter::SetNinjaOutputs( + std::vector<OutputFile>* ninja_outputs) { + ninja_outputs_ = ninja_outputs; +} + +void NinjaTargetWriter::Run() { + NinjaTargetGroup group = GenerateTargetGroup(); + if (ninja_outputs_) { + for (const auto& edge : group.edges) { + if (edge.is_target_output) { + ninja_outputs_->insert(ninja_outputs_->end(), edge.outputs.begin(), + edge.outputs.end()); + ninja_outputs_->insert(ninja_outputs_->end(), + edge.implicit_outputs.begin(), + edge.implicit_outputs.end()); + } + } + } + NinjaFile file; + file.AddTargetGroup(std::move(group)); + file.Serialize(out_); +} + +std::vector<OutputFile> NinjaTargetWriter::ToOutputFiles( + const std::vector<SourceFile>& sources) const { + std::vector<OutputFile> outputs; + outputs.reserve(sources.size()); + for (const auto& source : sources) { + outputs.emplace_back(settings_->build_settings(), source); + } + return outputs; +} void NinjaTargetWriter::SetResolvedTargetData(ResolvedTargetData* resolved) { if (resolved) { @@ -49,11 +94,6 @@ } } -void NinjaTargetWriter::SetNinjaOutputs( - std::vector<OutputFile>* ninja_outputs) { - ninja_outputs_ = ninja_outputs; -} - ResolvedTargetData* NinjaTargetWriter::GetResolvedTargetData() { return const_cast<ResolvedTargetData*>(&resolved()); } @@ -68,34 +108,6 @@ NinjaTargetWriter::~NinjaTargetWriter() = default; -void NinjaTargetWriter::WriteOutput(const OutputFile& output) const { - path_output_.WriteFile(out_, output); - if (ninja_outputs_) - ninja_outputs_->push_back(output); -} - -void NinjaTargetWriter::WriteOutput(OutputFile&& output) const { - path_output_.WriteFile(out_, output); - if (ninja_outputs_) - ninja_outputs_->push_back(std::move(output)); -} - -void NinjaTargetWriter::WriteOutputs( - const std::vector<OutputFile>& outputs) const { - path_output_.WriteFiles(out_, outputs); - if (ninja_outputs_) - ninja_outputs_->insert(ninja_outputs_->end(), outputs.begin(), - outputs.end()); -} - -void NinjaTargetWriter::WriteOutputs(std::vector<OutputFile>&& outputs) const { - path_output_.WriteFiles(out_, outputs); - if (ninja_outputs_) { - for (auto& output : outputs) - ninja_outputs_->push_back(std::move(output)); - } -} - // static std::string NinjaTargetWriter::RunAndWriteFile( const Target* target, @@ -110,10 +122,8 @@ if (g_scheduler->verbose_logging()) g_scheduler->Log("Computing", target->label().GetUserVisibleName(true)); - // It's ridiculously faster to write to a string and then write that to - // disk in one operation than to use an fstream here. - StringOutputBuffer storage; - std::ostream rules(&storage); + StringOutputBuffer dummy_storage; + std::ostream dummy_rules(&dummy_storage); // Call out to the correct sub-type of writer. Binary targets need to be // written to separate files for compiler flag scoping, but other target @@ -130,43 +140,37 @@ // Groups and actions don't use this type of flag, they make unique rules // or write variables scoped under each build line. As a result, they don't // need the separate files. + NinjaTargetGroup group; bool needs_file_write = false; if (target->output_type() == Target::BUNDLE_DATA) { - NinjaBundleDataTargetWriter writer(target, rules); + NinjaBundleDataTargetWriter writer(target, dummy_rules); writer.SetResolvedTargetData(resolved); - writer.SetNinjaOutputs(ninja_outputs); - writer.Run(); + group = writer.GenerateTargetGroup(); } else if (target->output_type() == Target::CREATE_BUNDLE) { - NinjaCreateBundleTargetWriter writer(target, rules); + NinjaCreateBundleTargetWriter writer(target, dummy_rules); writer.SetResolvedTargetData(resolved); - writer.SetNinjaOutputs(ninja_outputs); - writer.Run(); + group = writer.GenerateTargetGroup(); } else if (target->output_type() == Target::COPY_FILES) { - NinjaCopyTargetWriter writer(target, rules); + NinjaCopyTargetWriter writer(target, dummy_rules); writer.SetResolvedTargetData(resolved); - writer.SetNinjaOutputs(ninja_outputs); - writer.Run(); + group = writer.GenerateTargetGroup(); } else if (target->output_type() == Target::ACTION || target->output_type() == Target::ACTION_FOREACH) { - NinjaActionTargetWriter writer(target, rules); + NinjaActionTargetWriter writer(target, dummy_rules); writer.SetResolvedTargetData(resolved); - writer.SetNinjaOutputs(ninja_outputs); - writer.Run(); + group = writer.GenerateTargetGroup(); } else if (target->output_type() == Target::GROUP) { - NinjaGroupTargetWriter writer(target, rules); + NinjaGroupTargetWriter writer(target, dummy_rules); writer.SetResolvedTargetData(resolved); - writer.SetNinjaOutputs(ninja_outputs); - writer.Run(); + group = writer.GenerateTargetGroup(); } else if (target->output_type() == Target::GENERATED_FILE) { - NinjaGeneratedFileTargetWriter writer(target, rules); + NinjaGeneratedFileTargetWriter writer(target, dummy_rules); writer.SetResolvedTargetData(resolved); - writer.SetNinjaOutputs(ninja_outputs); - writer.Run(); + group = writer.GenerateTargetGroup(); } else if (target->IsBinary()) { needs_file_write = true; - NinjaBinaryTargetWriter writer(target, rules); + NinjaBinaryTargetWriter writer(target, dummy_rules); writer.SetResolvedTargetData(resolved); - writer.SetNinjaOutputs(ninja_outputs); if (target->module_type().test(Target::MODULEMAP_IS_GENERATED)) { const SourceFile* modulemap = target->modulemap_file(); CHECK(modulemap); @@ -187,12 +191,32 @@ *target->private_modulemap_file()); private_storage.WriteToFileIfChanged(private_path, nullptr); } - writer.Run(); + group = writer.GenerateTargetGroup(); } else { CHECK(0) << "Output type of target not handled."; } - WritePublicInputsStampOrPhony(target, resolved, rules); + if (ninja_outputs) { + for (const auto& edge : group.edges) { + if (edge.is_target_output) { + ninja_outputs->insert(ninja_outputs->end(), edge.outputs.begin(), + edge.outputs.end()); + ninja_outputs->insert(ninja_outputs->end(), + edge.implicit_outputs.begin(), + edge.implicit_outputs.end()); + } + } + } + + WritePublicInputsStampOrPhony(target, resolved, group); + + NinjaFile file; + file.AddTargetGroup(std::move(group)); + + // It's ridiculously faster to write to a string and then write that to + // disk in one operation than to use an fstream here. + StringOutputBuffer storage; + file.Serialize(storage); if (needs_file_write) { // Write the ninja file. @@ -221,7 +245,7 @@ void NinjaTargetWriter::WritePublicInputsStampOrPhony( const Target* target, ResolvedTargetData* resolved, - std::ostream& out) { + NinjaTargetGroup& group) { DCHECK(resolved); if (!resolved->ExportsPublicInputs(target)) return; @@ -231,7 +255,7 @@ std::vector<OutputFile> deps; for (const auto& file : target->public_inputs()) { - deps.push_back(OutputFile(build_settings, file)); + deps.emplace_back(build_settings, file); } for (const auto& dep : target->public_deps()) { if (resolved->ExportsPublicInputs(dep.ptr)) { @@ -239,111 +263,91 @@ } } - PathOutput path_output(build_settings->build_dir(), - build_settings->root_path_utf8(), ESCAPE_NINJA); - - out << "build "; - path_output.WriteFile(out, output); - + std::string rule; if (build_settings->no_stamp_files()) { - out << ": " << BuiltinTool::kBuiltinToolPhony; + rule = BuiltinTool::kBuiltinToolPhony; } else { - out << ": " << GetNinjaRulePrefixForToolchain(target->settings()) - << GeneralTool::kGeneralToolStamp; + rule = GetNinjaRulePrefixForToolchain(target->settings()) + + GeneralTool::kGeneralToolStamp; } - path_output.WriteFiles(out, deps); - out << std::endl << std::endl; + group.edges.push_back(NinjaBuildEdge{ + .rule = std::move(rule), + .outputs = {output}, + .explicit_inputs = std::move(deps), + .is_target_output = false, + }); } void NinjaTargetWriter::WriteEscapedSubstitution(const Substitution* type) { EscapeOptions opts; opts.mode = ESCAPE_NINJA; - out_ << type->ninja_name << " = "; + std::ostringstream val; EscapeStringToStream( - out_, SubstitutionWriter::GetTargetSubstitution(target_, type), opts); - out_ << std::endl; + val, SubstitutionWriter::GetTargetSubstitution(target_, type), opts); + target_group_.target_vars.emplace_back(type->ninja_name, val.str()); } void NinjaTargetWriter::WriteSharedVars(const SubstitutionBits& bits) { - bool written_anything = false; - // Target label. if (bits.used.count(&SubstitutionLabel)) { WriteEscapedSubstitution(&SubstitutionLabel); - written_anything = true; } // Target label name. if (bits.used.count(&SubstitutionLabelName)) { WriteEscapedSubstitution(&SubstitutionLabelName); - written_anything = true; } // Target label name without toolchain. if (bits.used.count(&SubstitutionLabelNoToolchain)) { WriteEscapedSubstitution(&SubstitutionLabelNoToolchain); - written_anything = true; } // Root gen dir. if (bits.used.count(&SubstitutionRootGenDir)) { WriteEscapedSubstitution(&SubstitutionRootGenDir); - written_anything = true; } // Root out dir. if (bits.used.count(&SubstitutionRootOutDir)) { WriteEscapedSubstitution(&SubstitutionRootOutDir); - written_anything = true; } // Target gen dir. if (bits.used.count(&SubstitutionTargetGenDir)) { WriteEscapedSubstitution(&SubstitutionTargetGenDir); - written_anything = true; } // Target out dir. if (bits.used.count(&SubstitutionTargetOutDir)) { WriteEscapedSubstitution(&SubstitutionTargetOutDir); - written_anything = true; } // Target output name. if (bits.used.count(&SubstitutionTargetOutputName)) { WriteEscapedSubstitution(&SubstitutionTargetOutputName); - written_anything = true; } - - // If we wrote any vars, separate them from the rest of the file that follows - // with a blank line. - if (written_anything) - out_ << std::endl; } -void NinjaTargetWriter::WriteCCompilerVars(const SubstitutionBits& bits, - bool indent, - bool respect_source_used) { +void NinjaTargetWriter::WriteCCompilerVars( + const SubstitutionBits& bits, + bool respect_source_used, + std::vector<NinjaVariable>& target_vars) { // Defines. if (bits.used.count(&CSubstitutionDefines)) { - if (indent) - out_ << " "; - out_ << CSubstitutionDefines.ninja_name << " ="; + std::ostringstream val; RecursiveTargetConfigToStream<std::string>(kRecursiveWriterSkipDuplicates, target_, &ConfigValues::defines, - DefineWriter(), out_); - out_ << std::endl; + DefineWriter(), val); + target_vars.emplace_back(CSubstitutionDefines.ninja_name, val.str()); } // Framework search path. if (bits.used.count(&CSubstitutionFrameworkDirs)) { const Tool* tool = target_->toolchain()->GetTool(CTool::kCToolLink); - - if (indent) - out_ << " "; - out_ << CSubstitutionFrameworkDirs.ninja_name << " ="; + std::ostringstream val; PathOutput framework_dirs_output( path_output_.current_dir(), settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND); @@ -351,22 +355,20 @@ kRecursiveWriterSkipDuplicates, target_, &ConfigValues::framework_dirs, FrameworkDirsWriter(framework_dirs_output, tool->framework_dir_switch()), - out_); - out_ << std::endl; + val); + target_vars.emplace_back(CSubstitutionFrameworkDirs.ninja_name, val.str()); } // Include directories. if (bits.used.count(&CSubstitutionIncludeDirs)) { - if (indent) - out_ << " "; - out_ << CSubstitutionIncludeDirs.ninja_name << " ="; + std::ostringstream val; PathOutput include_path_output( path_output_.current_dir(), settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND); RecursiveTargetConfigToStream<SourceDir>( kRecursiveWriterSkipDuplicates, target_, &ConfigValues::include_dirs, - IncludeWriter(include_path_output), out_); - out_ << std::endl; + IncludeWriter(include_path_output), val); + target_vars.emplace_back(CSubstitutionIncludeDirs.ninja_name, val.str()); } bool has_precompiled_headers = @@ -374,13 +376,24 @@ EscapeOptions opts; opts.mode = ESCAPE_NINJA_COMMAND; + + auto write_flag = + [&](const Substitution* subst, bool has_pch, const char* tool_name, + const std::vector<std::string>& (ConfigValues::*getter)() const) { + if (!target_->toolchain()->substitution_bits().used.count(subst)) + return; + std::ostringstream val; + WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, subst, has_pch, + tool_name, getter, opts, path_output_, val, + /*write_substitution=*/false, /*indent=*/false); + target_vars.emplace_back(subst->ninja_name, val.str()); + }; + if (respect_source_used ? target_->source_types_used().Get(SourceFile::SOURCE_S) : bits.used.count(&CSubstitutionAsmFlags)) { - WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, - &CSubstitutionAsmFlags, false, Tool::kToolNone, - &ConfigValues::asmflags, opts, path_output_, out_, true, - indent); + write_flag(&CSubstitutionAsmFlags, false, Tool::kToolNone, + &ConfigValues::asmflags); } if (respect_source_used ? (target_->source_types_used().Get(SourceFile::SOURCE_C) || @@ -389,62 +402,51 @@ target_->source_types_used().Get(SourceFile::SOURCE_MM) || target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) : bits.used.count(&CSubstitutionCFlags)) { - WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlags, - false, Tool::kToolNone, &ConfigValues::cflags, opts, - path_output_, out_, true, indent); + write_flag(&CSubstitutionCFlags, false, Tool::kToolNone, + &ConfigValues::cflags); } if (respect_source_used ? target_->source_types_used().Get(SourceFile::SOURCE_C) : bits.used.count(&CSubstitutionCFlagsC)) { - WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlagsC, - has_precompiled_headers, CTool::kCToolCc, - &ConfigValues::cflags_c, opts, path_output_, out_, true, - indent); + write_flag(&CSubstitutionCFlagsC, has_precompiled_headers, CTool::kCToolCc, + &ConfigValues::cflags_c); } if (respect_source_used ? (target_->source_types_used().Get(SourceFile::SOURCE_CPP) || target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) : bits.used.count(&CSubstitutionCFlagsCc)) { - WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, - &CSubstitutionCFlagsCc, has_precompiled_headers, - CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_, - out_, true, indent); + write_flag(&CSubstitutionCFlagsCc, has_precompiled_headers, + CTool::kCToolCxx, &ConfigValues::cflags_cc); } if (respect_source_used ? target_->source_types_used().Get(SourceFile::SOURCE_M) : bits.used.count(&CSubstitutionCFlagsObjC)) { - WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, - &CSubstitutionCFlagsObjC, has_precompiled_headers, - CTool::kCToolObjC, &ConfigValues::cflags_objc, opts, - path_output_, out_, true, indent); + write_flag(&CSubstitutionCFlagsObjC, has_precompiled_headers, + CTool::kCToolObjC, &ConfigValues::cflags_objc); } if (respect_source_used ? target_->source_types_used().Get(SourceFile::SOURCE_MM) : bits.used.count(&CSubstitutionCFlagsObjCc)) { - WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, - &CSubstitutionCFlagsObjCc, has_precompiled_headers, - CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts, - path_output_, out_, true, indent); + write_flag(&CSubstitutionCFlagsObjCc, has_precompiled_headers, + CTool::kCToolObjCxx, &ConfigValues::cflags_objcc); } if (target_->source_types_used().SwiftSourceUsed() || !respect_source_used) { if (bits.used.count(&CSubstitutionSwiftModuleName)) { - if (indent) - out_ << " "; - out_ << CSubstitutionSwiftModuleName.ninja_name << " = "; - EscapeStringToStream(out_, target_->swift_values().module_name(), opts); - out_ << std::endl; + std::ostringstream val; + EscapeStringToStream(val, target_->swift_values().module_name(), opts); + target_vars.emplace_back(CSubstitutionSwiftModuleName.ninja_name, + val.str()); } if (bits.used.count(&CSubstitutionSwiftBridgeHeader)) { - if (indent) - out_ << " "; - out_ << CSubstitutionSwiftBridgeHeader.ninja_name << " = "; + std::ostringstream val; if (!target_->swift_values().bridge_header().is_null()) { - path_output_.WriteFile(out_, target_->swift_values().bridge_header()); + path_output_.WriteFile(val, target_->swift_values().bridge_header()); } else { - out_ << R"("")"; + val << R"("")"; } - out_ << std::endl; + target_vars.emplace_back(CSubstitutionSwiftBridgeHeader.ninja_name, + val.str()); } if (bits.used.count(&CSubstitutionSwiftModuleDirs)) { @@ -454,51 +456,60 @@ for (const Target* dep : resolved().GetSwiftModuleDependencies(target_)) swiftmodule_dirs.push_back(dep->swift_values().module_output_dir()); - if (indent) - out_ << " "; - out_ << CSubstitutionSwiftModuleDirs.ninja_name << " ="; + std::ostringstream val; PathOutput swiftmodule_path_output( path_output_.current_dir(), settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND); IncludeWriter swiftmodule_path_writer(swiftmodule_path_output); for (const SourceDir& swiftmodule_dir : swiftmodule_dirs) { - swiftmodule_path_writer(swiftmodule_dir, out_); + swiftmodule_path_writer(swiftmodule_dir, val); } - out_ << std::endl; + target_vars.emplace_back(CSubstitutionSwiftModuleDirs.ninja_name, + val.str()); } + } +} - WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, - &CSubstitutionSwiftFlags, false, CTool::kCToolSwift, - &ConfigValues::swiftflags, opts, path_output_, out_, true, - indent); +void NinjaTargetWriter::WriteCCompilerVars(const SubstitutionBits& bits, + bool respect_source_used) { + WriteCCompilerVars(bits, respect_source_used, target_group_.target_vars); +} + +void NinjaTargetWriter::WriteRustCompilerVars( + const SubstitutionBits& bits, + bool always_write, + std::vector<NinjaVariable>& target_vars) { + EscapeOptions opts; + opts.mode = ESCAPE_NINJA_COMMAND; + + auto write_flag = + [&](const Substitution* subst, + const std::vector<std::string>& (ConfigValues::*getter)() const) { + std::ostringstream val; + WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, subst, false, + Tool::kToolNone, getter, opts, path_output_, val, + /*write_substitution=*/false, /*indent=*/false); + target_vars.emplace_back(subst->ninja_name, val.str()); + }; + + if (bits.used.count(&kRustSubstitutionRustFlags) || always_write) { + write_flag(&kRustSubstitutionRustFlags, &ConfigValues::rustflags); + } + + if (bits.used.count(&kRustSubstitutionRustEnv) || always_write) { + write_flag(&kRustSubstitutionRustEnv, &ConfigValues::rustenv); } } void NinjaTargetWriter::WriteRustCompilerVars(const SubstitutionBits& bits, - bool indent, bool always_write) { - EscapeOptions opts; - opts.mode = ESCAPE_NINJA_COMMAND; - - if (bits.used.count(&kRustSubstitutionRustFlags) || always_write) { - WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, - &kRustSubstitutionRustFlags, false, Tool::kToolNone, - &ConfigValues::rustflags, opts, path_output_, out_, true, - indent); - } - - if (bits.used.count(&kRustSubstitutionRustEnv) || always_write) { - WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, - &kRustSubstitutionRustEnv, false, Tool::kToolNone, - &ConfigValues::rustenv, opts, path_output_, out_, true, - indent); - } + WriteRustCompilerVars(bits, always_write, target_group_.target_vars); } NinjaTargetWriter::InputDeps NinjaTargetWriter::WriteInputDepsStampOrPhonyAndGetDep( const std::vector<const Target*>& additional_hard_deps, - size_t num_output_uses) const { + size_t num_output_uses) { CHECK(target_->toolchain()) << "Toolchain not set on target " << target_->label().GetUserVisibleName(true); @@ -606,7 +617,7 @@ // File input deps. for (const SourceFile* source : input_deps_sources) - deps.order_only.push_back(OutputFile(settings_->build_settings(), *source)); + deps.order_only.emplace_back(settings_->build_settings(), *source); // Target input deps. Sort by label so the output is deterministic (otherwise // some of the targets will have gone through std::sets which will have // sorted them by pointer). @@ -663,17 +674,15 @@ GeneralTool::kGeneralToolStamp; } - // These are not real outputs, so do not use WriteOutput() here. + // These are not real outputs, so do not mark as target output. // See https://gn.issues.chromium.org/448860851. - out_ << "build "; - path_output_.WriteFile(out_, input_stamp_or_phony); - out_ << ": " << tool; - path_output_.WriteFiles(out_, deps.implicit); - if (!deps.order_only.empty()) { - out_ << " ||"; - path_output_.WriteFiles(out_, deps.order_only); - } - out_ << "\n"; + AddEdge(NinjaBuildEdge{ + .rule = tool, + .outputs = {input_stamp_or_phony}, + .explicit_inputs = deps.implicit, + .order_only_inputs = deps.order_only, + .is_target_output = false, + }); InputDeps result; result.implicit.push_back(input_stamp_or_phony); @@ -699,35 +708,30 @@ } } + OutputFile output_file; + std::string rule; // We should have already discerned whether this target is a stamp or a phony. // If there's a dependency_output_file, it should be a stamp. Else is a phony // or omitted phony (in which case, we don't write it). if (target_->has_dependency_output_file()) { // Make a stamp target. - const OutputFile& stamp_file = target_->dependency_output_file(); + output_file = target_->dependency_output_file(); // First validate that the target's dependency is a stamp file. Otherwise, // we shouldn't have gotten here! - CHECK(base::EndsWithCaseInsensitiveASCII(stamp_file.value(), ".stamp")) + CHECK(base::EndsWithCaseInsensitiveASCII(output_file.value(), ".stamp")) << "Output should end in \".stamp\" for stamp file output. Instead " "got: " - << "\"" << stamp_file.value() << "\""; + << "\"" << output_file.value() << "\""; - out_ << "build "; - WriteOutput(stamp_file); - - out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) - << GeneralTool::kGeneralToolStamp; + rule = GetNinjaRulePrefixForToolchain(settings_) + + GeneralTool::kGeneralToolStamp; } else if (target_->has_dependency_output_alias()) { // Make a phony target. - const OutputFile& phony_target = target_->dependency_output_alias(); - CHECK(!phony_target.value().empty()); + output_file = target_->dependency_output_alias(); + CHECK(!output_file.value().empty()); - out_ << "build "; - WriteOutput(phony_target); - - out_ << ": " << BuiltinTool::kBuiltinToolPhony; - + rule = BuiltinTool::kBuiltinToolPhony; } else { // This is the omitted phony case. We should not get here if there were any // dependencies, so ensure that none got added. @@ -736,32 +740,21 @@ return; } - path_output_.WriteFiles(out_, all_files); - - if (!order_only_deps.empty()) { - out_ << " ||"; - path_output_.WriteFiles(out_, order_only_deps); - } - WriteValidations(); - out_ << std::endl; + NinjaBuildEdge edge{ + .rule = std::move(rule), + .outputs = {output_file}, + .explicit_inputs = std::move(all_files), + .order_only_inputs = order_only_deps, + }; + AddValidationInputs(edge); + AddEdge(std::move(edge)); } -void NinjaTargetWriter::WriteValidations() { - const LabelTargetVector& validations = target_->validations(); - if (validations.empty()) - return; - - bool first = true; - for (const auto& pair : validations) { +void NinjaTargetWriter::AddValidationInputs(NinjaBuildEdge& edge) const { + for (const auto& pair : target_->validations()) { // This check is needed because empty groups have no output. - if (!pair.ptr->has_dependency_output()) { - continue; + if (pair.ptr->has_dependency_output()) { + edge.validation_inputs.push_back(pair.ptr->dependency_output()); } - if (first) { - out_ << " |@"; - first = false; - } - out_ << " "; - WriteOutput(pair.ptr->dependency_output()); } }
diff --git a/src/gn/ninja_target_writer.h b/src/gn/ninja_target_writer.h index d7b2883..a29a590 100644 --- a/src/gn/ninja_target_writer.h +++ b/src/gn/ninja_target_writer.h
@@ -7,6 +7,7 @@ #include <iosfwd> +#include "gn/ninja_file.h" #include "gn/path_output.h" #include "gn/resolved_target_data.h" #include "gn/substitution_type.h" @@ -23,6 +24,12 @@ NinjaTargetWriter(const Target* target, std::ostream& out); virtual ~NinjaTargetWriter(); + NinjaTargetGroup& target_group() { return target_group_; } + const NinjaTargetGroup& target_group() const { return target_group_; } + + void AddTargetVar(std::string_view name, std::string value); + void AddEdge(NinjaBuildEdge edge); + // Returns a ResolvedTargetData that can be used to retrieve information // from targets. The instance can be set through SetResolvedTargetData() // or may be created on demand otherwise (which is useful to keep unit-tests @@ -59,11 +66,19 @@ // false. static void WritePublicInputsStampOrPhony(const Target* target, ResolvedTargetData* resolved, - std::ostream& out); + NinjaTargetGroup& group); - virtual void Run() = 0; + // Generates and returns the populated NinjaTargetGroup by value. + NinjaTargetGroup GenerateTargetGroup(); + + // Runs rule generation and serializes the target's rules to out_. + void Run(); protected: + // Generates the NinjaTargetGroup (target variables and edges) without + // serializing. + virtual void GenerateRules() = 0; + // Returns a writable pointer to resolved(). Only used internally. ResolvedTargetData* GetResolvedTargetData(); @@ -79,15 +94,18 @@ // respect the types of source code used; otherwise they will respect the bits // passed in. void WriteCCompilerVars(const SubstitutionBits& bits, - bool indent, + bool respect_source_used, + std::vector<NinjaVariable>& target_vars); + void WriteCCompilerVars(const SubstitutionBits& bits, bool respect_source_used); // Writes out the substitution values that are shared between Rust tools // and action tools. Only the substitutions identified by the given bits will // be written, unless 'always_write' is specified. void WriteRustCompilerVars(const SubstitutionBits& bits, - bool indent, - bool always_write); + bool always_write, + std::vector<NinjaVariable>& target_vars); + void WriteRustCompilerVars(const SubstitutionBits& bits, bool always_write); struct InputDeps { std::vector<OutputFile> implicit; @@ -103,7 +121,7 @@ // are passed in, this returns an empty struct. InputDeps WriteInputDepsStampOrPhonyAndGetDep( const std::vector<const Target*>& additional_hard_deps, - size_t num_output_uses) const; + size_t num_output_uses); // Writes to the output file a final phony rule for the target that aliases // the given list of files. @@ -111,23 +129,18 @@ const std::vector<OutputFile>& deps, const std::vector<OutputFile>& order_only_deps); - // Writes the validation dependencies to the output stream. - void WriteValidations(); + // Appends the target's validation dependencies to the given edge. + void AddValidationInputs(NinjaBuildEdge& edge) const; + + // Converts a list of SourceFiles to OutputFiles relative to the build dir. + std::vector<OutputFile> ToOutputFiles( + const std::vector<SourceFile>& sources) const; const Settings* settings_; // Non-owning. const Target* target_; // Non-owning. std::ostream& out_; PathOutput path_output_; - // Write a Ninja output file to out_, and also add it to |*ninja_outputs_| - // if needed. - void WriteOutput(const OutputFile& output) const; - void WriteOutput(OutputFile&& output) const; - - // Same as WriteOutput() for a list of Ninja output file paths. - void WriteOutputs(const std::vector<OutputFile>& outputs) const; - void WriteOutputs(std::vector<OutputFile>&& outputs) const; - // The list of all Ninja output file paths generated by this writer for // this target. Used to implement the --ide=ninja_outputs `gn gen` flag. // Needs to be mutable because WriteOutput() and WriteOutputs() need to @@ -140,6 +153,8 @@ mutable ResolvedTargetData* resolved_ptr_ = nullptr; mutable std::unique_ptr<ResolvedTargetData> resolved_owned_; + NinjaTargetGroup target_group_; + private: void WriteCopyRules(); void WriteEscapedSubstitution(const Substitution* type);
diff --git a/src/gn/ninja_target_writer_unittest.cc b/src/gn/ninja_target_writer_unittest.cc index 53b1a4d..10bd166 100644 --- a/src/gn/ninja_target_writer_unittest.cc +++ b/src/gn/ninja_target_writer_unittest.cc
@@ -20,20 +20,27 @@ std::ostream& out) : NinjaTargetWriter(target, out) {} - void Run() override {} + void GenerateRules() override {} // Make this public so the test can call it. NinjaTargetWriter::InputDeps WriteInputDepsStampOrPhonyAndGetDep( const std::vector<const Target*>& additional_hard_deps, size_t num_stamp_uses) { - return NinjaTargetWriter::WriteInputDepsStampOrPhonyAndGetDep( + auto deps = NinjaTargetWriter::WriteInputDepsStampOrPhonyAndGetDep( additional_hard_deps, num_stamp_uses); + NinjaFile file; + file.AddTargetGroup(std::move(target_group_)); + file.Serialize(out_); + return deps; } void WriteStampOrPhonyForTarget( const std::vector<OutputFile>& deps, const std::vector<OutputFile>& order_only_deps) { NinjaTargetWriter::WriteStampOrPhonyForTarget(deps, order_only_deps); + NinjaFile file; + file.AddTargetGroup(std::move(target_group_)); + file.Serialize(out_); } }; @@ -155,6 +162,7 @@ " description = ACTION //foo:action()\n" " restat = 1\n" "\n" + "\n" "build: __foo_action___rule | ../../foo/script.py" " ../../foo/action_source.txt ./target\n" "\n" @@ -255,6 +263,7 @@ " description = ACTION //foo:action()\n" " restat = 1\n" "\n" + "\n" "build: __foo_action___rule | ../../foo/script.py" " ../../foo/action_source.txt ./target\n" "\n" @@ -540,6 +549,7 @@ " description = ACTION //foo:b_pub()\n" " restat = 1\n" "\n" + "\n" "build b_pub.out: __foo_b_pub___rule | ../../foo/script.py " "phony/foo/a\n" "\n" @@ -558,6 +568,7 @@ " description = ACTION //foo:b_priv()\n" " restat = 1\n" "\n" + "\n" "build b_priv.out: __foo_b_priv___rule | ../../foo/script.py " "phony/foo/a\n" "\n" @@ -607,6 +618,7 @@ " description = ACTION //foo:b_data()\n" " restat = 1\n" "\n" + "\n" "build b_data.out: __foo_b_data___rule | ../../foo/script.py || " "phony/foo/a\n" "\n" @@ -625,6 +637,7 @@ " description = ACTION //foo:c_data()\n" " restat = 1\n" "\n" + "\n" "build c_data.out: __foo_c_data___rule | ../../foo/script.py " "phony/foo/b_data\n" "\n" @@ -675,9 +688,13 @@ { std::ostringstream stream; ResolvedTargetData resolved; + NinjaTargetGroup group; TestingNinjaTargetWriter::WritePublicInputsStampOrPhony(&a, &resolved, - stream); - EXPECT_EQ("build phony/foo/a.public_inputs: phony ../../foo/a.in\n\n", + group); + NinjaFile file; + file.AddTargetGroup(std::move(group)); + file.Serialize(stream); + EXPECT_EQ("build phony/foo/a.public_inputs: phony ../../foo/a.in\n", stream.str()); } @@ -739,9 +756,13 @@ { std::ostringstream stream; ResolvedTargetData resolved; + NinjaTargetGroup group; TestingNinjaTargetWriter::WritePublicInputsStampOrPhony(&g, &resolved, - stream); - EXPECT_EQ("build phony/foo/g.public_inputs: phony ../../foo/g.in\n\n", + group); + NinjaFile file; + file.AddTargetGroup(std::move(group)); + file.Serialize(stream); + EXPECT_EQ("build phony/foo/g.public_inputs: phony ../../foo/g.in\n", stream.str()); }
diff --git a/src/gn/substitution_writer.cc b/src/gn/substitution_writer.cc index b6d0c48..23ea92b 100644 --- a/src/gn/substitution_writer.cc +++ b/src/gn/substitution_writer.cc
@@ -8,6 +8,7 @@ #include "gn/c_substitution_type.h" #include "gn/escape.h" #include "gn/filesystem_utils.h" +#include "gn/ninja_file.h" #include "gn/output_file.h" #include "gn/rust_substitution_type.h" #include "gn/rust_tool.h" @@ -322,6 +323,31 @@ const SourceFile& source, const std::vector<const Substitution*>& types, const EscapeOptions& escape_options, + std::vector<NinjaVariable>& edge_vars) { + for (const auto& type : types) { + // Don't write SOURCE since that just maps to Ninja's $in variable, which + // is implicit in the rule. RESPONSE_FILE_NAME is written separately + // only when writing target rules since it can never be used in any + // other context (like process_file_template). + if (type != &SubstitutionSource && type != &SubstitutionRspFileName) { + std::ostringstream val; + EscapeStringToStream( + val, + GetSourceSubstitution(target, settings, source, type, OUTPUT_RELATIVE, + settings->build_settings()->build_dir()), + escape_options); + edge_vars.emplace_back(type->ninja_name, val.str()); + } + } +} + +// static +void SubstitutionWriter::WriteNinjaVariablesForSource( + const Target* target, + const Settings* settings, + const SourceFile& source, + const std::vector<const Substitution*>& types, + const EscapeOptions& escape_options, std::ostream& out) { for (const auto& type : types) { // Don't write SOURCE since that just maps to Ninja's $in variable, which
diff --git a/src/gn/substitution_writer.h b/src/gn/substitution_writer.h index 95e4b94..c7da823 100644 --- a/src/gn/substitution_writer.h +++ b/src/gn/substitution_writer.h
@@ -9,6 +9,7 @@ #include <string> #include <vector> +#include "gn/ninja_file.h" #include "gn/substitution_type.h" struct EscapeOptions; @@ -157,6 +158,13 @@ const SourceFile& source, const std::vector<const Substitution*>& types, const EscapeOptions& escape_options, + std::vector<NinjaVariable>& edge_vars); + static void WriteNinjaVariablesForSource( + const Target* target, + const Settings* settings, + const SourceFile& source, + const std::vector<const Substitution*>& types, + const EscapeOptions& escape_options, std::ostream& out); // Extracts the given type of substitution related to a source file from the