// 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 #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 } 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 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& all_targets, const OutputFile& bad_output) { std::vector matches; for (const Target* target : all_targets) { if (GetTargetOutputFile(target) == bad_output) matches.push_back(target); } // 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(false) + "\n"; Err result(matches[0]->defined_from(), "Duplicate output file.", "Two or more targets generate the same output:\n " + bad_output.value() + "\n" "This is normally the result of either overriding the output name or\n" "having two shared libraries or executables in different directories\n" "with the same name (since all such targets will be written to the root\n" "output directory).\n\nCollisions:\n" + matches_string); for (size_t i = 1; i < matches.size(); i++) result.AppendSubErr(Err(matches[i]->defined_from(), "Collision.")); return result; } } // 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); // Other files read by the build. std::vector other_files = g_scheduler->GetGenDependencies(); // Sort the input files to order them deterministically. // Additionally, remove duplicate filepaths that seem to creep in. std::set fileset(input_files.begin(), input_files.end()); fileset.insert(other_files.begin(), other_files.end()); for (const auto& other_file : fileset) 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 = GetTargetOutputFile(target); if (!target_files.insert(target_file.value()).second) { *err = GetDuplicateOutputError(default_toolchain_targets_, target_file); 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)) { // 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()); } // 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; }