diff options
Diffstat (limited to 'tools/gn/ninja_target_writer.cc')
-rw-r--r-- | tools/gn/ninja_target_writer.cc | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc new file mode 100644 index 0000000..ed2d09d --- /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"; +} |