[refactor] More post-NSS WebCrypto cleanups (utility functions).
[chromium-blink-merge.git] / tools / gn / ninja_build_writer.cc
blobd27ebdcce66420b813f64cafbbdffc505809911b
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 OutputFile GetTargetOutputFile(const Target* target) {
81 OutputFile result(target->dependency_output_file());
83 // The output files may have leading "./" so normalize those away.
84 NormalizePath(&result.value());
85 return result;
88 // Given an output that appears more than once, generates an error message
89 // that describes the problem and which targets generate it.
90 Err GetDuplicateOutputError(const std::vector<const Target*>& all_targets,
91 const OutputFile& bad_output) {
92 std::vector<const Target*> matches;
93 for (const Target* target : all_targets) {
94 if (GetTargetOutputFile(target) == bad_output)
95 matches.push_back(target);
98 // There should always be at least two targets generating this file for this
99 // function to be called in the first place.
100 DCHECK(matches.size() >= 2);
101 std::string matches_string;
102 for (const Target* target : matches)
103 matches_string += " " + target->label().GetUserVisibleName(false) + "\n";
105 Err result(matches[0]->defined_from(), "Duplicate output file.",
106 "Two or more targets generate the same output:\n " +
107 bad_output.value() + "\n"
108 "This is normally the result of either overriding the output name or\n"
109 "having two shared libraries or executables in different directories\n"
110 "with the same name (since all such targets will be written to the root\n"
111 "output directory).\n\nCollisions:\n" + matches_string);
112 for (size_t i = 1; i < matches.size(); i++)
113 result.AppendSubErr(Err(matches[i]->defined_from(), "Collision."));
114 return result;
117 } // namespace
119 NinjaBuildWriter::NinjaBuildWriter(
120 const BuildSettings* build_settings,
121 const std::vector<const Settings*>& all_settings,
122 const Toolchain* default_toolchain,
123 const std::vector<const Target*>& default_toolchain_targets,
124 std::ostream& out,
125 std::ostream& dep_out)
126 : build_settings_(build_settings),
127 all_settings_(all_settings),
128 default_toolchain_(default_toolchain),
129 default_toolchain_targets_(default_toolchain_targets),
130 out_(out),
131 dep_out_(dep_out),
132 path_output_(build_settings->build_dir(),
133 build_settings->root_path_utf8(), ESCAPE_NINJA) {
136 NinjaBuildWriter::~NinjaBuildWriter() {
139 bool NinjaBuildWriter::Run(Err* err) {
140 WriteNinjaRules();
141 WriteLinkPool();
142 WriteSubninjas();
143 return WritePhonyAndAllRules(err);
146 // static
147 bool NinjaBuildWriter::RunAndWriteFile(
148 const BuildSettings* build_settings,
149 const std::vector<const Settings*>& all_settings,
150 const Toolchain* default_toolchain,
151 const std::vector<const Target*>& default_toolchain_targets,
152 Err* err) {
153 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja");
155 base::FilePath ninja_file(build_settings->GetFullPath(
156 SourceFile(build_settings->build_dir().value() + "build.ninja")));
157 base::CreateDirectory(ninja_file.DirName());
159 std::ofstream file;
160 file.open(FilePathToUTF8(ninja_file).c_str(),
161 std::ios_base::out | std::ios_base::binary);
162 if (file.fail()) {
163 *err = Err(Location(), "Couldn't open build.ninja for writing");
164 return false;
167 std::ofstream depfile;
168 depfile.open((FilePathToUTF8(ninja_file) + ".d").c_str(),
169 std::ios_base::out | std::ios_base::binary);
170 if (depfile.fail()) {
171 *err = Err(Location(), "Couldn't open depfile for writing");
172 return false;
175 NinjaBuildWriter gen(build_settings, all_settings, default_toolchain,
176 default_toolchain_targets, file, depfile);
177 return gen.Run(err);
180 void NinjaBuildWriter::WriteNinjaRules() {
181 out_ << "rule gn\n";
182 out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n";
183 out_ << " description = Regenerating ninja files\n";
184 out_ << " restat = 1\n\n";
186 // This rule will regenerate the ninja files when any input file has changed,
187 // or is missing.
188 out_ << "build build.ninja";
190 // Other files read by the build.
191 EscapeOptions path_escaping;
192 path_escaping.mode = ESCAPE_NINJA_COMMAND;
193 std::vector<SourceFile> written_files = g_scheduler->GetWrittenFiles();
194 for (const auto& written_file : written_files)
195 out_ << " " << EscapeString(RebasePath(written_file.value(),
196 build_settings_->build_dir(), build_settings_->root_path_utf8()),
197 path_escaping, nullptr);
199 out_ << ": gn\n"
200 << " generator = 1\n"
201 << " depfile = build.ninja.d\n";
203 // Input build files. These go in the ".d" file. If we write them as
204 // dependencies in the .ninja file itself, ninja will expect the files to
205 // exist and will error if they don't. When files are listed in a depfile,
206 // missing files are ignored.
207 dep_out_ << "build.ninja:";
208 std::vector<base::FilePath> input_files;
209 g_scheduler->input_file_manager()->GetAllPhysicalInputFileNames(&input_files);
210 for (const auto& input_file : input_files)
211 dep_out_ << " " << FilePathToUTF8(input_file);
213 // Other files read by the build.
214 std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies();
215 for (const auto& other_file : other_files)
216 dep_out_ << " " << FilePathToUTF8(other_file);
218 out_ << std::endl;
221 void NinjaBuildWriter::WriteLinkPool() {
222 out_ << "pool link_pool\n"
223 << " depth = " << default_toolchain_->concurrent_links() << std::endl
224 << std::endl;
227 void NinjaBuildWriter::WriteSubninjas() {
228 for (const auto& elem : all_settings_) {
229 out_ << "subninja ";
230 path_output_.WriteFile(out_, GetNinjaFileForToolchain(elem));
231 out_ << std::endl;
233 out_ << std::endl;
236 bool NinjaBuildWriter::WritePhonyAndAllRules(Err* err) {
237 std::string all_rules;
239 // Track rules as we generate them so we don't accidentally write a phony
240 // rule that collides with something else.
241 // GN internally generates an "all" target, so don't duplicate it.
242 std::set<std::string> written_rules;
243 written_rules.insert("all");
245 // Write phony rules for all uniquely-named targets in the default toolchain.
246 // Don't do other toolchains or we'll get naming conflicts, and if the name
247 // isn't unique, also skip it. The exception is for the toplevel targets
248 // which we also find.
249 std::map<std::string, int> small_name_count;
250 std::map<std::string, int> exe_count;
251 std::vector<const Target*> toplevel_targets;
252 base::hash_set<std::string> target_files;
253 for (const auto& target : default_toolchain_targets_) {
254 const Label& label = target->label();
255 small_name_count[label.name()]++;
257 // Look for targets with a name of the form
258 // dir = "//foo/", name = "foo"
259 // i.e. where the target name matches the top level directory. We will
260 // always write phony rules for these even if there is another target with
261 // the same short name.
262 const std::string& dir_string = label.dir().value();
263 if (dir_string.size() == label.name().size() + 3 && // Size matches.
264 dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning.
265 dir_string[dir_string.size() - 1] == '/' && // "/" at end.
266 dir_string.compare(2, label.name().size(), label.name()) == 0)
267 toplevel_targets.push_back(target);
269 // Look for executables; later we will generate phony rules for them
270 // even if there are non-executable targets with the same name.
271 if (target->output_type() == Target::EXECUTABLE)
272 exe_count[label.name()]++;
274 // Add the files to the list of generated targets so we don't write phony
275 // rules that collide.
276 std::string target_file(target->dependency_output_file().value());
277 NormalizePath(&target_file);
278 written_rules.insert(target_file);
281 for (const auto& target : default_toolchain_targets_) {
282 const Label& label = target->label();
283 OutputFile target_file = GetTargetOutputFile(target);
284 if (!target_files.insert(target_file.value()).second) {
285 *err = GetDuplicateOutputError(default_toolchain_targets_, target_file);
286 return false;
289 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz".
290 std::string long_name = label.GetUserVisibleName(false);
291 base::TrimString(long_name, "/", &long_name);
292 WritePhonyRule(target, target_file, long_name, &written_rules);
294 // Write the directory name with no target name if they match
295 // (e.g. "//foo/bar:bar" -> "foo/bar").
296 if (FindLastDirComponent(label.dir()) == label.name()) {
297 std::string medium_name = DirectoryWithNoLastSlash(label.dir());
298 base::TrimString(medium_name, "/", &medium_name);
299 // That may have generated a name the same as the short name of the
300 // target which we already wrote.
301 if (medium_name != label.name())
302 WritePhonyRule(target, target_file, medium_name, &written_rules);
305 // Write short names for ones which are either completely unique or there
306 // at least only one of them in the default toolchain that is an exe.
307 if (small_name_count[label.name()] == 1 ||
308 (target->output_type() == Target::EXECUTABLE &&
309 exe_count[label.name()] == 1)) {
310 WritePhonyRule(target, target_file, label.name(), &written_rules);
313 if (!all_rules.empty())
314 all_rules.append(" $\n ");
315 all_rules.append(target_file.value());
318 // Pick up phony rules for the toplevel targets with non-unique names (which
319 // would have been skipped in the above loop).
320 for (const auto& toplevel_target : toplevel_targets) {
321 if (small_name_count[toplevel_target->label().name()] > 1) {
322 WritePhonyRule(toplevel_target, toplevel_target->dependency_output_file(),
323 toplevel_target->label().name(), &written_rules);
327 // Figure out if the BUILD file wants to declare a custom "default"
328 // target (rather than building 'all' by default). By convention
329 // we use group("default") but it doesn't have to be a group.
330 bool default_target_exists = false;
331 for (const auto& target : default_toolchain_targets_) {
332 const Label& label = target->label();
333 if (label.dir().value() == "//" && label.name() == "default")
334 default_target_exists = true;
337 if (!all_rules.empty()) {
338 out_ << "\nbuild all: phony " << all_rules << std::endl;
341 if (default_target_exists) {
342 out_ << "default default" << std::endl;
343 } else if (!all_rules.empty()) {
344 out_ << "default all" << std::endl;
347 return true;
350 void NinjaBuildWriter::WritePhonyRule(const Target* target,
351 const OutputFile& target_file,
352 const std::string& phony_name,
353 std::set<std::string>* written_rules) {
354 if (target_file.value() == phony_name)
355 return; // No need for a phony rule.
357 if (written_rules->find(phony_name) != written_rules->end())
358 return; // Already exists.
359 written_rules->insert(phony_name);
361 EscapeOptions ninja_escape;
362 ninja_escape.mode = ESCAPE_NINJA;
364 // Escape for special chars Ninja will handle.
365 std::string escaped = EscapeString(phony_name, ninja_escape, nullptr);
367 out_ << "build " << escaped << ": phony ";
368 path_output_.WriteFile(out_, target_file);
369 out_ << std::endl;