blob: 627edb27a267ffbc733b4760ffa8d074dc29f5c9 [file] [log] [blame]
// 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 <stddef.h>
#include <fstream>
#include <map>
#include <sstream>
#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/pool.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 <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;
};
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
}
// 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(false) + "\n";
Err result(matches[0]->defined_from(), "Duplicate output file.",
"Two or more targets generate the same output:\n " +
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;
}
} // namespace
NinjaBuildWriter::NinjaBuildWriter(
const BuildSettings* build_settings,
const std::vector<const Settings*>& all_settings,
const Toolchain* default_toolchain,
const std::vector<const Target*>& default_toolchain_targets,
const std::vector<const Pool*>& all_pools,
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),
all_pools_(all_pools),
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();
WriteAllPools();
WriteSubninjas();
return WritePhonyAndAllRules(err);
}
// static
bool NinjaBuildWriter::RunAndWriteFile(
const BuildSettings* build_settings,
const std::vector<const Settings*>& all_settings,
const Toolchain* default_toolchain,
const std::vector<const Target*>& default_toolchain_targets,
const std::vector<const Pool*>& all_pools,
Err* err) {
ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja");
std::stringstream file;
std::stringstream depfile;
NinjaBuildWriter gen(build_settings, all_settings, default_toolchain,
default_toolchain_targets, all_pools, 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 (base::WriteFile(ninja_file_name, ninja_contents.data(),
static_cast<int>(ninja_contents.size())) !=
static_cast<int>(ninja_contents.size()))
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 (base::WriteFile(dep_file_name, dep_contents.data(),
static_cast<int>(dep_contents.size())) !=
static_cast<int>(dep_contents.size()))
return false;
return true;
}
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<base::FilePath> input_files;
g_scheduler->input_file_manager()->GetAllPhysicalInputFileNames(&input_files);
// Other files read by the build.
std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies();
// Sort the input files to order them deterministically.
// Additionally, remove duplicate filepaths that seem to creep in.
std::set<base::FilePath> 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::WriteAllPools() {
out_ << "pool link_pool\n"
<< " depth = " << default_toolchain_->concurrent_links() << std::endl
<< std::endl;
for (const Pool* pool : all_pools_) {
std::string pool_name = pool->GetNinjaName(default_toolchain_->label());
out_ << "pool " << pool_name << std::endl
<< " depth = " << pool->depth() << 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) {
// 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.
base::hash_set<std::string> written_rules;
written_rules.insert("all");
// 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;
// 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();
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;
}
// 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 auto& output : target->computed_outputs()) {
// 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();
// 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);
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());
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() &&
written_rules.insert(medium_name).second)
WritePhonyRule(target, medium_name);
}
// 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());
}
// 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_) {
out_ << " $\n ";
path_output_.WriteFile(out_, target->dependency_output_file());
}
}
out_ << 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 std::string& 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->dependency_output_file());
out_ << std::endl;
}