普通文本  |  468行  |  16.35 KB

// 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_binary_target_writer.h"

#include <set>

#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/string_utils.h"

namespace {

// Returns the proper escape options for writing compiler and linker flags.
EscapeOptions GetFlagOptions() {
  EscapeOptions opts;
  opts.mode = ESCAPE_NINJA;

  // Some flag strings are actually multiple flags that expect to be just
  // added to the command line. We assume that quoting is done by the
  // buildfiles if it wants such things quoted.
  opts.inhibit_quoting = true;

  return opts;
}

struct DefineWriter {
  DefineWriter() {
    options.mode = ESCAPE_SHELL;
  }

  void operator()(const std::string& s, std::ostream& out) const {
    out << " -D";
    EscapeStringToStream(out, s, options);
  }

  EscapeOptions options;
};

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.
};

Toolchain::ToolType GetToolTypeForTarget(const Target* target) {
  switch (target->output_type()) {
    case Target::STATIC_LIBRARY:
      return Toolchain::TYPE_ALINK;
    case Target::SHARED_LIBRARY:
      return Toolchain::TYPE_SOLINK;
    case Target::EXECUTABLE:
      return Toolchain::TYPE_LINK;
    default:
      return Toolchain::TYPE_NONE;
  }
}

}  // namespace

NinjaBinaryTargetWriter::NinjaBinaryTargetWriter(const Target* target,
                                                 const Toolchain* toolchain,
                                                 std::ostream& out)
    : NinjaTargetWriter(target, toolchain, out),
      tool_type_(GetToolTypeForTarget(target)){
}

NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() {
}

void NinjaBinaryTargetWriter::Run() {
  WriteCompilerVars();

  std::vector<OutputFile> obj_files;
  WriteSources(&obj_files);

  if (target_->output_type() == Target::SOURCE_SET)
    WriteSourceSetStamp(obj_files);
  else
    WriteLinkerStuff(obj_files);
}

void NinjaBinaryTargetWriter::WriteCompilerVars() {
  // Defines.
  out_ << "defines =";
  RecursiveTargetConfigToStream<std::string>(target_, &ConfigValues::defines,
                                             DefineWriter(), out_);
  out_ << std::endl;

  // Include directories.
  out_ << "includes =";
  RecursiveTargetConfigToStream<SourceDir>(target_, &ConfigValues::include_dirs,
                                           IncludeWriter(path_output_, helper_),
                                           out_);

  out_ << std::endl;

  // C flags and friends.
  EscapeOptions flag_escape_options = GetFlagOptions();
#define WRITE_FLAGS(name) \
    out_ << #name " ="; \
    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::name, \
                                         flag_escape_options, out_); \
    out_ << std::endl;

  WRITE_FLAGS(cflags)
  WRITE_FLAGS(cflags_c)
  WRITE_FLAGS(cflags_cc)
  WRITE_FLAGS(cflags_objc)
  WRITE_FLAGS(cflags_objcc)

#undef WRITE_FLAGS

  out_ << std::endl;
}

void NinjaBinaryTargetWriter::WriteSources(
    std::vector<OutputFile>* object_files) {
  const Target::FileList& sources = target_->sources();
  object_files->reserve(sources.size());

  std::string implicit_deps = GetSourcesImplicitDeps();

  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.
    std::string command =
        helper_.GetRuleForSourceType(settings_, input_file_type);
    if (command.empty())
      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_ << implicit_deps << std::endl;
  }
  out_ << std::endl;
}

void NinjaBinaryTargetWriter::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;
  }

  const Toolchain::Tool& tool = toolchain_->GetTool(tool_type_);
  WriteLinkerFlags(tool, windows_manifest);
  WriteLibs(tool);

  // 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 {
      internal_output_file = external_output_file;
    }
  } else {
    internal_output_file = external_output_file;
  }

  // In Python see "self.ninja.build(output, command, input,"
  WriteLinkCommand(external_output_file, internal_output_file, object_files);

  if (target_->output_type() == Target::SHARED_LIBRARY) {
    // The shared object name doesn't include a path.
    out_ << "  soname = ";
    out_ << FindFilename(&internal_output_file.value());
    out_ << std::endl;

    out_ << "  lib = ";
    path_output_.WriteFile(out_, internal_output_file);
    out_ << std::endl;

    if (settings_->IsWin()) {
      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.
    if (settings_->IsMac())
      out_ << "  postbuilds = $ && (export BUILT_PRODUCTS_DIR=/Users/brettw/prj/src/out/gn; export CONFIGURATION=Debug; export DYLIB_INSTALL_NAME_BASE=@rpath; export EXECUTABLE_NAME=libbase.dylib; export EXECUTABLE_PATH=libbase.dylib; export FULL_PRODUCT_NAME=libbase.dylib; export LD_DYLIB_INSTALL_NAME=@rpath/libbase.dylib; export MACH_O_TYPE=mh_dylib; export PRODUCT_NAME=base; export PRODUCT_TYPE=com.apple.product-type.library.dynamic; export SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk; export SRCROOT=/Users/brettw/prj/src/out/gn/../../base; export SOURCE_ROOT=\"$${SRCROOT}\"; export TARGET_BUILD_DIR=/Users/brettw/prj/src/out/gn; export TEMP_DIR=\"$${TMPDIR}\"; (cd ../../base && ../build/mac/strip_from_xcode); G=$$?; ((exit $$G) || rm -rf libbase.dylib) && exit $$G)";
  }

  out_ << std::endl;
}

