• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetSelfInvocationCommand(const BuildSettings * build_settings)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 
NinjaBuildWriter(const BuildSettings * build_settings,const std::vector<const Settings * > & all_settings,const std::vector<const Target * > & default_toolchain_targets,std::ostream & out,std::ostream & dep_out)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 
~NinjaBuildWriter()89 NinjaBuildWriter::~NinjaBuildWriter() {
90 }
91 
Run()92 void NinjaBuildWriter::Run() {
93   WriteNinjaRules();
94   WriteSubninjas();
95   WritePhonyAndAllRules();
96 }
97 
98 // static
RunAndWriteFile(const BuildSettings * build_settings,const std::vector<const Settings * > & all_settings,const std::vector<const Target * > & default_toolchain_targets)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 
WriteNinjaRules()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 
WriteSubninjas()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 
WritePhonyAndAllRules()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 
WritePhonyRule(const Target * target,const OutputFile & target_file,const std::string & phony_name)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