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