| // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gn/ninja_build_writer.h" | |
| #include <stddef.h> | |
| #include <fstream> | |
| #include <map> | |
| #include <set> | |
| #include <sstream> | |
| #include "base/command_line.h" | |
| #include "base/files/file_util.h" | |
| #include "base/strings/string_util.h" | |
| #include "base/strings/utf_string_conversions.h" | |
| #include "gn/build_settings.h" | |
| #include "gn/builder.h" | |
| #include "gn/err.h" | |
| #include "gn/escape.h" | |
| #include "gn/filesystem_utils.h" | |
| #include "gn/input_file_manager.h" | |
| #include "gn/loader.h" | |
| #include "gn/ninja_utils.h" | |
| #include "gn/pool.h" | |
| #include "gn/scheduler.h" | |
| #include "gn/string_atom.h" | |
| #include "gn/switches.h" | |
| #include "gn/target.h" | |
| #include "gn/trace.h" | |
| #include "util/atomic_write.h" | |
| #include "util/build_config.h" | |
| #include "util/exe_path.h" | |
| #if defined(OS_WIN) | |
| #include <windows.h> | |
| #endif | |
| 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; | |
| }; | |
| } // namespace | |
| base::CommandLine GetSelfInvocationCommandLine( | |
| const BuildSettings* build_settings) { | |
| const base::FilePath build_path = | |
| build_settings->build_dir().Resolve(build_settings->root_path()); | |
| base::FilePath exe_path = GetExePath(); | |
| if (build_path.IsAbsolute()) | |
| exe_path = MakeAbsoluteFilePathRelativeIfPossible(build_path, exe_path); | |
| base::CommandLine cmdline(exe_path.NormalizePathSeparatorsTo('/')); | |
| // Use "." for the directory to generate. When Ninja runs the command it | |
| // will have the build directory as the current one. Coding it explicitly | |
| // will cause everything to get confused if the user renames the directory. | |
| cmdline.AppendArg("gen"); | |
| cmdline.AppendArg("."); | |
| base::FilePath root_path = build_settings->root_path(); | |
| if (build_path.IsAbsolute()) | |
| root_path = MakeAbsoluteFilePathRelativeIfPossible(build_path, root_path); | |
| cmdline.AppendSwitchPath(std::string("--") + switches::kRoot, | |
| root_path.NormalizePathSeparatorsTo('/')); | |
| // Successful automatic invocations shouldn't print output. | |
| cmdline.AppendSwitch(std::string("-") + switches::kQuiet); | |
| EscapeOptions escape_shell; | |
| escape_shell.mode = ESCAPE_NINJA_COMMAND; | |
| #if defined(OS_WIN) | |
| // The command line code quoting varies by platform. We have one string, | |
| // possibly with spaces, that we want to quote. The Windows command line | |
| // quotes again, so we don't want quoting. The Posix one doesn't. | |
| escape_shell.inhibit_quoting = true; | |
| #endif | |
| // If both --root and --dotfile are passed, make sure the --dotfile is | |
| // made relative to the build dir here. | |
| base::FilePath dotfile_path = build_settings->dotfile_name(); | |
| if (!dotfile_path.empty()) { | |
| if (build_path.IsAbsolute()) { | |
| dotfile_path = | |
| MakeAbsoluteFilePathRelativeIfPossible(build_path, dotfile_path); | |
| } | |
| cmdline.AppendSwitchPath(std::string("--") + switches::kDotfile, | |
| dotfile_path.NormalizePathSeparatorsTo('/')); | |
| } | |
| const base::CommandLine& our_cmdline = | |
| *base::CommandLine::ForCurrentProcess(); | |
| const base::CommandLine::SwitchMap& switches = our_cmdline.GetSwitches(); | |
| for (base::CommandLine::SwitchMap::const_iterator i = switches.begin(); | |
| i != switches.end(); ++i) { | |
| // Only write arguments we haven't already written. Always skip "args" | |
| // since those will have been written to the file and will be used | |
| // implicitly in the future. Keeping --args would mean changes to the file | |
| // would be ignored. | |
| if (i->first != switches::kQuiet && i->first != switches::kRoot && | |
| i->first != switches::kDotfile && i->first != switches::kArgs) { | |
| std::string escaped_value = | |
| EscapeString(FilePathToUTF8(i->second), escape_shell, nullptr); | |
| cmdline.AppendSwitch(i->first, escaped_value); | |
| } | |
| } | |
| // Add the regeneration switch if not already present. This is so that when | |
| // the regeneration is invoked by ninja, the gen command is aware that it is a | |
| // regeneration invocation and not an user invocation. This allows the gen | |
| // command to elide ninja post processing steps that ninja will perform | |
| // itself. | |
| if (!cmdline.HasSwitch(switches::kRegeneration)) { | |
| cmdline.AppendSwitch(switches::kRegeneration); | |
| } | |
| return cmdline; | |
| } | |
| namespace { | |
| std::string GetSelfInvocationCommand(const BuildSettings* build_settings) { | |
| base::CommandLine cmdline = GetSelfInvocationCommandLine(build_settings); | |
| #if defined(OS_WIN) | |
| return base::UTF16ToUTF8(cmdline.GetCommandLineString()); | |
| #else | |
| return cmdline.GetCommandLineString(); | |
| #endif | |
| } | |
| // 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, | |
| const OutputFile& bad_output) { | |
| std::vector<const Target*> matches; | |
| for (const Target* target : all_targets) { | |
| for (const auto& output : target->computed_outputs()) { | |
| if (output == bad_output) { | |
| matches.push_back(target); | |
| break; | |
| } | |
| } | |
| } | |
| // There should always be at least two targets generating this file for this | |
| // function to be called in the first place. | |
| DCHECK(matches.size() >= 2); | |
| std::string matches_string; | |
| for (const Target* target : matches) | |
| matches_string += " " + target->label().GetUserVisibleName(true) + "\n"; | |
| Err result(matches[0]->defined_from(), "Duplicate output file.", | |
| "Two or more targets generate the same output:\n " + | |
| std::string(bad_output.value()) + | |
| "\n\n" | |
| "This is can often be fixed by changing one of the target " | |
| "names, or by \n" | |
| "setting an output_name on one of them.\n" | |
| "\nCollisions:\n" + | |
| matches_string); | |
| for (size_t i = 1; i < matches.size(); i++) | |
| result.AppendSubErr(Err(matches[i]->defined_from(), "Collision.")); | |
| return result; | |
| } | |
| // Given two toolchains with the same name, generates an error message | |
| // that describes the problem. | |
| Err GetDuplicateToolchainError(const SourceFile& source_file, | |
| const Toolchain* previous_toolchain, | |
| const Toolchain* toolchain) { | |
| Err result( | |
| toolchain->defined_from(), "Duplicate toolchain.", | |
| "Two or more toolchains write to the same directory:\n " + | |
| source_file.GetDir().value() + | |
| "\n\n" | |
| "This can be fixed by making sure that distinct toolchains have\n" | |
| "distinct names.\n"); | |
| result.AppendSubErr( | |
| Err(previous_toolchain->defined_from(), "Previous toolchain.")); | |
| return result; | |
| } | |
| } // namespace | |
| NinjaBuildWriter::NinjaBuildWriter( | |
| const BuildSettings* build_settings, | |
| const std::unordered_map<const Settings*, const Toolchain*>& | |
| used_toolchains, | |
| const std::vector<const Target*>& all_targets, | |
| const Toolchain* default_toolchain, | |
| const std::vector<const Target*>& default_toolchain_targets, | |
| std::ostream& out, | |
| std::ostream& dep_out) | |
| : build_settings_(build_settings), | |
| used_toolchains_(used_toolchains), | |
| all_targets_(all_targets), | |
| default_toolchain_(default_toolchain), | |
| default_toolchain_targets_(default_toolchain_targets), | |
| out_(out), | |
| dep_out_(dep_out), | |
| path_output_(build_settings->build_dir(), | |
| build_settings->root_path_utf8(), | |
| ESCAPE_NINJA) {} | |
| NinjaBuildWriter::~NinjaBuildWriter() = default; | |
| bool NinjaBuildWriter::Run(Err* err) { | |
| WriteNinjaRules(); | |
| WriteAllPools(); | |
| return WriteSubninjas(err) && WritePhonyAndAllRules(err); | |
| } | |
| // static | |
| bool NinjaBuildWriter::RunAndWriteFile(const BuildSettings* build_settings, | |
| const Builder& builder, | |
| Err* err) { | |
| ScopedTrace trace(TraceItem::TRACE_FILE_WRITE_NINJA, "build.ninja"); | |
| std::vector<const Target*> all_targets = builder.GetAllResolvedTargets(); | |
| std::unordered_map<const Settings*, const Toolchain*> used_toolchains; | |
| // Find the default toolchain info. | |
| Label default_toolchain_label = builder.loader()->GetDefaultToolchain(); | |
| const Settings* default_toolchain_settings = | |
| builder.loader()->GetToolchainSettings(default_toolchain_label); | |
| const Toolchain* default_toolchain = | |
| builder.GetToolchain(default_toolchain_label); | |
| // Most targets will be in the default toolchain. Add it at the beginning and | |
| // skip adding it to the list every time in the loop. | |
| used_toolchains[default_toolchain_settings] = default_toolchain; | |
| std::vector<const Target*> default_toolchain_targets; | |
| default_toolchain_targets.reserve(all_targets.size()); | |
| for (const Target* target : all_targets) { | |
| if (target->settings() == default_toolchain_settings) { | |
| default_toolchain_targets.push_back(target); | |
| // The default toolchain will already have been added to the used | |
| // settings array. | |
| } else if (used_toolchains.find(target->settings()) == | |
| used_toolchains.end()) { | |
| used_toolchains[target->settings()] = | |
| builder.GetToolchain(target->settings()->toolchain_label()); | |
| } | |
| } | |
| std::stringstream file; | |
| std::stringstream depfile; | |
| NinjaBuildWriter gen(build_settings, used_toolchains, all_targets, | |
| default_toolchain, default_toolchain_targets, file, | |
| depfile); | |
| if (!gen.Run(err)) | |
| return false; | |
| // Unconditionally write the build.ninja. Ninja's build-out-of-date | |
| // checking will re-run GN when any build input is newer than build.ninja, so | |
| // any time the build is updated, build.ninja's timestamp needs to updated | |
| // also, even if the contents haven't been changed. | |
| base::FilePath ninja_file_name(build_settings->GetFullPath( | |
| SourceFile(build_settings->build_dir().value() + "build.ninja"))); | |
| base::CreateDirectory(ninja_file_name.DirName()); | |
| std::string ninja_contents = file.str(); | |
| if (util::WriteFileAtomically(ninja_file_name, ninja_contents.data(), | |
| static_cast<int>(ninja_contents.size())) != | |
| static_cast<int>(ninja_contents.size())) { | |
| *err = Err(Location(), "Failed to write build.ninja."); | |
| return false; | |
| } | |
| // Dep file listing build dependencies. | |
| base::FilePath dep_file_name(build_settings->GetFullPath( | |
| SourceFile(build_settings->build_dir().value() + "build.ninja.d"))); | |
| std::string dep_contents = depfile.str(); | |
| if (util::WriteFileAtomically(dep_file_name, dep_contents.data(), | |
| static_cast<int>(dep_contents.size())) != | |
| static_cast<int>(dep_contents.size())) { | |
| *err = Err(Location(), "Failed to write build.ninja.d"); | |
| return false; | |
| } | |
| // Finally, write the empty build.ninja.stamp file. This is the output | |
| // expected by the first of the two ninja rules used to accomplish | |
| // regeneration. | |
| base::FilePath stamp_file_name(build_settings->GetFullPath( | |
| SourceFile(build_settings->build_dir().value() + "build.ninja.stamp"))); | |
| std::string stamp_contents; | |
| if (util::WriteFileAtomically(stamp_file_name, stamp_contents.data(), | |
| static_cast<int>(stamp_contents.size())) != | |
| static_cast<int>(stamp_contents.size())) { | |
| *err = Err(Location(), "Failed to write build.ninja.stamp."); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // static | |
| std::string NinjaBuildWriter::ExtractRegenerationCommands( | |
| std::istream& build_ninja_in) { | |
| std::ostringstream out; | |
| int num_blank_lines = 0; | |
| for (std::string line; std::getline(build_ninja_in, line);) { | |
| out << line << '\n'; | |
| if (line.empty()) | |
| ++num_blank_lines; | |
| // Warning: Significant magic number. Represents the number of blank lines | |
| // in the ninja rules written by NinjaBuildWriter::WriteNinjaRules below. | |
| if (num_blank_lines == 4) | |
| return out.str(); | |
| } | |
| return std::string{}; | |
| } | |
| void NinjaBuildWriter::WriteNinjaRules() { | |
| out_ << "ninja_required_version = " | |
| << build_settings_->ninja_required_version().Describe() << "\n\n"; | |
| out_ << "rule gn\n"; | |
| out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n"; | |
| // Putting gn rule to console pool for colorful output on regeneration | |
| out_ << " pool = console\n"; | |
| out_ << " description = Regenerating ninja files\n\n"; | |
| // A comment is left in the build.ninja explaining the two statement setup to | |
| // avoid confusion, since build.ninja is written earlier than the ninja rules | |
| // might make someone think. | |
| out_ << "# The 'gn' rule also writes build.ninja, unbeknownst to ninja. The\n" | |
| << "# build.ninja edge is separate to prevent ninja from deleting it\n" | |
| << "# (due to depfile usage) if interrupted. gn uses atomic writes to\n" | |
| << "# ensure that build.ninja is always valid even if interrupted.\n" | |
| << "build build.ninja.stamp: gn\n" | |
| << " generator = 1\n" | |
| << " depfile = build.ninja.d\n" | |
| << "\n" | |
| << "build build.ninja: phony build.ninja.stamp\n" | |
| << " generator = 1\n"; | |
| // Input build files. These go in the ".d" file. If we write them | |
| // as dependencies in the .ninja file itself, ninja will expect | |
| // the files to exist and will error if they don't. When files are | |
| // listed in a depfile, missing files are ignored. | |
| dep_out_ << "build.ninja.stamp:"; | |
| // Other files read by the build. | |
| std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies(); | |
| const InputFileManager* input_file_manager = | |
| g_scheduler->input_file_manager(); | |
| VectorSetSorter<base::FilePath> sorter( | |
| input_file_manager->GetInputFileCount() + other_files.size()); | |
| input_file_manager->AddAllPhysicalInputFileNamesToVectorSetSorter(&sorter); | |
| sorter.Add(other_files.begin(), other_files.end()); | |
| const base::FilePath build_path = | |
| build_settings_->build_dir().Resolve(build_settings_->root_path()); | |
| EscapeOptions depfile_escape; | |
| depfile_escape.mode = ESCAPE_DEPFILE; | |
| auto item_callback = [this, &depfile_escape, | |
| &build_path](const base::FilePath& input_file) { | |
| const base::FilePath file = | |
| MakeAbsoluteFilePathRelativeIfPossible(build_path, input_file); | |
| dep_out_ << " "; | |
| EscapeStringToStream(dep_out_, | |
| FilePathToUTF8(file.NormalizePathSeparatorsTo('/')), | |
| depfile_escape); | |
| }; | |
| sorter.IterateOver(item_callback); | |
| out_ << std::endl; | |
| } | |
| void NinjaBuildWriter::WriteAllPools() { | |
| // Compute the pools referenced by all tools of all used toolchains. | |
| std::set<const Pool*> used_pools; | |
| for (const auto& pair : used_toolchains_) { | |
| for (const auto& tool : pair.second->tools()) { | |
| if (tool.second->pool().ptr) | |
| used_pools.insert(tool.second->pool().ptr); | |
| } | |
| } | |
| for (const Target* target : all_targets_) { | |
| if (target->IsBinary() || target->output_type() == Target::ACTION || | |
| target->output_type() == Target::ACTION_FOREACH) { | |
| const LabelPtrPair<Pool>& pool = target->pool(); | |
| if (pool.ptr) | |
| used_pools.insert(pool.ptr); | |
| } | |
| } | |
| // Write pools sorted by their name, to make output deterministic. | |
| std::vector<const Pool*> sorted_pools(used_pools.begin(), used_pools.end()); | |
| auto pool_name = [this](const Pool* pool) { | |
| return pool->GetNinjaName(default_toolchain_->label()); | |
| }; | |
| std::sort(sorted_pools.begin(), sorted_pools.end(), | |
| [&pool_name](const Pool* a, const Pool* b) { | |
| return pool_name(a) < pool_name(b); | |
| }); | |
| for (const Pool* pool : sorted_pools) { | |
| std::string name = pool_name(pool); | |
| if (name == "console") | |
| continue; | |
| out_ << "pool " << name << std::endl | |
| << " depth = " << pool->depth() << std::endl | |
| << std::endl; | |
| } | |
| } | |
| bool NinjaBuildWriter::WriteSubninjas(Err* err) { | |
| // Write toolchains sorted by their name, to make output deterministic. | |
| std::vector<std::pair<const Settings*, const Toolchain*>> sorted_settings( | |
| used_toolchains_.begin(), used_toolchains_.end()); | |
| std::sort(sorted_settings.begin(), sorted_settings.end(), | |
| [this](const std::pair<const Settings*, const Toolchain*>& a, | |
| const std::pair<const Settings*, const Toolchain*>& b) { | |
| // Always put the default toolchain first. | |
| if (b.second == default_toolchain_) | |
| return false; | |
| if (a.second == default_toolchain_) | |
| return true; | |
| return GetNinjaFileForToolchain(a.first) < | |
| GetNinjaFileForToolchain(b.first); | |
| }); | |
| SourceFile previous_subninja; | |
| const Toolchain* previous_toolchain = nullptr; | |
| for (const auto& pair : sorted_settings) { | |
| SourceFile subninja = GetNinjaFileForToolchain(pair.first); | |
| // Since the toolchains are sorted, comparing to the previous subninja is | |
| // enough to find duplicates. | |
| if (subninja == previous_subninja) { | |
| *err = | |
| GetDuplicateToolchainError(subninja, previous_toolchain, pair.second); | |
| return false; | |
| } | |
| out_ << "subninja "; | |
| path_output_.WriteFile(out_, subninja); | |
| out_ << std::endl; | |
| previous_subninja = subninja; | |
| previous_toolchain = pair.second; | |
| } | |
| out_ << std::endl; | |
| return true; | |
| } | |
| const char kNinjaRules_Help[] = | |
| R"(Ninja build rules | |
| The "all" and "default" rules | |
| All generated targets (see "gn help execution") will be added to an implicit | |
| build rule called "all" so "ninja all" will always compile everything. The | |
| default rule will be used by Ninja if no specific target is specified (just | |
| typing "ninja"). If there is a target named "default" in the root build file, | |
| it will be the default build rule, otherwise the implicit "all" rule will be | |
| used. | |
| Phony rules | |
| GN generates Ninja "phony" rules for targets in the default toolchain. The | |
| phony rules can collide with each other and with the names of generated files | |
| so are generated with the following priority: | |
| 1. Actual files generated by the build always take precedence. | |
| 2. Targets in the toplevel //BUILD.gn file. | |
| 3. Targets in toplevel directories matching the names of the directories. | |
| So "ninja foo" can be used to compile "//foo:foo". This only applies to | |
| the first level of directories since usually these are the most | |
| important (so this won't apply to "//foo/bar:bar"). | |
| 4. The short names of executables if there is only one executable with that | |
| short name. Use "ninja doom_melon" to compile the | |
| "//tools/fruit:doom_melon" executable. | |
| Note that for Apple platforms, create_bundle targets with a product_type | |
| of "com.apple.product-type.application" are considered as executable | |
| for this rule (as they define application bundles). | |
| 5. The short names of all targets if there is only one target with that | |
| short name. | |
| 6. Full label name with no leading slashes. So you can use | |
| "ninja tools/fruit:doom_melon" to build "//tools/fruit:doom_melon". | |
| 7. Labels with an implicit name part (when the short names match the | |
| directory). So you can use "ninja foo/bar" to compile "//foo/bar:bar". | |
| These "phony" rules are provided only for running Ninja since this matches | |
| people's historical expectations for building. For consistency with the rest | |
| of the program, GN introspection commands accept explicit labels. | |
| To explicitly compile a target in a non-default toolchain, you must give | |
| Ninja the exact name of the output file relative to the build directory. | |
| )"; | |
| bool NinjaBuildWriter::WritePhonyAndAllRules(Err* err) { | |
| // 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<StringAtom, StringAtom::PtrCompare> written_rules; | |
| written_rules.insert(StringAtom("all")); | |
| // Set if we encounter a target named "//:default". | |
| const Target* default_target = nullptr; | |
| // Targets in the root build file. | |
| std::vector<const Target*> toplevel_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; | |
| // ---------------------------------------------------- | |
| // If you change this algorithm, update the help above! | |
| // ---------------------------------------------------- | |
| for (const Target* target : default_toolchain_targets_) { | |
| const Label& label = target->label(); | |
| const std::string& short_name = label.name(); | |
| if (label.dir() == build_settings_->root_target_label().dir() && | |
| short_name == "default") | |
| default_target = target; | |
| // 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; | |
| } | |
| if (target->output_type() == Target::CREATE_BUNDLE && | |
| target->bundle_data().is_application()) { | |
| Counts& exes_counts = exes[short_name]; | |
| exes_counts.count++; | |
| exes_counts.last_seen = target; | |
| } | |
| // Find targets in "important" directories. | |
| const std::string& dir_string = label.dir().value(); | |
| 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_dir_targets.push_back(target); | |
| } | |
| } | |
| // 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 Target* target : all_targets_) { | |
| for (const auto& output : target->computed_outputs()) { | |
| // Need to normalize because many toolchain outputs will be preceded | |
| // with "./". | |
| std::string output_string(output.value()); | |
| NormalizePath(&output_string); | |
| const StringAtom output_string_atom(output_string); | |
| if (!written_rules.insert(output_string_atom).second) { | |
| *err = GetDuplicateOutputError(all_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_atom()).second) | |
| WritePhonyRule(target, target->label().name_atom()); | |
| } | |
| // Next prefer short names of toplevel dir targets. | |
| for (const Target* target : toplevel_dir_targets) { | |
| if (written_rules.insert(target->label().name_atom()).second) | |
| WritePhonyRule(target, target->label().name_atom()); | |
| } | |
| // 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 StringAtom& short_name = counts.last_seen->label().name_atom(); | |
| 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 StringAtom& short_name = counts.last_seen->label().name_atom(); | |
| 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(); | |
| // 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); | |
| const StringAtom long_name_atom(long_name); | |
| if (written_rules.insert(long_name_atom).second) | |
| WritePhonyRule(target, long_name_atom); | |
| // 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()); | |
| base::TrimString(medium_name, "/", &medium_name); | |
| const StringAtom medium_name_atom(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() && | |
| written_rules.insert(medium_name_atom).second) | |
| WritePhonyRule(target, medium_name_atom); | |
| } | |
| } | |
| // Write the autogenerated "all" rule. | |
| if (!default_toolchain_targets_.empty()) { | |
| out_ << "\nbuild all: phony"; | |
| EscapeOptions ninja_escape; | |
| ninja_escape.mode = ESCAPE_NINJA; | |
| for (const Target* target : default_toolchain_targets_) { | |
| if (target->has_dependency_output()) { | |
| out_ << " $\n "; | |
| path_output_.WriteFile(out_, target->dependency_output()); | |
| } | |
| } | |
| } | |
| out_ << std::endl; | |
| if (default_target) { | |
| // Use the short name when available | |
| if (written_rules.find(StringAtom("default")) != written_rules.end()) { | |
| out_ << "\ndefault default" << std::endl; | |
| } else if (default_target->has_dependency_output()) { | |
| // If the default target does not have a dependency output file or phony, | |
| // then the target specified as default is a no-op. We omit the default | |
| // statement entirely to avoid ninja runtime failure. | |
| out_ << "\ndefault "; | |
| path_output_.WriteFile(out_, default_target->dependency_output()); | |
| out_ << std::endl; | |
| } | |
| } else if (!default_toolchain_targets_.empty()) { | |
| out_ << "\ndefault all" << std::endl; | |
| } | |
| return true; | |
| } | |
| void NinjaBuildWriter::WritePhonyRule(const Target* target, | |
| std::string_view phony_name) { | |
| EscapeOptions ninja_escape; | |
| ninja_escape.mode = ESCAPE_NINJA; | |
| // Escape for special chars Ninja will handle. | |
| std::string escaped = EscapeString(phony_name, ninja_escape, nullptr); | |
| // If the target doesn't have a dependency_output(), we should | |
| // still emit the phony rule, but with no dependencies. This allows users to | |
| // continue to use the phony rule, but it will effectively be a no-op. | |
| out_ << "build " << escaped << ": phony "; | |
| if (target->has_dependency_output()) { | |
| path_output_.WriteFile(out_, target->dependency_output()); | |
| } | |
| out_ << std::endl; | |
| } |