void NinjaBinaryTargetWriter::WriteLinkerFlags(
    const Toolchain::Tool& tool,
    const OutputFile& windows_manifest) {
  out_ << "ldflags =";

  // First the ldflags from the target and its config.
  EscapeOptions flag_options = GetFlagOptions();
  RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags,
                                       flag_options, out_);

  // Followed by library search paths that have been recursively pushed
  // through the dependency tree.
  const OrderedSet<SourceDir> all_lib_dirs = target_->all_lib_dirs();
  if (!all_lib_dirs.empty()) {
    // Since we're passing these on the command line to the linker and not
    // to Ninja, we need to do shell escaping.
    PathOutput lib_path_output(path_output_.current_dir(), ESCAPE_NINJA_SHELL,
                               true);
    for (size_t i = 0; i < all_lib_dirs.size(); i++) {
      out_ << " " << tool.lib_dir_prefix;
      lib_path_output.WriteDir(out_, all_lib_dirs[i],
                               PathOutput::DIR_NO_LAST_SLASH);
    }
  }

  // Append manifest flag on Windows to reference our file.
  // HACK ERASEME BRETTW FIXME
  if (settings_->IsWin()) {
    out_ << " /MANIFEST /ManifestFile:";
    path_output_.WriteFile(out_, windows_manifest);
  }
  out_ << std::endl;
}

void NinjaBinaryTargetWriter::WriteLibs(const Toolchain::Tool& tool) {
  out_ << "libs =";

  // Libraries that have been recursively pushed through the dependency tree.
  EscapeOptions lib_escape_opts;
  lib_escape_opts.mode = ESCAPE_NINJA_SHELL;
  const OrderedSet<std::string> all_libs = target_->all_libs();
  const std::string framework_ending(".framework");
  for (size_t i = 0; i < all_libs.size(); i++) {
    if (settings_->IsMac() && EndsWith(all_libs[i], framework_ending, false)) {
      // Special-case libraries ending in ".framework" on Mac. Add the
      // -framework switch and don't add the extension to the output.
      out_ << " -framework ";
      EscapeStringToStream(out_,
          all_libs[i].substr(0, all_libs[i].size() - framework_ending.size()),
          lib_escape_opts);
    } else {
      out_ << " " << tool.lib_prefix;
      EscapeStringToStream(out_, all_libs[i], lib_escape_opts);
    }
  }
  out_ << std::endl;
}

void NinjaBinaryTargetWriter::WriteLinkCommand(
    const OutputFile& external_output_file,
    const OutputFile& internal_output_file,
    const std::vector<OutputFile>& object_files) {
  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_ << ": "
       << helper_.GetRulePrefix(target_->settings())
       << Toolchain::ToolTypeToName(tool_type_);

  std::set<OutputFile> extra_object_files;
  std::vector<const Target*> linkable_deps;
  std::vector<const Target*> non_linkable_deps;
  GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps);

  // Object files.
  for (size_t i = 0; i < object_files.size(); i++) {
    out_ << " ";
    path_output_.WriteFile(out_, object_files[i]);
  }
  for (std::set<OutputFile>::iterator i = extra_object_files.begin();
       i != extra_object_files.end(); ++i) {
    out_ << " ";
    path_output_.WriteFile(out_, *i);
  }

  // Libs.
  for (size_t i = 0; i < linkable_deps.size(); i++) {
    out_ << " ";
    path_output_.WriteFile(out_, helper_.GetTargetOutputFile(linkable_deps[i]));
  }

  // Append data dependencies as implicit dependencies.
  WriteImplicitDependencies(non_linkable_deps);

  out_ << std::endl;
}

