| Index: tools/gn/ninja_target_writer.cc
|
| diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..ed2d09d06aece53432548738e0d70f158e3630ba
|
| --- /dev/null
|
| +++ b/tools/gn/ninja_target_writer.cc
|
| @@ -0,0 +1,550 @@
|
| +// 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_target_writer.h"
|
| +
|
| +#include <fstream>
|
| +#include <sstream>
|
| +
|
| +#include "base/file_util.h"
|
| +#include "base/logging.h"
|
| +#include "base/strings/string_util.h"
|
| +#include "tools/gn/config_values_extractors.h"
|
| +#include "tools/gn/err.h"
|
| +#include "tools/gn/escape.h"
|
| +#include "tools/gn/file_template.h"
|
| +#include "tools/gn/location.h"
|
| +#include "tools/gn/path_output.h"
|
| +#include "tools/gn/scheduler.h"
|
| +#include "tools/gn/string_utils.h"
|
| +#include "tools/gn/target.h"
|
| +
|
| +namespace {
|
| +
|
| +static const char kCustomTargetSourceKey[] = "{{source}}";
|
| +static const char kCustomTargetSourceNamePartKey[] = "{{source_name_part}}";
|
| +
|
| +struct DefineWriter {
|
| + void operator()(const std::string& s, std::ostream& out) const {
|
| + out << " -D" << s;
|
| + }
|
| +};
|
| +
|
| +struct IncludeWriter {
|
| + IncludeWriter(PathOutput& path_output,
|
| + const NinjaHelper& h)
|
| + : helper(h),
|
| + path_output_(path_output),
|
| + old_inhibit_quoting_(path_output.inhibit_quoting()) {
|
| + // Inhibit quoting since we'll put quotes around the whole thing ourselves.
|
| + // Since we're writing in NINJA escaping mode, this won't actually do
|
| + // anything, but I think we may need to change to shell-and-then-ninja
|
| + // escaping for this in the future.
|
| + path_output_.set_inhibit_quoting(true);
|
| + }
|
| + ~IncludeWriter() {
|
| + path_output_.set_inhibit_quoting(old_inhibit_quoting_);
|
| + }
|
| +
|
| + void operator()(const SourceDir& d, std::ostream& out) const {
|
| + out << " \"-I";
|
| + // It's important not to include the trailing slash on directories or on
|
| + // Windows it will be a backslash and the compiler might think we're
|
| + // escaping the quote!
|
| + path_output_.WriteDir(out, d, PathOutput::DIR_NO_LAST_SLASH);
|
| + out << "\"";
|
| + }
|
| +
|
| + const NinjaHelper& helper;
|
| + PathOutput& path_output_;
|
| + bool old_inhibit_quoting_; // So we can put the PathOutput back.
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +NinjaTargetWriter::NinjaTargetWriter(const Target* target, std::ostream& out)
|
| + : settings_(target->settings()),
|
| + target_(target),
|
| + out_(out),
|
| + path_output_(settings_->build_settings()->build_dir(),
|
| + ESCAPE_NINJA, true),
|
| + helper_(settings_->build_settings()) {
|
| +}
|
| +
|
| +NinjaTargetWriter::~NinjaTargetWriter() {
|
| +}
|
| +
|
| +void NinjaTargetWriter::Run() {
|
| + out_ << "arch = environment.x86\n";
|
| +
|
| + if (target_->output_type() == Target::COPY_FILES) {
|
| + WriteCopyRules();
|
| + } else if (target_->output_type() == Target::CUSTOM) {
|
| + WriteCustomRules();
|
| + } else {
|
| + WriteCompilerVars();
|
| +
|
| + std::vector<OutputFile> obj_files;
|
| + WriteSources(&obj_files);
|
| +
|
| + WriteLinkerStuff(obj_files);
|
| + }
|
| +}
|
| +
|
| +// static
|
| +void NinjaTargetWriter::RunAndWriteFile(const Target* target) {
|
| + if (target->output_type() == Target::NONE)
|
| + return;
|
| +
|
| + const Settings* settings = target->settings();
|
| + NinjaHelper helper(settings->build_settings());
|
| +
|
| + base::FilePath ninja_file(settings->build_settings()->GetFullPath(
|
| + helper.GetNinjaFileForTarget(target).GetSourceFile(
|
| + settings->build_settings())));
|
| +
|
| + file_util::CreateDirectory(ninja_file.DirName());
|
| +
|
| + // It's rediculously faster to write to a string and then write that to
|
| + // disk in one operation than to use an fstream here.
|
| + std::stringstream file;
|
| + if (file.fail()) {
|
| + g_scheduler->FailWithError(
|
| + Err(Location(), "Error writing ninja file.",
|
| + "Unable to open \"" + FilePathToUTF8(ninja_file) + "\"\n"
|
| + "for writing."));
|
| + return;
|
| + }
|
| +
|
| + NinjaTargetWriter gen(target, file);
|
| + gen.Run();
|
| +
|
| + std::string contents = file.str();
|
| + file_util::WriteFile(ninja_file, contents.c_str(), contents.size());
|
| +}
|
| +
|
| +void NinjaTargetWriter::WriteCopyRules() {
|
| + // The dest dir should be inside the output dir so we can just remove the
|
| + // prefix and get ninja-relative paths.
|
| + const std::string& output_dir =
|
| + settings_->build_settings()->build_dir().value();
|
| + const std::string& dest_dir = target_->destdir().value();
|
| + DCHECK(StartsWithASCII(dest_dir, output_dir, true));
|
| + std::string relative_dest_dir(&dest_dir[output_dir.size()],
|
| + dest_dir.size() - output_dir.size());
|
| +
|
| + const Target::FileList& sources = target_->sources();
|
| + std::vector<OutputFile> dest_files;
|
| + dest_files.reserve(sources.size());
|
| +
|
| + // Write out rules for each file copied.
|
| + for (size_t i = 0; i < sources.size(); i++) {
|
| + const SourceFile& input_file = sources[i];
|
| +
|
| + // The files should have the same name but in the dest dir.
|
| + base::StringPiece name_part = FindFilename(&input_file.value());
|
| + OutputFile dest_file(relative_dest_dir);
|
| + AppendStringPiece(&dest_file.value(), name_part);
|
| +
|
| + dest_files.push_back(dest_file);
|
| +
|
| + out_ << "build ";
|
| + path_output_.WriteFile(out_, dest_file);
|
| + out_ << ": copy ";
|
| + path_output_.WriteFile(out_, input_file);
|
| + out_ << std::endl;
|
| + }
|
| +
|
| + // Write out the rule for the target to copy all of them.
|
| + out_ << std::endl << "build ";
|
| + path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_));
|
| + out_ << ": stamp";
|
| + for (size_t i = 0; i < dest_files.size(); i++) {
|
| + out_ << " ";
|
| + path_output_.WriteFile(out_, dest_files[i]);
|
| + }
|
| + out_ << std::endl;
|
| +
|
| + // TODO(brettw) need some kind of stamp file for depending on this, as well
|
| + // as order_only=prebuild.
|
| +}
|
| +
|
| +void NinjaTargetWriter::WriteCustomRules() {
|
| + // Make a unique name for this rule.
|
| + std::string target_label = target_->label().GetUserVisibleName(true);
|
| + std::string custom_rule_name(target_label);
|
| + ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name);
|
| + custom_rule_name.append("_rule");
|
| +
|
| + // Run the script from the dir of the BUILD file. This has no trailing
|
| + // slash.
|
| + const SourceDir& script_cd = target_->label().dir();
|
| + std::string script_cd_to_root = InvertDir(script_cd);
|
| + if (script_cd_to_root.empty()) {
|
| + script_cd_to_root = ".";
|
| + } else {
|
| + // Remove trailing slash
|
| + DCHECK(script_cd_to_root[script_cd_to_root.size() - 1] == '/');
|
| + script_cd_to_root.resize(script_cd_to_root.size() - 1);
|
| + }
|
| +
|
| + std::string script_relative_to_cd =
|
| + script_cd_to_root + target_->script().value();
|
| +
|
| + bool no_sources = target_->sources().empty();
|
| +
|
| + // Use a unique name for the response file when there are multiple build
|
| + // steps so that they don't stomp on each other.
|
| + std::string rspfile = custom_rule_name;
|
| + if (!no_sources)
|
| + rspfile += ".$unique_name";
|
| + rspfile += ".rsp";
|
| +
|
| + // First write the custom rule.
|
| + out_ << "rule " << custom_rule_name << std::endl;
|
| + out_ << " command = $pythonpath gyp-win-tool action-wrapper $arch "
|
| + << rspfile << " ";
|
| + path_output_.WriteDir(out_, script_cd, PathOutput::DIR_NO_LAST_SLASH);
|
| + out_ << std::endl;
|
| + out_ << " description = CUSTOM " << target_label << std::endl;
|
| + out_ << " restat = 1" << std::endl;
|
| + out_ << " rspfile = " << rspfile << std::endl;
|
| +
|
| + // The build command goes in the rsp file.
|
| + out_ << " rspfile_content = $pythonpath " << script_relative_to_cd;
|
| + for (size_t i = 0; i < target_->script_args().size(); i++) {
|
| + const std::string& arg = target_->script_args()[i];
|
| + out_ << " ";
|
| + WriteCustomArg(arg);
|
| + }
|
| + out_ << std::endl << std::endl;
|
| +
|
| + // Precompute the common dependencies for each step. This includes the
|
| + // script itself (changing the script should force a rebuild) and any data
|
| + // files.
|
| + std::ostringstream common_deps_stream;
|
| + path_output_.WriteFile(common_deps_stream, target_->script());
|
| + const Target::FileList& datas = target_->data();
|
| + for (size_t i = 0; i < datas.size(); i++) {
|
| + common_deps_stream << " ";
|
| + path_output_.WriteFile(common_deps_stream, datas[i]);
|
| + }
|
| + const std::string& common_deps = common_deps_stream.str();
|
| +
|
| + // Collects all output files for writing below.
|
| + std::vector<OutputFile> output_files;
|
| +
|
| + if (no_sources) {
|
| + // No sources, write a rule that invokes the script once with the
|
| + // outputs as outputs, and the data as inputs.
|
| + out_ << "build";
|
| + const Target::FileList& outputs = target_->outputs();
|
| + for (size_t i = 0; i < outputs.size(); i++) {
|
| + OutputFile output_path(
|
| + RemovePrefix(outputs[i].value(),
|
| + settings_->build_settings()->build_dir().value()));
|
| + output_files.push_back(output_path);
|
| + out_ << " ";
|
| + path_output_.WriteFile(out_, output_path);
|
| + }
|
| + out_ << ": " << custom_rule_name << " " << common_deps << std::endl;
|
| + } else {
|
| + // Write separate rules for each input source file.
|
| + WriteCustomSourceRules(custom_rule_name, common_deps, script_cd,
|
| + script_cd_to_root, &output_files);
|
| + }
|
| + out_ << std::endl;
|
| +
|
| + // Last write a stamp rule to collect all outputs.
|
| + out_ << "build ";
|
| + path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_));
|
| + out_ << ": stamp";
|
| + for (size_t i = 0; i < output_files.size(); i++) {
|
| + out_ << " ";
|
| + path_output_.WriteFile(out_, output_files[i]);
|
| + }
|
| + out_ << std::endl;
|
| +}
|
| +
|
| +void NinjaTargetWriter::WriteCustomArg(const std::string& arg) {
|
| + // This can be optimized if it's called a lot.
|
| + EscapeOptions options;
|
| + options.mode = ESCAPE_NINJA;
|
| + std::string output_str = EscapeString(arg, options);
|
| +
|
| + // Do this substitution after escaping our our $ will be escaped (which we
|
| + // don't want).
|
| + ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSource,
|
| + "${source}");
|
| + ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSourceNamePart,
|
| + "${source_name_part}");
|
| + out_ << output_str;
|
| +}
|
| +
|
| +void NinjaTargetWriter::WriteCustomSourceRules(
|
| + const std::string& custom_rule_name,
|
| + const std::string& common_deps,
|
| + const SourceDir& script_cd,
|
| + const std::string& script_cd_to_root,
|
| + std::vector<OutputFile>* output_files) {
|
| + // Construct the template for generating the output files from each source.
|
| + const Target::FileList& outputs = target_->outputs();
|
| + std::vector<std::string> output_template_args;
|
| + for (size_t i = 0; i < outputs.size(); i++) {
|
| + // All outputs should be in the output dir.
|
| + output_template_args.push_back(
|
| + RemovePrefix(outputs[i].value(),
|
| + settings_->build_settings()->build_dir().value()));
|
| + }
|
| + FileTemplate output_template(output_template_args);
|
| +
|
| + // Prevent re-allocating each time by initializing outside the loop.
|
| + std::vector<std::string> output_template_result;
|
| +
|
| + // Path output formatter for wrigin source paths passed to the script.
|
| + PathOutput script_source_path_output(script_cd, ESCAPE_SHELL, true);
|
| +
|
| + const Target::FileList& sources = target_->sources();
|
| + for (size_t i = 0; i < sources.size(); i++) {
|
| + // Write outputs for this source file computed by the template.
|
| + out_ << "build";
|
| + output_template.ApplyString(sources[i].value(), &output_template_result);
|
| + for (size_t out_i = 0; out_i < output_template_result.size(); out_i++) {
|
| + OutputFile output_path(output_template_result[out_i]);
|
| + output_files->push_back(output_path);
|
| + out_ << " ";
|
| + path_output_.WriteFile(out_, output_path);
|
| + }
|
| +
|
| + out_ << ": " << custom_rule_name
|
| + << " " << common_deps
|
| + << " ";
|
| + path_output_.WriteFile(out_, sources[i]);
|
| + out_ << std::endl;
|
| +
|
| + out_ << " unique_name = " << i << std::endl;
|
| +
|
| + // The source file here should be relative to the script directory since
|
| + // this is the variable passed to the script. Here we slightly abuse the
|
| + // OutputFile object by putting a non-output-relative path in it to signal
|
| + // that the PathWriter should not prepend directories.
|
| + out_ << " source = ";
|
| + script_source_path_output.WriteFile(out_, sources[i]);
|
| + out_ << std::endl;
|
| +
|
| + out_ << " source_name_part = "
|
| + << FindFilenameNoExtension(&sources[i].value()).as_string()
|
| + << std::endl;
|
| + }
|
| +}
|
| +
|
| +void NinjaTargetWriter::WriteCompilerVars() {
|
| + // Defines.
|
| + out_ << "defines =";
|
| + RecursiveTargetConfigToStream(target_, &ConfigValues::defines,
|
| + DefineWriter(), out_);
|
| + out_ << std::endl;
|
| +
|
| + // Includes.
|
| + out_ << "includes =";
|
| + RecursiveTargetConfigToStream(target_, &ConfigValues::includes,
|
| + IncludeWriter(path_output_, helper_), out_);
|
| +
|
| + out_ << std::endl;
|
| +
|
| + // C flags and friends.
|
| + out_ << "cflags =";
|
| + RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags, out_);
|
| + out_ << std::endl;
|
| + out_ << "cflags_c =";
|
| + RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_c, out_);
|
| + out_ << std::endl;
|
| + out_ << "cflags_cc =";
|
| + RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_cc, out_);
|
| + out_ << std::endl;
|
| +
|
| + out_ << std::endl;
|
| +}
|
| +
|
| +void NinjaTargetWriter::WriteSources(
|
| + std::vector<OutputFile>* object_files) {
|
| + const Target::FileList& sources = target_->sources();
|
| + object_files->reserve(sources.size());
|
| +
|
| + for (size_t i = 0; i < sources.size(); i++) {
|
| + const SourceFile& input_file = sources[i];
|
| +
|
| + SourceFileType input_file_type = GetSourceFileType(input_file,
|
| + settings_->target_os());
|
| + if (input_file_type == SOURCE_UNKNOWN)
|
| + continue; // Skip unknown file types.
|
| + const char* command = GetCommandForSourceType(input_file_type);
|
| + if (!command)
|
| + continue; // Skip files not needing compilation.
|
| +
|
| + OutputFile output_file = helper_.GetOutputFileForSource(
|
| + target_, input_file, input_file_type);
|
| + object_files->push_back(output_file);
|
| +
|
| + out_ << "build ";
|
| + path_output_.WriteFile(out_, output_file);
|
| + out_ << ": " << command << " ";
|
| + path_output_.WriteFile(out_, input_file);
|
| + out_ << std::endl;
|
| + }
|
| + out_ << std::endl;
|
| +}
|
| +
|
| +void NinjaTargetWriter::WriteLinkerStuff(
|
| + const std::vector<OutputFile>& object_files) {
|
| + // Manifest file on Windows.
|
| + // TODO(brettw) this seems not to be necessary for static libs, skip in
|
| + // that case?
|
| + OutputFile windows_manifest;
|
| + if (settings_->IsWin()) {
|
| + windows_manifest.value().assign(helper_.GetTargetOutputDir(target_));
|
| + windows_manifest.value().append(target_->label().name());
|
| + windows_manifest.value().append(".intermediate.manifest");
|
| + out_ << "manifests = ";
|
| + path_output_.WriteFile(out_, windows_manifest);
|
| + out_ << std::endl;
|
| + }
|
| +
|
| + // Linker flags, append manifest flag on Windows to reference our file.
|
| + out_ << "ldflags =";
|
| + RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags, out_);
|
| + if (settings_->IsWin())
|
| + out_ << " /MANIFEST /ManifestFile:";
|
| + path_output_.WriteFile(out_, windows_manifest);
|
| + { // HACK ERASEME BRETTW FIXME
|
| + out_ << " /DEBUG /MACHINE:X86 /LIBPATH:\"C:\\Program Files (x86)\\Windows Kits\\8.0\\Lib\\win8\\um\\x86\" /DELAYLOAD:dbghelp.dll /DELAYLOAD:dwmapi.dll /DELAYLOAD:shell32.dll /DELAYLOAD:uxtheme.dll /safeseh /dynamicbase /ignore:4199 /ignore:4221 /nxcompat /SUBSYSTEM:CONSOLE /INCREMENTAL /FIXED:NO /DYNAMICBASE:NO wininet.lib dnsapi.lib version.lib msimg32.lib ws2_32.lib usp10.lib psapi.lib dbghelp.lib winmm.lib shlwapi.lib kernel32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib user32.lib uuid.lib odbc32.lib odbccp32.lib delayimp.lib /NXCOMPAT";
|
| + }
|
| + out_ << std::endl;
|
| +
|
| + // Libraries to link.
|
| + out_ << "libs =" << std::endl;
|
| +
|
| + // The external output file is the one that other libs depend on.
|
| + OutputFile external_output_file = helper_.GetTargetOutputFile(target_);
|
| +
|
| + // The internal output file is the "main thing" we think we're making. In
|
| + // the case of shared libraries, this is the shared library and the external
|
| + // output file is the import library. In other cases, the internal one and
|
| + // the external one are the same.
|
| + OutputFile internal_output_file;
|
| + if (target_->output_type() == Target::SHARED_LIBRARY) {
|
| + if (settings_->IsWin()) {
|
| + internal_output_file = OutputFile(target_->label().name() + ".dll");
|
| + } else {
|
| + NOTREACHED(); // TODO(brettw) write this.
|
| + }
|
| + } else {
|
| + internal_output_file = external_output_file;
|
| + }
|
| +
|
| + // TODO(brettw) should we append data files to this?
|
| +
|
| + // In Python see "self.ninja.build(output, command, input,"
|
| + out_ << "build ";
|
| + path_output_.WriteFile(out_, internal_output_file);
|
| + if (external_output_file != internal_output_file) {
|
| + out_ << " ";
|
| + path_output_.WriteFile(out_, external_output_file);
|
| + }
|
| + out_ << ": " << GetCommandForTargetType();
|
| + for (size_t i = 0; i < object_files.size(); i++) {
|
| + out_ << " ";
|
| + path_output_.WriteFile(out_, object_files[i]);
|
| + }
|
| +
|
| + if (target_->output_type() == Target::EXECUTABLE ||
|
| + target_->output_type() == Target::SHARED_LIBRARY ||
|
| + target_->output_type() == Target::LOADABLE_MODULE) {
|
| + const std::vector<const Target*>& deps = target_->deps();
|
| + const std::set<const Target*>& inherited = target_->inherited_libraries();
|
| +
|
| + // Now append linkable libraries to the linker command.
|
| + for (size_t i = 0; i < deps.size(); i++) {
|
| + if (deps[i]->IsLinkable() &&
|
| + inherited.find(deps[i]) == inherited.end()) {
|
| + out_ << " ";
|
| + path_output_.WriteFile(out_,
|
| + helper_.GetTargetOutputFile(target_->deps()[i]));
|
| + }
|
| + }
|
| + for (std::set<const Target*>::const_iterator i = inherited.begin();
|
| + i != inherited.end(); ++i) {
|
| + out_ << " ";
|
| + path_output_.WriteFile(out_, helper_.GetTargetOutputFile(*i));
|
| + }
|
| + }
|
| + out_ << std::endl;
|
| +
|
| + if (target_->output_type() == Target::SHARED_LIBRARY) {
|
| + out_ << " soname = ";
|
| + path_output_.WriteFile(out_, internal_output_file);
|
| + out_ << std::endl;
|
| +
|
| + out_ << " lib = ";
|
| + path_output_.WriteFile(out_, internal_output_file);
|
| + out_ << std::endl;
|
| +
|
| + out_ << " dll = ";
|
| + path_output_.WriteFile(out_, internal_output_file);
|
| + out_ << std::endl;
|
| +
|
| + if (settings_->IsWin()) {
|
| + out_ << " implibflag = /IMPLIB:";
|
| + path_output_.WriteFile(out_, external_output_file);
|
| + out_ << std::endl;
|
| + }
|
| + }
|
| +
|
| + // TODO(brettw) postbuild steps here.
|
| +
|
| + out_ << std::endl;
|
| +}
|
| +
|
| +const char* NinjaTargetWriter::GetCommandForSourceType(
|
| + SourceFileType type) const {
|
| + if (type == SOURCE_C)
|
| + return "cc";
|
| + if (type == SOURCE_CC)
|
| + return "cxx";
|
| +
|
| + // TODO(brettw) asm files.
|
| +
|
| + if (settings_->IsMac()) {
|
| + if (type == SOURCE_M)
|
| + return "objc";
|
| + if (type == SOURCE_MM)
|
| + return "objcxx";
|
| + }
|
| +
|
| + if (settings_->IsWin()) {
|
| + if (type == SOURCE_RC)
|
| + return "rc";
|
| + }
|
| +
|
| + // TODO(brettw) stuff about "S" files on non-Windows.
|
| + return NULL;
|
| +}
|
| +
|
| +const char* NinjaTargetWriter::GetCommandForTargetType() const {
|
| + if (target_->output_type() == Target::NONE) {
|
| + NOTREACHED();
|
| + return "";
|
| + }
|
| +
|
| + if (target_->output_type() == Target::STATIC_LIBRARY) {
|
| + // TODO(brettw) stuff about standalong static libraryes on Unix in
|
| + // WriteTarget in the Python one, and lots of postbuild steps.
|
| + return "alink";
|
| + }
|
| +
|
| + if (target_->output_type() == Target::SHARED_LIBRARY)
|
| + return "solink";
|
| +
|
| + return "link";
|
| +}
|
|
|