Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(98)

Unified Diff: tools/gn/ninja_build_writer.cc

Issue 1958783003: GN: Prefer toplevel targets for phony names. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Review comments Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/gn/ninja_build_writer.h ('k') | tools/gn/ninja_build_writer_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/gn/ninja_build_writer.cc
diff --git a/tools/gn/ninja_build_writer.cc b/tools/gn/ninja_build_writer.cc
index d28969084eea60e485a8471e302c2fe8fe5af485..ff74e77fa33d40a3caa6313789285501b54b8791 100644
--- a/tools/gn/ninja_build_writer.cc
+++ b/tools/gn/ninja_build_writer.cc
@@ -8,6 +8,7 @@
#include <fstream>
#include <map>
+#include <sstream>
#include "base/command_line.h"
#include "base/files/file_util.h"
@@ -33,6 +34,16 @@
namespace {
+struct Counts {
+ Counts() : count(0), last_seen(nullptr) {}
+
+ // Number of targets of this type.
+ int count;
+
+ // The last one we encountered.
+ const Target* last_seen;
+};
+
std::string GetSelfInvocationCommand(const BuildSettings* build_settings) {
base::FilePath executable;
PathService::Get(base::FILE_EXE, &executable);
@@ -79,35 +90,6 @@ std::string GetSelfInvocationCommand(const BuildSettings* build_settings) {
#endif
}
-OutputFile GetTargetOutputFile(const Target* target) {
- OutputFile result(target->dependency_output_file());
-
- // The output files may have leading "./" so normalize those away.
- NormalizePath(&result.value());
- return result;
-}
-
-bool HasOutputIdenticalToLabel(const Target* target,
- const std::string& short_name) {
- if (target->output_type() != Target::ACTION &&
- target->output_type() != Target::ACTION_FOREACH)
- return false;
-
- // Rather than convert all outputs to be relative to the build directory
- // and then compare to the short name, convert the short name to look like a
- // file in the output directory since this is only one conversion.
- SourceFile short_name_as_source_file(
- target->settings()->build_settings()->build_dir().value() + short_name);
-
- std::vector<SourceFile> outputs_as_source;
- target->action_values().GetOutputsAsSourceFiles(target, &outputs_as_source);
- for (const SourceFile& output_as_source : outputs_as_source) {
- if (output_as_source == short_name_as_source_file)
- return true;
- }
- return false;
-}
-
// Given an output that appears more than once, generates an error message
// that describes the problem and which targets generate it.
Err GetDuplicateOutputError(const std::vector<const Target*>& all_targets,
@@ -178,29 +160,24 @@ bool NinjaBuildWriter::RunAndWriteFile(
Err* err) {
ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja");
- base::FilePath ninja_file(build_settings->GetFullPath(
- SourceFile(build_settings->build_dir().value() + "build.ninja")));
- base::CreateDirectory(ninja_file.DirName());
-
- std::ofstream file;
- file.open(FilePathToUTF8(ninja_file).c_str(),
- std::ios_base::out | std::ios_base::binary);
- if (file.fail()) {
- *err = Err(Location(), "Couldn't open build.ninja for writing");
+ std::stringstream file;
+ std::stringstream depfile;
+ NinjaBuildWriter gen(build_settings, all_settings, default_toolchain,
+ default_toolchain_targets, file, depfile);
+ if (!gen.Run(err))
return false;
- }
- std::ofstream depfile;
- depfile.open((FilePathToUTF8(ninja_file) + ".d").c_str(),
- std::ios_base::out | std::ios_base::binary);
- if (depfile.fail()) {
- *err = Err(Location(), "Couldn't open depfile for writing");
+ base::FilePath ninja_file_name(build_settings->GetFullPath(
+ SourceFile(build_settings->build_dir().value() + "build.ninja")));
+ base::FilePath dep_file_name(build_settings->GetFullPath(
+ SourceFile(build_settings->build_dir().value() + "build.ninja.d")));
+ base::CreateDirectory(ninja_file_name.DirName());
+
+ if (!WriteFileIfChanged(ninja_file_name, file.str(), err) ||
+ !WriteFileIfChanged(dep_file_name, depfile.str(), err))
return false;
- }
- NinjaBuildWriter gen(build_settings, all_settings, default_toolchain,
- default_toolchain_targets, file, depfile);
- return gen.Run(err);
+ return true;
}
void NinjaBuildWriter::WriteNinjaRules() {
@@ -251,149 +228,166 @@ void NinjaBuildWriter::WriteSubninjas() {
}
bool NinjaBuildWriter::WritePhonyAndAllRules(Err* err) {
- std::string all_rules;
-
// Track rules as we generate them so we don't accidentally write a phony
// rule that collides with something else.
// GN internally generates an "all" target, so don't duplicate it.
- std::set<std::string> written_rules;
+ base::hash_set<std::string> written_rules;
written_rules.insert("all");
- // Write phony rules for all uniquely-named targets in the default toolchain.
- // Don't do other toolchains or we'll get naming conflicts, and if the name
- // isn't unique, also skip it. The exception is for the toplevel targets
- // which we also find.
- std::map<std::string, int> small_name_count;
- std::map<std::string, int> exe_count;
+ // Set if we encounter a target named "//:default".
+ bool default_target_exists = false;
+
+ // Targets in the root build file.
std::vector<const Target*> toplevel_targets;
- base::hash_set<std::string> target_files;
- for (const auto& target : default_toolchain_targets_) {
+
+ // Targets with names matching their toplevel directories. For example
+ // "//foo:foo". Expect this is the naming scheme for "big components."
+ std::vector<const Target*> toplevel_dir_targets;
+
+ // Tracks the number of each target with the given short name, as well
+ // as the short names of executables (which will be a subset of short_names).
+ std::map<std::string, Counts> short_names;
+ std::map<std::string, Counts> exes;
+
+ for (const Target* target : default_toolchain_targets_) {
const Label& label = target->label();
- small_name_count[label.name()]++;
+ const std::string& short_name = label.name();
+
+ if (label.dir().value() == "//" && label.name() == "default")
+ default_target_exists = true;
+
+ // Count the number of targets with the given short name.
+ Counts& short_names_counts = short_names[short_name];
+ short_names_counts.count++;
+ short_names_counts.last_seen = target;
+
+ // Count executables with the given short name.
+ if (target->output_type() == Target::EXECUTABLE) {
+ Counts& exes_counts = exes[short_name];
+ exes_counts.count++;
+ exes_counts.last_seen = target;
+ }
- // Look for targets with a name of the form
- // dir = "//foo/", name = "foo"
- // i.e. where the target name matches the top level directory. We will
- // always write phony rules for these even if there is another target with
- // the same short name.
+ // Find targets in "important" directories.
const std::string& dir_string = label.dir().value();
- if (dir_string.size() == label.name().size() + 3 && // Size matches.
+ if (dir_string.size() == 2 &&
+ dir_string[0] == '/' && dir_string[1] == '/') {
+ toplevel_targets.push_back(target);
+ } else if (
+ dir_string.size() == label.name().size() + 3 && // Size matches.
dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning.
dir_string[dir_string.size() - 1] == '/' && // "/" at end.
- dir_string.compare(2, label.name().size(), label.name()) == 0)
- toplevel_targets.push_back(target);
-
- // Look for executables; later we will generate phony rules for them
- // even if there are non-executable targets with the same name.
- if (target->output_type() == Target::EXECUTABLE)
- exe_count[label.name()]++;
-
- // Add the files to the list of generated targets so we don't write phony
- // rules that collide.
- std::string target_file(target->dependency_output_file().value());
- NormalizePath(&target_file);
- written_rules.insert(target_file);
- }
+ dir_string.compare(2, label.name().size(), label.name()) == 0) {
+ toplevel_dir_targets.push_back(target);
+ }
- for (const auto& target : default_toolchain_targets_) {
- const Label& label = target->label();
+ // Add the output files from each target to the written rules so that
+ // we don't write phony rules that collide with anything generated by the
+ // build.
+ //
+ // If at this point there is a collision (no phony rules have been
+ // generated yet), two targets make the same output so throw an error.
for (const auto& output : target->computed_outputs()) {
- if (!target_files.insert(output.value()).second) {
+ // Need to normalize because many toolchain outputs will be preceeded
+ // with "./".
+ std::string output_string(output.value());
+ NormalizePath(&output_string);
+ if (!written_rules.insert(output_string).second) {
*err = GetDuplicateOutputError(default_toolchain_targets_, output);
return false;
}
}
+ }
+
+ // First prefer the short names of toplevel targets.
+ for (const Target* target : toplevel_targets) {
+ if (written_rules.insert(target->label().name()).second)
+ WritePhonyRule(target, target->label().name());
+ }
+
+ // Next prefer short names of toplevel dir targets.
+ for (const Target* target : toplevel_dir_targets) {
+ if (written_rules.insert(target->label().name()).second)
+ WritePhonyRule(target, target->label().name());
+ }
+
+ // Write out the names labels of executables. Many toolchains will produce
+ // executables in the root build directory with no extensions, so the names
+ // will already exist and this will be a no-op. But on Windows such programs
+ // will have extensions, and executables may override the output directory to
+ // go into some other place.
+ //
+ // Putting this after the "toplevel" rules above also means that you can
+ // steal the short name from an executable by outputting the executable to
+ // a different directory or using a different output name, and writing a
+ // toplevel build rule.
+ for (const auto& pair : exes) {
+ const Counts& counts = pair.second;
+ const std::string& short_name = counts.last_seen->label().name();
+ if (counts.count == 1 && written_rules.insert(short_name).second)
+ WritePhonyRule(counts.last_seen, short_name);
+ }
+
+ // Write short names when those names are unique and not already taken.
+ for (const auto& pair : short_names) {
+ const Counts& counts = pair.second;
+ const std::string& short_name = counts.last_seen->label().name();
+ if (counts.count == 1 && written_rules.insert(short_name).second)
+ WritePhonyRule(counts.last_seen, short_name);
+ }
+
+ // Write the label variants of the target name.
+ for (const Target* target : default_toolchain_targets_) {
+ const Label& label = target->label();
- OutputFile target_file = GetTargetOutputFile(target);
// Write the long name "foo/bar:baz" for the target "//foo/bar:baz".
std::string long_name = label.GetUserVisibleName(false);
base::TrimString(long_name, "/", &long_name);
- WritePhonyRule(target, target_file, long_name, &written_rules);
+ if (written_rules.insert(long_name).second)
+ WritePhonyRule(target, long_name);
// Write the directory name with no target name if they match
// (e.g. "//foo/bar:bar" -> "foo/bar").
if (FindLastDirComponent(label.dir()) == label.name()) {
- std::string medium_name = DirectoryWithNoLastSlash(label.dir());
+ std::string medium_name = DirectoryWithNoLastSlash(label.dir());
base::TrimString(medium_name, "/", &medium_name);
// That may have generated a name the same as the short name of the
// target which we already wrote.
- if (medium_name != label.name())
- WritePhonyRule(target, target_file, medium_name, &written_rules);
+ if (medium_name != label.name() &&
+ written_rules.insert(medium_name).second)
+ WritePhonyRule(target, medium_name);
}
- // Write short names for ones which are either completely unique or there
- // at least only one of them in the default toolchain that is an exe.
- if (small_name_count[label.name()] == 1 ||
- (target->output_type() == Target::EXECUTABLE &&
- exe_count[label.name()] == 1)) {
- // It's reasonable to generate output files in the root build directory
- // with the same name as the target. Don't generate phony rules for
- // these cases.
- //
- // All of this does not do the general checking of all target's outputs
- // which may theoretically collide. But it's not very reasonable for
- // a script target named "foo" to generate a file named "bar" with no
- // extension in the root build directory while another target is named
- // "bar". If this does occur, the user is likely to be confused when
- // building "bar" that is builds foo anyway, so you probably just
- // shouldn't do that.
- //
- // We should fix this however, and build up all generated script outputs
- // and check everything against that. There are other edge cases that the
- // current phony rule generator doesn't check. We may need to make a big
- // set of every possible generated file in the build for this purpose.
- if (!HasOutputIdenticalToLabel(target, label.name()))
- WritePhonyRule(target, target_file, label.name(), &written_rules);
- }
-
- if (!all_rules.empty())
- all_rules.append(" $\n ");
- all_rules.append(target_file.value());
+ // Write the short name if no other target shares that short name and
+ // non of the higher-priority rules above claimed it.
+ if (short_names[label.name()].count == 1 &&
+ written_rules.insert(label.name()).second)
+ WritePhonyRule(target, label.name());
}
- // Pick up phony rules for the toplevel targets with non-unique names (which
- // would have been skipped in the above loop).
- for (const auto& toplevel_target : toplevel_targets) {
- if (small_name_count[toplevel_target->label().name()] > 1) {
- WritePhonyRule(toplevel_target, toplevel_target->dependency_output_file(),
- toplevel_target->label().name(), &written_rules);
- }
- }
+ // Write the autogenerated "all" rule.
+ if (!default_toolchain_targets_.empty()) {
+ out_ << "\nbuild all: phony";
- // Figure out if the BUILD file wants to declare a custom "default"
- // target (rather than building 'all' by default). By convention
- // we use group("default") but it doesn't have to be a group.
- bool default_target_exists = false;
- for (const auto& target : default_toolchain_targets_) {
- const Label& label = target->label();
- if (label.dir().value() == "//" && label.name() == "default")
- default_target_exists = true;
- }
-
- if (!all_rules.empty()) {
- out_ << "\nbuild all: phony " << all_rules << std::endl;
+ EscapeOptions ninja_escape;
+ ninja_escape.mode = ESCAPE_NINJA;
+ for (const Target* target : default_toolchain_targets_) {
+ out_ << " $\n ";
+ path_output_.WriteFile(out_, target->dependency_output_file());
+ }
}
+ out_ << std::endl;
- if (default_target_exists) {
- out_ << "default default" << std::endl;
- } else if (!all_rules.empty()) {
- out_ << "default all" << std::endl;
- }
+ if (default_target_exists)
+ out_ << "\ndefault default" << std::endl;
+ else if (!default_toolchain_targets_.empty())
+ out_ << "\ndefault all" << std::endl;
return true;
}
void NinjaBuildWriter::WritePhonyRule(const Target* target,
- const OutputFile& target_file,
- const std::string& phony_name,
- std::set<std::string>* written_rules) {
- if (target_file.value() == phony_name)
- return; // No need for a phony rule.
-
- if (written_rules->find(phony_name) != written_rules->end())
- return; // Already exists.
- written_rules->insert(phony_name);
-
+ const std::string& phony_name) {
EscapeOptions ninja_escape;
ninja_escape.mode = ESCAPE_NINJA;
@@ -401,6 +395,6 @@ void NinjaBuildWriter::WritePhonyRule(const Target* target,
std::string escaped = EscapeString(phony_name, ninja_escape, nullptr);
out_ << "build " << escaped << ": phony ";
- path_output_.WriteFile(out_, target_file);
+ path_output_.WriteFile(out_, target->dependency_output_file());
out_ << std::endl;
}
« no previous file with comments | « tools/gn/ninja_build_writer.h ('k') | tools/gn/ninja_build_writer_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698