Home | History | Annotate | Download | only in gn
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "tools/gn/ninja_binary_target_writer.h"
      6 
      7 #include <set>
      8 
      9 #include "base/strings/string_util.h"
     10 #include "tools/gn/config_values_extractors.h"
     11 #include "tools/gn/err.h"
     12 #include "tools/gn/escape.h"
     13 #include "tools/gn/string_utils.h"
     14 
     15 namespace {
     16 
     17 // Returns the proper escape options for writing compiler and linker flags.
     18 EscapeOptions GetFlagOptions() {
     19   EscapeOptions opts;
     20   opts.mode = ESCAPE_NINJA;
     21 
     22   // Some flag strings are actually multiple flags that expect to be just
     23   // added to the command line. We assume that quoting is done by the
     24   // buildfiles if it wants such things quoted.
     25   opts.inhibit_quoting = true;
     26 
     27   return opts;
     28 }
     29 
     30 struct DefineWriter {
     31   DefineWriter() {
     32     options.mode = ESCAPE_SHELL;
     33   }
     34 
     35   void operator()(const std::string& s, std::ostream& out) const {
     36     out << " -D";
     37     EscapeStringToStream(out, s, options);
     38   }
     39 
     40   EscapeOptions options;
     41 };
     42 
     43 struct IncludeWriter {
     44   IncludeWriter(PathOutput& path_output,
     45                 const NinjaHelper& h)
     46       : helper(h),
     47         path_output_(path_output),
     48         old_inhibit_quoting_(path_output.inhibit_quoting()) {
     49     // Inhibit quoting since we'll put quotes around the whole thing ourselves.
     50     // Since we're writing in NINJA escaping mode, this won't actually do
     51     // anything, but I think we may need to change to shell-and-then-ninja
     52     // escaping for this in the future.
     53     path_output_.set_inhibit_quoting(true);
     54   }
     55   ~IncludeWriter() {
     56     path_output_.set_inhibit_quoting(old_inhibit_quoting_);
     57   }
     58 
     59   void operator()(const SourceDir& d, std::ostream& out) const {
     60     out << " \"-I";
     61     // It's important not to include the trailing slash on directories or on
     62     // Windows it will be a backslash and the compiler might think we're
     63     // escaping the quote!
     64     path_output_.WriteDir(out, d, PathOutput::DIR_NO_LAST_SLASH);
     65     out << "\"";
     66   }
     67 
     68   const NinjaHelper& helper;
     69   PathOutput& path_output_;
     70   bool old_inhibit_quoting_;  // So we can put the PathOutput back.
     71 };
     72 
     73 Toolchain::ToolType GetToolTypeForTarget(const Target* target) {
     74   switch (target->output_type()) {
     75     case Target::STATIC_LIBRARY:
     76       return Toolchain::TYPE_ALINK;
     77     case Target::SHARED_LIBRARY:
     78       return Toolchain::TYPE_SOLINK;
     79     case Target::EXECUTABLE:
     80       return Toolchain::TYPE_LINK;
     81     default:
     82       return Toolchain::TYPE_NONE;
     83   }
     84 }
     85 
     86 }  // namespace
     87 
     88 NinjaBinaryTargetWriter::NinjaBinaryTargetWriter(const Target* target,
     89                                                  const Toolchain* toolchain,
     90                                                  std::ostream& out)
     91     : NinjaTargetWriter(target, toolchain, out),
     92       tool_type_(GetToolTypeForTarget(target)){
     93 }
     94 
     95 NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() {
     96 }
     97 
     98 void NinjaBinaryTargetWriter::Run() {
     99   WriteCompilerVars();
    100 
    101   std::vector<OutputFile> obj_files;
    102   WriteSources(&obj_files);
    103 
    104   if (target_->output_type() == Target::SOURCE_SET)
    105     WriteSourceSetStamp(obj_files);
    106   else
    107     WriteLinkerStuff(obj_files);
    108 }
    109 
    110 void NinjaBinaryTargetWriter::WriteCompilerVars() {
    111   // Defines.
    112   out_ << "defines =";
    113   RecursiveTargetConfigToStream<std::string>(target_, &ConfigValues::defines,
    114                                              DefineWriter(), out_);
    115   out_ << std::endl;
    116 
    117   // Include directories.
    118   out_ << "includes =";
    119   RecursiveTargetConfigToStream<SourceDir>(target_, &ConfigValues::include_dirs,
    120                                            IncludeWriter(path_output_, helper_),
    121                                            out_);
    122 
    123   out_ << std::endl;
    124 
    125   // C flags and friends.
    126   EscapeOptions flag_escape_options = GetFlagOptions();
    127 #define WRITE_FLAGS(name) \
    128     out_ << #name " ="; \
    129     RecursiveTargetConfigStringsToStream(target_, &ConfigValues::name, \
    130                                          flag_escape_options, out_); \
    131     out_ << std::endl;
    132 
    133   WRITE_FLAGS(cflags)
    134   WRITE_FLAGS(cflags_c)
    135   WRITE_FLAGS(cflags_cc)
    136   WRITE_FLAGS(cflags_objc)
    137   WRITE_FLAGS(cflags_objcc)
    138 
    139 #undef WRITE_FLAGS
    140 
    141   out_ << std::endl;
    142 }
    143 
    144 void NinjaBinaryTargetWriter::WriteSources(
    145     std::vector<OutputFile>* object_files) {
    146   const Target::FileList& sources = target_->sources();
    147   object_files->reserve(sources.size());
    148 
    149   std::string implicit_deps = GetSourcesImplicitDeps();
    150 
    151   for (size_t i = 0; i < sources.size(); i++) {
    152     const SourceFile& input_file = sources[i];
    153 
    154     SourceFileType input_file_type = GetSourceFileType(input_file,
    155                                                        settings_->target_os());
    156     if (input_file_type == SOURCE_UNKNOWN)
    157       continue;  // Skip unknown file types.
    158     std::string command =
    159         helper_.GetRuleForSourceType(settings_, input_file_type);
    160     if (command.empty())
    161       continue;  // Skip files not needing compilation.
    162 
    163     OutputFile output_file = helper_.GetOutputFileForSource(
    164         target_, input_file, input_file_type);
    165     object_files->push_back(output_file);
    166 
    167     out_ << "build ";
    168     path_output_.WriteFile(out_, output_file);
    169     out_ << ": " << command << " ";
    170     path_output_.WriteFile(out_, input_file);
    171     out_ << implicit_deps << std::endl;
    172   }
    173   out_ << std::endl;
    174 }
    175 
    176 void NinjaBinaryTargetWriter::WriteLinkerStuff(
    177     const std::vector<OutputFile>& object_files) {
    178   // Manifest file on Windows.
    179   // TODO(brettw) this seems not to be necessary for static libs, skip in
    180   // that case?
    181   OutputFile windows_manifest;
    182   if (settings_->IsWin()) {
    183     windows_manifest.value().assign(helper_.GetTargetOutputDir(target_));
    184     windows_manifest.value().append(target_->label().name());
    185     windows_manifest.value().append(".intermediate.manifest");
    186     out_ << "manifests = ";
    187     path_output_.WriteFile(out_, windows_manifest);
    188     out_ << std::endl;
    189   }
    190 
    191   const Toolchain::Tool& tool = toolchain_->GetTool(tool_type_);
    192   WriteLinkerFlags(tool, windows_manifest);
    193   WriteLibs(tool);
    194 
    195   // The external output file is the one that other libs depend on.
    196   OutputFile external_output_file = helper_.GetTargetOutputFile(target_);
    197 
    198   // The internal output file is the "main thing" we think we're making. In
    199   // the case of shared libraries, this is the shared library and the external
    200   // output file is the import library. In other cases, the internal one and
    201   // the external one are the same.
    202   OutputFile internal_output_file;
    203   if (target_->output_type() == Target::SHARED_LIBRARY) {
    204     if (settings_->IsWin()) {
    205       internal_output_file = OutputFile(target_->label().name() + ".dll");
    206     } else {
    207       internal_output_file = external_output_file;
    208     }
    209   } else {
    210     internal_output_file = external_output_file;
    211   }
    212 
    213   // In Python see "self.ninja.build(output, command, input,"
    214   WriteLinkCommand(external_output_file, internal_output_file, object_files);
    215 
    216   if (target_->output_type() == Target::SHARED_LIBRARY) {
    217     // The shared object name doesn't include a path.
    218     out_ << "  soname = ";
    219     out_ << FindFilename(&internal_output_file.value());
    220     out_ << std::endl;
    221 
    222     out_ << "  lib = ";
    223     path_output_.WriteFile(out_, internal_output_file);
    224     out_ << std::endl;
    225 
    226     if (settings_->IsWin()) {
    227       out_ << "  dll = ";
    228       path_output_.WriteFile(out_, internal_output_file);
    229       out_ << std::endl;
    230     }
    231 
    232     if (settings_->IsWin()) {
    233       out_ << "  implibflag = /IMPLIB:";
    234       path_output_.WriteFile(out_, external_output_file);
    235       out_ << std::endl;
    236     }
    237 
    238     // TODO(brettw) postbuild steps.
    239     if (settings_->IsMac())
    240       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)";
    241   }
    242 
    243   out_ << std::endl;
    244 }
    245 
    246 void NinjaBinaryTargetWriter::WriteLinkerFlags(
    247     const Toolchain::Tool& tool,
    248     const OutputFile& windows_manifest) {
    249   out_ << "ldflags =";
    250 
    251   // First the ldflags from the target and its config.
    252   EscapeOptions flag_options = GetFlagOptions();
    253   RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags,
    254                                        flag_options, out_);
    255 
    256   // Followed by library search paths that have been recursively pushed
    257   // through the dependency tree.
    258   const OrderedSet<SourceDir> all_lib_dirs = target_->all_lib_dirs();
    259   if (!all_lib_dirs.empty()) {
    260     // Since we're passing these on the command line to the linker and not
    261     // to Ninja, we need to do shell escaping.
    262     PathOutput lib_path_output(path_output_.current_dir(), ESCAPE_NINJA_SHELL,
    263                                true);
    264     for (size_t i = 0; i < all_lib_dirs.size(); i++) {
    265       out_ << " " << tool.lib_dir_prefix;
    266       lib_path_output.WriteDir(out_, all_lib_dirs[i],
    267                                PathOutput::DIR_NO_LAST_SLASH);
    268     }
    269   }
    270 
    271   // Append manifest flag on Windows to reference our file.
    272   // HACK ERASEME BRETTW FIXME
    273   if (settings_->IsWin()) {
    274     out_ << " /MANIFEST /ManifestFile:";
    275     path_output_.WriteFile(out_, windows_manifest);
    276   }
    277   out_ << std::endl;
    278 }
    279 
    280 void NinjaBinaryTargetWriter::WriteLibs(const Toolchain::Tool& tool) {
    281   out_ << "libs =";
    282 
    283   // Libraries that have been recursively pushed through the dependency tree.
    284   EscapeOptions lib_escape_opts;
    285   lib_escape_opts.mode = ESCAPE_NINJA_SHELL;
    286   const OrderedSet<std::string> all_libs = target_->all_libs();
    287   const std::string framework_ending(".framework");
    288   for (size_t i = 0; i < all_libs.size(); i++) {
    289     if (settings_->IsMac() && EndsWith(all_libs[i], framework_ending, false)) {
    290       // Special-case libraries ending in ".framework" on Mac. Add the
    291       // -framework switch and don't add the extension to the output.
    292       out_ << " -framework ";
    293       EscapeStringToStream(out_,
    294           all_libs[i].substr(0, all_libs[i].size() - framework_ending.size()),
    295           lib_escape_opts);
    296     } else {
    297       out_ << " " << tool.lib_prefix;
    298       EscapeStringToStream(out_, all_libs[i], lib_escape_opts);
    299     }
    300   }
    301   out_ << std::endl;
    302 }
    303 
    304 void NinjaBinaryTargetWriter::WriteLinkCommand(
    305     const OutputFile& external_output_file,
    306     const OutputFile& internal_output_file,
    307     const std::vector<OutputFile>& object_files) {
    308   out_ << "build ";
    309   path_output_.WriteFile(out_, internal_output_file);
    310   if (external_output_file != internal_output_file) {
    311     out_ << " ";
    312     path_output_.WriteFile(out_, external_output_file);
    313   }
    314   out_ << ": "
    315        << helper_.GetRulePrefix(target_->settings())
    316        << Toolchain::ToolTypeToName(tool_type_);
    317 
    318   std::set<OutputFile> extra_object_files;
    319   std::vector<const Target*> linkable_deps;
    320   std::vector<const Target*> non_linkable_deps;
    321   GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps);
    322 
    323   // Object files.
    324   for (size_t i = 0; i < object_files.size(); i++) {
    325     out_ << " ";
    326     path_output_.WriteFile(out_, object_files[i]);
    327   }
    328   for (std::set<OutputFile>::iterator i = extra_object_files.begin();
    329        i != extra_object_files.end(); ++i) {
    330     out_ << " ";
    331     path_output_.WriteFile(out_, *i);
    332   }
    333 
    334   // Libs.
    335   for (size_t i = 0; i < linkable_deps.size(); i++) {
    336     out_ << " ";
    337     path_output_.WriteFile(out_, helper_.GetTargetOutputFile(linkable_deps[i]));
    338   }
    339 
    340   // Append data dependencies as implicit dependencies.
    341   WriteImplicitDependencies(non_linkable_deps);
    342 
    343   out_ << std::endl;
    344 }
    345 
    346 void NinjaBinaryTargetWriter::WriteSourceSetStamp(
    347     const std::vector<OutputFile>& object_files) {
    348   // The stamp rule for source sets is generally not used, since targets that
    349   // depend on this will reference the object files directly. However, writing
    350   // this rule allows the user to type the name of the target and get a build
    351   // which can be convenient for development.
    352   out_ << "build ";
    353   path_output_.WriteFile(out_, helper_.GetTargetOutputFile(target_));
    354   out_ << ": "
    355        << helper_.GetRulePrefix(target_->settings())
    356        << "stamp";
    357 
    358   std::set<OutputFile> extra_object_files;
    359   std::vector<const Target*> linkable_deps;
    360   std::vector<const Target*> non_linkable_deps;
    361   GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps);
    362 
    363   // The classifier should never put extra object files in a source set:
    364   // any source sets that we depend on should appear in our non-linkable
    365   // deps instead.
    366   DCHECK(extra_object_files.empty());
    367 
    368   for (size_t i = 0; i < object_files.size(); i++) {
    369     out_ << " ";
    370     path_output_.WriteFile(out_, object_files[i]);
    371   }
    372 
    373   // Append data dependencies as implicit dependencies.
    374   WriteImplicitDependencies(non_linkable_deps);
    375 
    376   out_ << std::endl;
    377 }
    378 
    379 void NinjaBinaryTargetWriter::GetDeps(
    380     std::set<OutputFile>* extra_object_files,
    381     std::vector<const Target*>* linkable_deps,
    382     std::vector<const Target*>* non_linkable_deps) const {
    383   const LabelTargetVector& deps = target_->deps();
    384   const std::set<const Target*>& inherited = target_->inherited_libraries();
    385 
    386   // Normal deps.
    387   for (size_t i = 0; i < deps.size(); i++) {
    388     if (inherited.find(deps[i].ptr) != inherited.end())
    389       continue;  // Don't add dupes.
    390     ClassifyDependency(deps[i].ptr, extra_object_files,
    391                        linkable_deps, non_linkable_deps);
    392   }
    393 
    394   // Inherited libraries.
    395   for (std::set<const Target*>::const_iterator i = inherited.begin();
    396        i != inherited.end(); ++i) {
    397     ClassifyDependency(*i, extra_object_files,
    398                        linkable_deps, non_linkable_deps);
    399   }
    400 
    401   // Data deps.
    402   const LabelTargetVector& datadeps = target_->datadeps();
    403   for (size_t i = 0; i < datadeps.size(); i++)
    404     non_linkable_deps->push_back(datadeps[i].ptr);
    405 }
    406 
    407 void NinjaBinaryTargetWriter::ClassifyDependency(
    408     const Target* dep,
    409     std::set<OutputFile>* extra_object_files,
    410     std::vector<const Target*>* linkable_deps,
    411     std::vector<const Target*>* non_linkable_deps) const {
    412   // Only these types of outputs have libraries linked into them. Child deps of
    413   // static libraries get pushed up the dependency tree until one of these is
    414   // reached, and source sets don't link at all.
    415   bool can_link_libs =
    416       (target_->output_type() == Target::EXECUTABLE ||
    417        target_->output_type() == Target::SHARED_LIBRARY);
    418 
    419   if (dep->output_type() == Target::SOURCE_SET) {
    420     if (target_->output_type() == Target::SOURCE_SET) {
    421       // When a source set depends on another source set, add it as a data
    422       // dependency so if the user says "ninja second_source_set" it will
    423       // also compile the first (what you would expect) even though we'll
    424       // never do anything with the first one's files.
    425       non_linkable_deps->push_back(dep);
    426     } else {
    427       // Linking in a source set, copy its object files.
    428       for (size_t i = 0; i < dep->sources().size(); i++) {
    429         SourceFileType input_file_type = GetSourceFileType(
    430             dep->sources()[i], dep->settings()->target_os());
    431         if (input_file_type != SOURCE_UNKNOWN &&
    432             input_file_type != SOURCE_H) {
    433           // Note we need to specify the target as the source_set target
    434           // itself, since this is used to prefix the object file name.
    435           extra_object_files->insert(helper_.GetOutputFileForSource(
    436               dep, dep->sources()[i], input_file_type));
    437         }
    438       }
    439     }
    440   } else if (can_link_libs && dep->IsLinkable()) {
    441     linkable_deps->push_back(dep);
    442   } else {
    443     non_linkable_deps->push_back(dep);
    444   }
    445 }
    446 
    447 void NinjaBinaryTargetWriter::WriteImplicitDependencies(
    448     const std::vector<const Target*>& non_linkable_deps) {
    449   const std::vector<SourceFile>& data = target_->data();
    450   if (!non_linkable_deps.empty() || !data.empty()) {
    451     out_ << " ||";
    452 
    453     // Non-linkable targets.
    454     for (size_t i = 0; i < non_linkable_deps.size(); i++) {
    455       out_ << " ";
    456       path_output_.WriteFile(out_,
    457                              helper_.GetTargetOutputFile(non_linkable_deps[i]));
    458     }
    459 
    460     // Data files.
    461     const std::vector<SourceFile>& data = target_->data();
    462     for (size_t i = 0; i < data.size(); i++) {
    463       out_ << " ";
    464       path_output_.WriteFile(out_, data[i]);
    465     }
    466   }
    467 }
    468