// 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 "tools/gn/ninja_build_writer.h" #include #include #include "base/command_line.h" #include "base/files/file_util.h" #include "base/path_service.h" #include "base/process/process_handle.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "tools/gn/build_settings.h" #include "tools/gn/err.h" #include "tools/gn/escape.h" #include "tools/gn/filesystem_utils.h" #include "tools/gn/input_file_manager.h" #include "tools/gn/ninja_utils.h" #include "tools/gn/scheduler.h" #include "tools/gn/switches.h" #include "tools/gn/target.h" #include "tools/gn/trace.h" #if defined(OS_WIN) #include #endif namespace { std::string GetSelfInvocationCommand(const BuildSettings* build_settings) { base::FilePath executable; PathService::Get(base::FILE_EXE, &executable); base::CommandLine cmdline(executable.NormalizePathSeparatorsTo('/')); cmdline.AppendArg("gen"); cmdline.AppendArg(build_settings->build_dir().value()); cmdline.AppendSwitchPath(std::string("--") + switches::kRoot, build_settings->root_path()); // 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 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::kArgs) { std::string escaped_value = EscapeString(FilePathToUTF8(i->second), escape_shell, nullptr); cmdline.AppendSwitchASCII(i->first, escaped_value); } } #if defined(OS_WIN) return base::WideToUTF8(cmdline.GetCommandLineString()); #else return cmdline.GetCommandLineString(); #endif } } // namespace NinjaBuildWriter::NinjaBuildWriter( const BuildSettings* build_settings, const std::vector& all_settings, const Toolchain* default_toolchain, const std::vector& default_toolchain_targets, std::ostream& out, std::ostream& dep_out) : build_settings_(build_settings), all_settings_(all_settings), 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() { } bool NinjaBuildWriter::Run(Err* err) { WriteNinjaRules(); WriteLinkPool(); WriteSubninjas(); return WritePhonyAndAllRules(err); } // static bool NinjaBuildWriter::RunAndWriteFile( const BuildSettings* build_settings, const std::vector& all_settings, const Toolchain* default_toolchain, const std::vector& default_toolchain_targets, 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"); 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"); return false; } NinjaBuildWriter gen(build_settings, all_settings, default_toolchain, default_toolchain_targets, file, depfile); return gen.Run(err); } void NinjaBuildWriter::WriteNinjaRules() { out_ << "rule gn\n"; out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n"; out_ << " description = Regenerating ninja files\n\n"; // This rule will regenerate the ninja files when any input file has changed. out_ << "build build.ninja: gn\n" << " generator = 1\n" << " depfile = build.ninja.d\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:"; std::vector input_files; g_scheduler->input_file_manager()->GetAllPhysicalInputFileNames(&input_files); for (const auto& input_file : input_files) dep_out_ << " " << FilePathToUTF8(input_file); // Other files read by the build. std::vector other_files = g_scheduler->GetGenDependencies(); for (const auto& other_file : other_files) dep_out_ << " " << FilePathToUTF8(other_file); out_ << std::endl; } void NinjaBuildWriter::WriteLinkPool() { out_ << "pool link_pool\n" << " depth = " << default_toolchain_->concurrent_links() << std::endl << std::endl; } void NinjaBuildWriter::WriteSubninjas() { for (const auto& elem : all_settings_) { out_ << "subninja "; path_output_.WriteFile(out_, GetNinjaFileForToolchain(elem)); out_ << std::endl; } out_ << std::endl; } 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 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 small_name_count; std::map exe_count; std::vector toplevel_targets; base::hash_set target_files; for (const auto& target : default_toolchain_targets_) { const Label& label = target->label(); small_name_count[label.name()]++; // 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. const std::string& dir_string = label.dir().value(); 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); } for (const auto& target : default_toolchain_targets_) { const Label& label = target->label(); OutputFile target_file(target->dependency_output_file()); // The output files may have leading "./" so normalize those away. NormalizePath(&target_file.value()); if (!target_files.insert(target_file.value()).second) { *err = Err(Location(), "Duplicate rules for " + target_file.value()); return false; } // 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); // 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); // 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); } // 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)) { WritePhonyRule(target, target_file, label.name(), &written_rules); } if (!all_rules.empty()) all_rules.append(" $\n "); all_rules.append(target_file.value()); } // 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); } } // 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; } if (default_target_exists) { out_ << "default default" << std::endl; } else if (!all_rules.empty()) { out_ << "default all" << std::endl; } return true; } void NinjaBuildWriter::WritePhonyRule(const Target* target, const OutputFile& target_file, const std::string& phony_name, std::set* 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); EscapeOptions ninja_escape; ninja_escape.mode = ESCAPE_NINJA; // Escape for special chars Ninja will handle. std::string escaped = EscapeString(phony_name, ninja_escape, nullptr); out_ << "build " << escaped << ": phony "; path_output_.WriteFile(out_, target_file); out_ << std::endl; }