ApplicationImpl cleanup, part 1:
[chromium-blink-merge.git] / tools / gn / ninja_build_writer.cc
blob591947d38cae9ca29918c11fc7697625fd56a229
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.
5 #include "tools/gn/ninja_build_writer.h"
7 #include <fstream>
8 #include <map>
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/err.h"
19 #include "tools/gn/escape.h"
20 #include "tools/gn/filesystem_utils.h"
21 #include "tools/gn/input_file_manager.h"
22 #include "tools/gn/ninja_utils.h"
23 #include "tools/gn/scheduler.h"
24 #include "tools/gn/switches.h"
25 #include "tools/gn/target.h"
26 #include "tools/gn/trace.h"
28 #if defined(OS_WIN)
29 #include <windows.h>
30 #endif
32 namespace {
34 std::string GetSelfInvocationCommand(const BuildSettings* build_settings) {
35 base::FilePath executable;
36 PathService::Get(base::FILE_EXE, &executable);
38 base::CommandLine cmdline(executable.NormalizePathSeparatorsTo('/'));
39 cmdline.AppendArg("gen");
40 cmdline.AppendArg(build_settings->build_dir().value());
41 cmdline.AppendSwitchPath(std::string("--") + switches::kRoot,
42 build_settings->root_path());
43 // Successful automatic invocations shouldn't print output.
44 cmdline.AppendSwitch(std::string("-") + switches::kQuiet);
46 EscapeOptions escape_shell;
47 escape_shell.mode = ESCAPE_NINJA_COMMAND;
48 #if defined(OS_WIN)
49 // The command line code quoting varies by platform. We have one string,
50 // possibly with spaces, that we want to quote. The Windows command line
51 // quotes again, so we don't want quoting. The Posix one doesn't.
52 escape_shell.inhibit_quoting = true;
53 #endif
55 const base::CommandLine& our_cmdline =
56 *base::CommandLine::ForCurrentProcess();
57 const base::CommandLine::SwitchMap& switches = our_cmdline.GetSwitches();
58 for (base::CommandLine::SwitchMap::const_iterator i = switches.begin();
59 i != switches.end(); ++i) {
60 // Only write arguments we haven't already written. Always skip "args"
61 // since those will have been written to the file and will be used
62 // implicitly in the future. Keeping --args would mean changes to the file
63 // would be ignored.
64 if (i->first != switches::kQuiet &&
65 i->first != switches::kRoot &&
66 i->first != switches::kArgs) {
67 std::string escaped_value =
68 EscapeString(FilePathToUTF8(i->second), escape_shell, nullptr);
69 cmdline.AppendSwitchASCII(i->first, escaped_value);
73 #if defined(OS_WIN)
74 return base::WideToUTF8(cmdline.GetCommandLineString());
75 #else
76 return cmdline.GetCommandLineString();
77 #endif
80 } // namespace
82 NinjaBuildWriter::NinjaBuildWriter(
83 const BuildSettings* build_settings,
84 const std::vector<const Settings*>& all_settings,
85 const Toolchain* default_toolchain,
86 const std::vector<const Target*>& default_toolchain_targets,
87 std::ostream& out,
88 std::ostream& dep_out)
89 : build_settings_(build_settings),
90 all_settings_(all_settings),
91 default_toolchain_(default_toolchain),
92 default_toolchain_targets_(default_toolchain_targets),
93 out_(out),
94 dep_out_(dep_out),
95 path_output_(build_settings->build_dir(),
96 build_settings->root_path_utf8(), ESCAPE_NINJA) {
99 NinjaBuildWriter::~NinjaBuildWriter() {
102 bool NinjaBuildWriter::Run(Err* err) {
103 WriteNinjaRules();
104 WriteLinkPool();
105 WriteSubninjas();
106 return WritePhonyAndAllRules(err);
109 // static
110 bool NinjaBuildWriter::RunAndWriteFile(
111 const BuildSettings* build_settings,
112 const std::vector<const Settings*>& all_settings,
113 const Toolchain* default_toolchain,
114 const std::vector<const Target*>& default_toolchain_targets,
115 Err* err) {
116 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja");
118 base::FilePath ninja_file(build_settings->GetFullPath(
119 SourceFile(build_settings->build_dir().value() + "build.ninja")));
120 base::CreateDirectory(ninja_file.DirName());
122 std::ofstream file;
123 file.open(FilePathToUTF8(ninja_file).c_str(),
124 std::ios_base::out | std::ios_base::binary);
125 if (file.fail()) {
126 *err = Err(Location(), "Couldn't open build.ninja for writing");
127 return false;
130 std::ofstream depfile;
131 depfile.open((FilePathToUTF8(ninja_file) + ".d").c_str(),
132 std::ios_base::out | std::ios_base::binary);
133 if (depfile.fail()) {
134 *err = Err(Location(), "Couldn't open depfile for writing");
135 return false;
138 NinjaBuildWriter gen(build_settings, all_settings, default_toolchain,
139 default_toolchain_targets, file, depfile);
140 return gen.Run(err);
143 void NinjaBuildWriter::WriteNinjaRules() {
144 out_ << "rule gn\n";
145 out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n";
146 out_ << " description = Regenerating ninja files\n\n";
148 // This rule will regenerate the ninja files when any input file has changed.
149 out_ << "build build.ninja: gn\n"
150 << " generator = 1\n"
151 << " depfile = build.ninja.d\n";
153 // Input build files. These go in the ".d" file. If we write them as
154 // dependencies in the .ninja file itself, ninja will expect the files to
155 // exist and will error if they don't. When files are listed in a depfile,
156 // missing files are ignored.
157 dep_out_ << "build.ninja:";
158 std::vector<base::FilePath> input_files;
159 g_scheduler->input_file_manager()->GetAllPhysicalInputFileNames(&input_files);
160 for (const auto& input_file : input_files)
161 dep_out_ << " " << FilePathToUTF8(input_file);
163 // Other files read by the build.
164 std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies();
165 for (const auto& other_file : other_files)
166 dep_out_ << " " << FilePathToUTF8(other_file);
168 out_ << std::endl;
171 void NinjaBuildWriter::WriteLinkPool() {
172 out_ << "pool link_pool\n"
173 << " depth = " << default_toolchain_->concurrent_links() << std::endl
174 << std::endl;
177 void NinjaBuildWriter::WriteSubninjas() {
178 for (const auto& elem : all_settings_) {
179 out_ << "subninja ";
180 path_output_.WriteFile(out_, GetNinjaFileForToolchain(elem));
181 out_ << std::endl;
183 out_ << std::endl;
186 bool NinjaBuildWriter::WritePhonyAndAllRules(Err* err) {
187 std::string all_rules;
189 // Track rules as we generate them so we don't accidentally write a phony
190 // rule that collides with something else.
191 // GN internally generates an "all" target, so don't duplicate it.
192 std::set<std::string> written_rules;
193 written_rules.insert("all");
195 // Write phony rules for all uniquely-named targets in the default toolchain.
196 // Don't do other toolchains or we'll get naming conflicts, and if the name
197 // isn't unique, also skip it. The exception is for the toplevel targets
198 // which we also find.
199 std::map<std::string, int> small_name_count;
200 std::map<std::string, int> exe_count;
201 std::vector<const Target*> toplevel_targets;
202 base::hash_set<std::string> target_files;
203 for (const auto& target : default_toolchain_targets_) {
204 const Label& label = target->label();
205 small_name_count[label.name()]++;
207 // Look for targets with a name of the form
208 // dir = "//foo/", name = "foo"
209 // i.e. where the target name matches the top level directory. We will
210 // always write phony rules for these even if there is another target with
211 // the same short name.
212 const std::string& dir_string = label.dir().value();
213 if (dir_string.size() == label.name().size() + 3 && // Size matches.
214 dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning.
215 dir_string[dir_string.size() - 1] == '/' && // "/" at end.
216 dir_string.compare(2, label.name().size(), label.name()) == 0)
217 toplevel_targets.push_back(target);
219 // Look for executables; later we will generate phony rules for them
220 // even if there are non-executable targets with the same name.
221 if (target->output_type() == Target::EXECUTABLE)
222 exe_count[label.name()]++;
224 // Add the files to the list of generated targets so we don't write phony
225 // rules that collide.
226 std::string target_file(target->dependency_output_file().value());
227 NormalizePath(&target_file);
228 written_rules.insert(target_file);
231 for (const auto& target : default_toolchain_targets_) {
232 const Label& label = target->label();
233 OutputFile target_file(target->dependency_output_file());
234 // The output files may have leading "./" so normalize those away.
235 NormalizePath(&target_file.value());
236 if (!target_files.insert(target_file.value()).second) {
237 *err = Err(Location(), "Duplicate rules for " + target_file.value());
238 return false;
241 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz".
242 std::string long_name = label.GetUserVisibleName(false);
243 base::TrimString(long_name, "/", &long_name);
244 WritePhonyRule(target, target_file, long_name, &written_rules);
246 // Write the directory name with no target name if they match
247 // (e.g. "//foo/bar:bar" -> "foo/bar").
248 if (FindLastDirComponent(label.dir()) == label.name()) {
249 std::string medium_name = DirectoryWithNoLastSlash(label.dir());
250 base::TrimString(medium_name, "/", &medium_name);
251 // That may have generated a name the same as the short name of the
252 // target which we already wrote.
253 if (medium_name != label.name())
254 WritePhonyRule(target, target_file, medium_name, &written_rules);
257 // Write short names for ones which are either completely unique or there
258 // at least only one of them in the default toolchain that is an exe.
259 if (small_name_count[label.name()] == 1 ||
260 (target->output_type() == Target::EXECUTABLE &&
261 exe_count[label.name()] == 1)) {
262 WritePhonyRule(target, target_file, label.name(), &written_rules);
265 if (!all_rules.empty())
266 all_rules.append(" $\n ");
267 all_rules.append(target_file.value());
270 // Pick up phony rules for the toplevel targets with non-unique names (which
271 // would have been skipped in the above loop).
272 for (const auto& toplevel_target : toplevel_targets) {
273 if (small_name_count[toplevel_target->label().name()] > 1) {
274 WritePhonyRule(toplevel_target, toplevel_target->dependency_output_file(),
275 toplevel_target->label().name(), &written_rules);
279 // Figure out if the BUILD file wants to declare a custom "default"
280 // target (rather than building 'all' by default). By convention
281 // we use group("default") but it doesn't have to be a group.
282 bool default_target_exists = false;
283 for (const auto& target : default_toolchain_targets_) {
284 const Label& label = target->label();
285 if (label.dir().value() == "//" && label.name() == "default")
286 default_target_exists = true;
289 if (!all_rules.empty()) {
290 out_ << "\nbuild all: phony " << all_rules << std::endl;
293 if (default_target_exists) {
294 out_ << "default default" << std::endl;
295 } else if (!all_rules.empty()) {
296 out_ << "default all" << std::endl;
299 return true;
302 void NinjaBuildWriter::WritePhonyRule(const Target* target,
303 const OutputFile& target_file,
304 const std::string& phony_name,
305 std::set<std::string>* written_rules) {
306 if (target_file.value() == phony_name)
307 return; // No need for a phony rule.
309 if (written_rules->find(phony_name) != written_rules->end())
310 return; // Already exists.
311 written_rules->insert(phony_name);
313 EscapeOptions ninja_escape;
314 ninja_escape.mode = ESCAPE_NINJA;
316 // Escape for special chars Ninja will handle.
317 std::string escaped = EscapeString(phony_name, ninja_escape, nullptr);
319 out_ << "build " << escaped << ": phony ";
320 path_output_.WriteFile(out_, target_file);
321 out_ << std::endl;