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_build_writer.h"
      6 
      7 #include <fstream>
      8 #include <map>
      9 
     10 #include "base/command_line.h"
     11 #include "base/files/file_util.h"
     12 #include "base/path_service.h"
     13 #include "base/process/process_handle.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "build/build_config.h"
     17 #include "tools/gn/build_settings.h"
     18 #include "tools/gn/escape.h"
     19 #include "tools/gn/filesystem_utils.h"
     20 #include "tools/gn/input_file_manager.h"
     21 #include "tools/gn/ninja_utils.h"
     22 #include "tools/gn/scheduler.h"
     23 #include "tools/gn/target.h"
     24 #include "tools/gn/trace.h"
     25 
     26 #if defined(OS_WIN)
     27 #include <windows.h>
     28 #endif
     29 
     30 namespace {
     31 
     32 std::string GetSelfInvocationCommand(const BuildSettings* build_settings) {
     33   base::FilePath executable;
     34   PathService::Get(base::FILE_EXE, &executable);
     35 
     36   CommandLine cmdline(executable.NormalizePathSeparatorsTo('/'));
     37   cmdline.AppendArg("gen");
     38   cmdline.AppendArg(build_settings->build_dir().value());
     39   cmdline.AppendSwitchPath("--root", build_settings->root_path());
     40   cmdline.AppendSwitch("-q");  // Don't write output.
     41 
     42   EscapeOptions escape_shell;
     43   escape_shell.mode = ESCAPE_NINJA_COMMAND;
     44 #if defined(OS_WIN)
     45   // The command line code quoting varies by platform. We have one string,
     46   // possibly with spaces, that we want to quote. The Windows command line
     47   // quotes again, so we don't want quoting. The Posix one doesn't.
     48   escape_shell.inhibit_quoting = true;
     49 #endif
     50 
     51   const CommandLine& our_cmdline = *CommandLine::ForCurrentProcess();
     52   const CommandLine::SwitchMap& switches = our_cmdline.GetSwitches();
     53   for (CommandLine::SwitchMap::const_iterator i = switches.begin();
     54        i != switches.end(); ++i) {
     55     // Only write arguments we haven't already written. Always skip "args"
     56     // since those will have been written to the file and will be used
     57     // implicitly in the future. Keeping --args would mean changes to the file
     58     // would be ignored.
     59     if (i->first != "q" && i->first != "root" && i->first != "args") {
     60       std::string escaped_value =
     61           EscapeString(FilePathToUTF8(i->second), escape_shell, NULL);
     62       cmdline.AppendSwitchASCII(i->first, escaped_value);
     63     }
     64   }
     65 
     66 #if defined(OS_WIN)
     67   return base::WideToUTF8(cmdline.GetCommandLineString());
     68 #else
     69   return cmdline.GetCommandLineString();
     70 #endif
     71 }
     72 
     73 }  // namespace
     74 
     75 NinjaBuildWriter::NinjaBuildWriter(
     76     const BuildSettings* build_settings,
     77     const std::vector<const Settings*>& all_settings,
     78     const Toolchain* default_toolchain,
     79     const std::vector<const Target*>& default_toolchain_targets,
     80     std::ostream& out,
     81     std::ostream& dep_out)
     82     : build_settings_(build_settings),
     83       all_settings_(all_settings),
     84       default_toolchain_(default_toolchain),
     85       default_toolchain_targets_(default_toolchain_targets),
     86       out_(out),
     87       dep_out_(dep_out),
     88       path_output_(build_settings->build_dir(), ESCAPE_NINJA) {
     89 }
     90 
     91 NinjaBuildWriter::~NinjaBuildWriter() {
     92 }
     93 
     94 void NinjaBuildWriter::Run() {
     95   WriteNinjaRules();
     96   WriteLinkPool();
     97   WriteSubninjas();
     98   WritePhonyAndAllRules();
     99 }
    100 
    101 // static
    102 bool NinjaBuildWriter::RunAndWriteFile(
    103     const BuildSettings* build_settings,
    104     const std::vector<const Settings*>& all_settings,
    105     const Toolchain* default_toolchain,
    106     const std::vector<const Target*>& default_toolchain_targets) {
    107   ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja");
    108 
    109   base::FilePath ninja_file(build_settings->GetFullPath(
    110       SourceFile(build_settings->build_dir().value() + "build.ninja")));
    111   base::CreateDirectory(ninja_file.DirName());
    112 
    113   std::ofstream file;
    114   file.open(FilePathToUTF8(ninja_file).c_str(),
    115             std::ios_base::out | std::ios_base::binary);
    116   if (file.fail())
    117     return false;
    118 
    119   std::ofstream depfile;
    120   depfile.open((FilePathToUTF8(ninja_file) + ".d").c_str(),
    121                std::ios_base::out | std::ios_base::binary);
    122   if (depfile.fail())
    123     return false;
    124 
    125   NinjaBuildWriter gen(build_settings, all_settings, default_toolchain,
    126                        default_toolchain_targets, file, depfile);
    127   gen.Run();
    128   return true;
    129 }
    130 
    131 void NinjaBuildWriter::WriteNinjaRules() {
    132   out_ << "rule gn\n";
    133   out_ << "  command = " << GetSelfInvocationCommand(build_settings_) << "\n";
    134   out_ << "  description = Regenerating ninja files\n\n";
    135 
    136   // This rule will regenerate the ninja files when any input file has changed.
    137   out_ << "build build.ninja: gn\n"
    138        << "  generator = 1\n"
    139        << "  depfile = build.ninja.d\n";
    140 
    141   // Input build files. These go in the ".d" file. If we write them as
    142   // dependencies in the .ninja file itself, ninja will expect the files to
    143   // exist and will error if they don't. When files are listed in a depfile,
    144   // missing files are ignored.
    145   dep_out_ << "build.ninja:";
    146   std::vector<base::FilePath> input_files;
    147   g_scheduler->input_file_manager()->GetAllPhysicalInputFileNames(&input_files);
    148   for (size_t i = 0; i < input_files.size(); i++)
    149     dep_out_ << " " << FilePathToUTF8(input_files[i]);
    150 
    151   // Other files read by the build.
    152   std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies();
    153   for (size_t i = 0; i < other_files.size(); i++)
    154     dep_out_ << " " << FilePathToUTF8(other_files[i]);
    155 
    156   out_ << std::endl;
    157 }
    158 
    159 void NinjaBuildWriter::WriteLinkPool() {
    160   out_ << "pool link_pool\n"
    161        << "  depth = " << default_toolchain_->concurrent_links() << std::endl
    162        << std::endl;
    163 }
    164 
    165 void NinjaBuildWriter::WriteSubninjas() {
    166   for (size_t i = 0; i < all_settings_.size(); i++) {
    167     out_ << "subninja ";
    168     path_output_.WriteFile(out_, GetNinjaFileForToolchain(all_settings_[i]));
    169     out_ << std::endl;
    170   }
    171   out_ << std::endl;
    172 }
    173 
    174 void NinjaBuildWriter::WritePhonyAndAllRules() {
    175   std::string all_rules;
    176 
    177   // Write phony rules for all uniquely-named targets in the default toolchain.
    178   // Don't do other toolchains or we'll get naming conflicts, and if the name
    179   // isn't unique, also skip it. The exception is for the toplevel targets
    180   // which we also find.
    181   std::map<std::string, int> small_name_count;
    182   std::vector<const Target*> toplevel_targets;
    183   for (size_t i = 0; i < default_toolchain_targets_.size(); i++) {
    184     const Target* target = default_toolchain_targets_[i];
    185     const Label& label = target->label();
    186     small_name_count[label.name()]++;
    187 
    188     // Look for targets with a name of the form
    189     //   dir = "//foo/", name = "foo"
    190     // i.e. where the target name matches the top level directory. We will
    191     // always write phony rules for these even if there is another target with
    192     // the same short name.
    193     const std::string& dir_string = label.dir().value();
    194     if (dir_string.size() == label.name().size() + 3 &&  // Size matches.
    195         dir_string[0] == '/' && dir_string[1] == '/' &&  // "//" at beginning.
    196         dir_string[dir_string.size() - 1] == '/' &&  // "/" at end.
    197         dir_string.compare(2, label.name().size(), label.name()) == 0)
    198       toplevel_targets.push_back(target);
    199   }
    200 
    201   for (size_t i = 0; i < default_toolchain_targets_.size(); i++) {
    202     const Target* target = default_toolchain_targets_[i];
    203     const Label& label = target->label();
    204     OutputFile target_file(target->dependency_output_file());
    205     // The output files may have leading "./" so normalize those away.
    206     NormalizePath(&target_file.value());
    207 
    208     // Write the long name "foo/bar:baz" for the target "//foo/bar:baz".
    209     std::string long_name = label.GetUserVisibleName(false);
    210     base::TrimString(long_name, "/", &long_name);
    211     WritePhonyRule(target, target_file, long_name);
    212 
    213     // Write the directory name with no target name if they match
    214     // (e.g. "//foo/bar:bar" -> "foo/bar").
    215     if (FindLastDirComponent(label.dir()) == label.name()) {
    216       std::string medium_name =  DirectoryWithNoLastSlash(label.dir());
    217       base::TrimString(medium_name, "/", &medium_name);
    218       // That may have generated a name the same as the short name of the
    219       // target which we already wrote.
    220       if (medium_name != label.name())
    221         WritePhonyRule(target, target_file, medium_name);
    222     }
    223 
    224     // Write short names for ones which are unique.
    225     if (small_name_count[label.name()] == 1)
    226       WritePhonyRule(target, target_file, label.name());
    227 
    228     if (!all_rules.empty())
    229       all_rules.append(" $\n    ");
    230     all_rules.append(target_file.value());
    231   }
    232 
    233   // Pick up phony rules for the toplevel targets with non-unique names (which
    234   // would have been skipped in the above loop).
    235   for (size_t i = 0; i < toplevel_targets.size(); i++) {
    236     if (small_name_count[toplevel_targets[i]->label().name()] > 1) {
    237       const Target* target = toplevel_targets[i];
    238       WritePhonyRule(target, target->dependency_output_file(),
    239                      target->label().name());
    240     }
    241   }
    242 
    243   if (!all_rules.empty()) {
    244     out_ << "\nbuild all: phony " << all_rules << std::endl;
    245     out_ << "default all" << std::endl;
    246   }
    247 }
    248 
    249 void NinjaBuildWriter::WritePhonyRule(const Target* target,
    250                                       const OutputFile& target_file,
    251                                       const std::string& phony_name) {
    252   if (target_file.value() == phony_name)
    253     return;  // No need for a phony rule.
    254 
    255   EscapeOptions ninja_escape;
    256   ninja_escape.mode = ESCAPE_NINJA;
    257 
    258   // Escape for special chars Ninja will handle.
    259   std::string escaped = EscapeString(phony_name, ninja_escape, NULL);
    260 
    261   out_ << "build " << escaped << ": phony ";
    262   path_output_.WriteFile(out_, target_file);
    263   out_ << std::endl;
    264 }
    265