[gn] Don't read entire build.ninja into memory in gn gen/clean This switches ExtractRegenerationCommands to use std::istream rather than reading the whole file just to use the first ~10 lines. Change-Id: I2a77c24541edd20d3f5e20130199855148e304fc Reviewed-on: https://gn-review.googlesource.com/c/gn/+/14321 Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/commands.cc b/src/gn/commands.cc index 5ff7ca8..3840761 100644 --- a/src/gn/commands.cc +++ b/src/gn/commands.cc
@@ -4,6 +4,7 @@ #include "gn/commands.h" +#include <fstream> #include <optional> #include "base/command_line.h" @@ -492,25 +493,25 @@ // Write a stripped down build.ninja file with just the commands needed // for ninja to call GN and regenerate ninja files. - base::FilePath build_ninja_file(settings->GetFullPath( + base::FilePath build_ninja_path(settings->GetFullPath( SourceFile(settings->build_dir().value() + "build.ninja"))); - std::string build_ninja_contents; - if (!base::ReadFileToString(build_ninja_file, &build_ninja_contents)) { - // Couldn't parse the build.ninja file. - Err(Location(), "Couldn't read build.ninja in this directory.", + std::ifstream build_ninja_file(build_ninja_path.value()); + if (!build_ninja_file) { + // Couldn't open the build.ninja file. + Err(Location(), "Couldn't open build.ninja in this directory.", "Try running \"gn gen\" on it and then re-running \"gn clean\".") .PrintToStdout(); return false; } std::string build_commands = - NinjaBuildWriter::ExtractRegenerationCommands(build_ninja_contents); + NinjaBuildWriter::ExtractRegenerationCommands(build_ninja_file); if (build_commands.empty()) { Err(Location(), "Unexpected build.ninja contents in this directory.", "Try running \"gn gen\" on it and then re-running \"gn clean\".") .PrintToStdout(); return false; } - if (util::WriteFileAtomically(build_ninja_file, build_commands.data(), + if (util::WriteFileAtomically(build_ninja_path, build_commands.data(), static_cast<int>(build_commands.size())) == -1) { Err(Location(), std::string("Failed to write build.ninja."))
diff --git a/src/gn/ninja_build_writer.cc b/src/gn/ninja_build_writer.cc index 90a296b..05db217 100644 --- a/src/gn/ninja_build_writer.cc +++ b/src/gn/ninja_build_writer.cc
@@ -13,7 +13,6 @@ #include "base/command_line.h" #include "base/files/file_util.h" -#include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "gn/build_settings.h" @@ -302,13 +301,10 @@ // static std::string NinjaBuildWriter::ExtractRegenerationCommands( - const std::string& build_ninja_in) { - std::vector<std::string_view> lines = base::SplitStringPiece( - build_ninja_in, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); - + std::istream& build_ninja_in) { std::ostringstream out; int num_blank_lines = 0; - for (const auto& line : lines) { + for (std::string line; std::getline(build_ninja_in, line);) { out << line << '\n'; if (line.empty()) ++num_blank_lines;
diff --git a/src/gn/ninja_build_writer.h b/src/gn/ninja_build_writer.h index 54a80aa..c21769a 100644 --- a/src/gn/ninja_build_writer.h +++ b/src/gn/ninja_build_writer.h
@@ -56,8 +56,7 @@ // commands::PrepareForRegeneration. // // On error, returns an empty string. - static std::string ExtractRegenerationCommands( - const std::string& build_ninja_in); + static std::string ExtractRegenerationCommands(std::istream& build_ninja_in); bool Run(Err* err);
diff --git a/src/gn/ninja_build_writer_unittest.cc b/src/gn/ninja_build_writer_unittest.cc index 453ff03..f8abaf2 100644 --- a/src/gn/ninja_build_writer_unittest.cc +++ b/src/gn/ninja_build_writer_unittest.cc
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <fstream> #include <sstream> #include "base/command_line.h" @@ -211,7 +212,7 @@ std::vector<const Target*> targets = {&target_foo}; - std::ostringstream ninja_out; + std::stringstream ninja_out; std::ostringstream depfile_out; NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets, @@ -242,7 +243,7 @@ EXPECT_SNIPPET(ninja_out_str, expected_default); std::string commands = - NinjaBuildWriter::ExtractRegenerationCommands(ninja_out_str); + NinjaBuildWriter::ExtractRegenerationCommands(ninja_out); EXPECT_SNIPPET(commands, expected_rule_gn); EXPECT_SNIPPET(commands, expected_build_ninja_stamp); EXPECT_SNIPPET(commands, expected_build_ninja); @@ -254,6 +255,22 @@ #undef EXPECT_NO_SNIPPET } +TEST_F(NinjaBuildWriterTest, ExtractRegenerationCommands_DefaultStream) { + std::ifstream ninja_in; + EXPECT_EQ(NinjaBuildWriter::ExtractRegenerationCommands(ninja_in), ""); +} + +TEST_F(NinjaBuildWriterTest, ExtractRegenerationCommands_StreamError) { + std::ifstream ninja_in("/does/not/exist"); + EXPECT_EQ(NinjaBuildWriter::ExtractRegenerationCommands(ninja_in), ""); +} + +TEST_F(NinjaBuildWriterTest, ExtractRegenerationCommands_IncompleteNinja) { + std::stringstream ninja_in; + ninja_in << "foo\nbar\nbaz\nbif\n"; + EXPECT_EQ(NinjaBuildWriter::ExtractRegenerationCommands(ninja_in), ""); +} + TEST_F(NinjaBuildWriterTest, SpaceInDepfile) { TestWithScope setup; Err err;