void NinjaBinaryTargetWriter::WriteSourceSetStamp(
    const std::vector<OutputFile>& object_files) {
  // The stamp rule for source sets is generally not used, since targets that
  // depend on this will reference the object files directly. However, writing
  // this rule allows the user to type the name of the target and get a build
  // which can be convenient for development.
  out_ << "build ";
  path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_));
  out_ << ": "
       << helper_.GetRulePrefix(target_->settings())
       << "stamp";

  std::set<OutputFile> extra_object_files;
  std::vector<const Target*> linkable_deps;
  std::vector<const Target*> non_linkable_deps;
  GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps);

  // The classifier should never put extra object files in a source set:
  // any source sets that we depend on should appear in our non-linkable
  // deps instead.
  DCHECK(extra_object_files.empty());

  for (size_t i = 0; i < object_files.size(); i++) {
    out_ << " ";
    path_output_.WriteFile(out_, object_files[i]);
  }

  // Append data dependencies as implicit dependencies.
  WriteImplicitDependencies(non_linkable_deps);

  out_ << std::endl;
}

void NinjaBinaryTargetWriter::GetDeps(
    std::set<OutputFile>* extra_object_files,
    std::vector<const Target*>* linkable_deps,
    std::vector<const Target*>* non_linkable_deps) const {
  const LabelTargetVector& deps = target_->deps();
  const std::set<const Target*>& inherited = target_->inherited_libraries();

  // Normal deps.
  for (size_t i = 0; i < deps.size(); i++) {
    if (inherited.find(deps[i].ptr) != inherited.end())
      continue;  // Don't add dupes.
    ClassifyDependency(deps[i].ptr, extra_object_files,
                       linkable_deps, non_linkable_deps);
  }

  // Inherited libraries.
  for (std::set<const Target*>::const_iterator i = inherited.begin();
       i != inherited.end(); ++i) {
    ClassifyDependency(*i, extra_object_files,
                       linkable_deps, non_linkable_deps);
  }

  // Data deps.
  const LabelTargetVector& datadeps = target_->datadeps();
  for (size_t i = 0; i < datadeps.size(); i++)
    non_linkable_deps->push_back(datadeps[i].ptr);
}

void NinjaBinaryTargetWriter::ClassifyDependency(
    const Target* dep,
    std::set<OutputFile>* extra_object_files,
    std::vector<const Target*>* linkable_deps,
    std::vector<const Target*>* non_linkable_deps) const {
  // Only these types of outputs have libraries linked into them. Child deps of
  // static libraries get pushed up the dependency tree until one of these is
  // reached, and source sets don't link at all.
  bool can_link_libs =
      (target_->output_type() == Target::EXECUTABLE ||
       target_->output_type() == Target::SHARED_LIBRARY);

  if (dep->output_type() == Target::SOURCE_SET) {
    if (target_->output_type() == Target::SOURCE_SET) {
      // When a source set depends on another source set, add it as a data
      // dependency so if the user says "ninja second_source_set" it will
      // also compile the first (what you would expect) even though we'll
      // never do anything with the first one's files.
      non_linkable_deps->push_back(dep);
    } else {
      // Linking in a source set, copy its object files.
      for (size_t i = 0; i < dep->sources().size(); i++) {
        SourceFileType input_file_type = GetSourceFileType(
            dep->sources()[i], dep->settings()->target_os());
        if (input_file_type != SOURCE_UNKNOWN &&
            input_file_type != SOURCE_H) {
          // Note we need to specify the target as the source_set target
          // itself, since this is used to prefix the object file name.
          extra_object_files->insert(helper_.GetOutputFileForSource(
              dep, dep->sources()[i], input_file_type));
        }
      }
    }
  } else if (can_link_libs && dep->IsLinkable()) {
    linkable_deps->push_back(dep);
  } else {
    non_linkable_deps->push_back(dep);
  }
}

void NinjaBinaryTargetWriter::WriteImplicitDependencies(
    const std::vector<const Target*>& non_linkable_deps) {
  const std::vector<SourceFile>& data = target_->data();
  if (!non_linkable_deps.empty() || !data.empty()) {
    out_ << " ||";

    // Non-linkable targets.
    for (size_t i = 0; i < non_linkable_deps.size(); i++) {
      out_ << " ";
      path_output_.WriteFile(out_,
                             helper_.GetTargetOutputFile(non_linkable_deps[i]));
    }

    // Data files.
    const std::vector<SourceFile>& data = target_->data();
    for (size_t i = 0; i < data.size(); i++) {
      out_ << " ";
      path_output_.WriteFile(out_, data[i]);
    }
  }
}