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; |
} |