Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(838)

Side by Side Diff: tools/gn/ninja_build_writer.cc

Issue 1958783003: GN: Prefer toplevel targets for phony names. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Review comments Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/gn/ninja_build_writer.h ('k') | tools/gn/ninja_build_writer_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "tools/gn/ninja_build_writer.h" 5 #include "tools/gn/ninja_build_writer.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 8
9 #include <fstream> 9 #include <fstream>
10 #include <map> 10 #include <map>
11 #include <sstream>
11 12
12 #include "base/command_line.h" 13 #include "base/command_line.h"
13 #include "base/files/file_util.h" 14 #include "base/files/file_util.h"
14 #include "base/path_service.h" 15 #include "base/path_service.h"
15 #include "base/process/process_handle.h" 16 #include "base/process/process_handle.h"
16 #include "base/strings/string_util.h" 17 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h" 18 #include "base/strings/utf_string_conversions.h"
18 #include "build/build_config.h" 19 #include "build/build_config.h"
19 #include "tools/gn/build_settings.h" 20 #include "tools/gn/build_settings.h"
20 #include "tools/gn/err.h" 21 #include "tools/gn/err.h"
21 #include "tools/gn/escape.h" 22 #include "tools/gn/escape.h"
22 #include "tools/gn/filesystem_utils.h" 23 #include "tools/gn/filesystem_utils.h"
23 #include "tools/gn/input_file_manager.h" 24 #include "tools/gn/input_file_manager.h"
24 #include "tools/gn/ninja_utils.h" 25 #include "tools/gn/ninja_utils.h"
25 #include "tools/gn/scheduler.h" 26 #include "tools/gn/scheduler.h"
26 #include "tools/gn/switches.h" 27 #include "tools/gn/switches.h"
27 #include "tools/gn/target.h" 28 #include "tools/gn/target.h"
28 #include "tools/gn/trace.h" 29 #include "tools/gn/trace.h"
29 30
30 #if defined(OS_WIN) 31 #if defined(OS_WIN)
31 #include <windows.h> 32 #include <windows.h>
32 #endif 33 #endif
33 34
34 namespace { 35 namespace {
35 36
37 struct Counts {
38 Counts() : count(0), last_seen(nullptr) {}
39
40 // Number of targets of this type.
41 int count;
42
43 // The last one we encountered.
44 const Target* last_seen;
45 };
46
36 std::string GetSelfInvocationCommand(const BuildSettings* build_settings) { 47 std::string GetSelfInvocationCommand(const BuildSettings* build_settings) {
37 base::FilePath executable; 48 base::FilePath executable;
38 PathService::Get(base::FILE_EXE, &executable); 49 PathService::Get(base::FILE_EXE, &executable);
39 50
40 base::CommandLine cmdline(executable.NormalizePathSeparatorsTo('/')); 51 base::CommandLine cmdline(executable.NormalizePathSeparatorsTo('/'));
41 cmdline.AppendArg("gen"); 52 cmdline.AppendArg("gen");
42 cmdline.AppendArg(build_settings->build_dir().value()); 53 cmdline.AppendArg(build_settings->build_dir().value());
43 cmdline.AppendSwitchPath(std::string("--") + switches::kRoot, 54 cmdline.AppendSwitchPath(std::string("--") + switches::kRoot,
44 build_settings->root_path()); 55 build_settings->root_path());
45 // Successful automatic invocations shouldn't print output. 56 // Successful automatic invocations shouldn't print output.
(...skipping 26 matching lines...) Expand all
72 } 83 }
73 } 84 }
74 85
75 #if defined(OS_WIN) 86 #if defined(OS_WIN)
76 return base::WideToUTF8(cmdline.GetCommandLineString()); 87 return base::WideToUTF8(cmdline.GetCommandLineString());
77 #else 88 #else
78 return cmdline.GetCommandLineString(); 89 return cmdline.GetCommandLineString();
79 #endif 90 #endif
80 } 91 }
81 92
82 OutputFile GetTargetOutputFile(const Target* target) {
83 OutputFile result(target->dependency_output_file());
84
85 // The output files may have leading "./" so normalize those away.
86 NormalizePath(&result.value());
87 return result;
88 }
89
90 bool HasOutputIdenticalToLabel(const Target* target,
91 const std::string& short_name) {
92 if (target->output_type() != Target::ACTION &&
93 target->output_type() != Target::ACTION_FOREACH)
94 return false;
95
96 // Rather than convert all outputs to be relative to the build directory
97 // and then compare to the short name, convert the short name to look like a
98 // file in the output directory since this is only one conversion.
99 SourceFile short_name_as_source_file(
100 target->settings()->build_settings()->build_dir().value() + short_name);
101
102 std::vector<SourceFile> outputs_as_source;
103 target->action_values().GetOutputsAsSourceFiles(target, &outputs_as_source);
104 for (const SourceFile& output_as_source : outputs_as_source) {
105 if (output_as_source == short_name_as_source_file)
106 return true;
107 }
108 return false;
109 }
110
111 // Given an output that appears more than once, generates an error message 93 // Given an output that appears more than once, generates an error message
112 // that describes the problem and which targets generate it. 94 // that describes the problem and which targets generate it.
113 Err GetDuplicateOutputError(const std::vector<const Target*>& all_targets, 95 Err GetDuplicateOutputError(const std::vector<const Target*>& all_targets,
114 const OutputFile& bad_output) { 96 const OutputFile& bad_output) {
115 std::vector<const Target*> matches; 97 std::vector<const Target*> matches;
116 for (const Target* target : all_targets) { 98 for (const Target* target : all_targets) {
117 for (const auto& output : target->computed_outputs()) { 99 for (const auto& output : target->computed_outputs()) {
118 if (output == bad_output) { 100 if (output == bad_output) {
119 matches.push_back(target); 101 matches.push_back(target);
120 break; 102 break;
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
171 153
172 // static 154 // static
173 bool NinjaBuildWriter::RunAndWriteFile( 155 bool NinjaBuildWriter::RunAndWriteFile(
174 const BuildSettings* build_settings, 156 const BuildSettings* build_settings,
175 const std::vector<const Settings*>& all_settings, 157 const std::vector<const Settings*>& all_settings,
176 const Toolchain* default_toolchain, 158 const Toolchain* default_toolchain,
177 const std::vector<const Target*>& default_toolchain_targets, 159 const std::vector<const Target*>& default_toolchain_targets,
178 Err* err) { 160 Err* err) {
179 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja"); 161 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja");
180 162
181 base::FilePath ninja_file(build_settings->GetFullPath( 163 std::stringstream file;
182 SourceFile(build_settings->build_dir().value() + "build.ninja"))); 164 std::stringstream depfile;
183 base::CreateDirectory(ninja_file.DirName());
184
185 std::ofstream file;
186 file.open(FilePathToUTF8(ninja_file).c_str(),
187 std::ios_base::out | std::ios_base::binary);
188 if (file.fail()) {
189 *err = Err(Location(), "Couldn't open build.ninja for writing");
190 return false;
191 }
192
193 std::ofstream depfile;
194 depfile.open((FilePathToUTF8(ninja_file) + ".d").c_str(),
195 std::ios_base::out | std::ios_base::binary);
196 if (depfile.fail()) {
197 *err = Err(Location(), "Couldn't open depfile for writing");
198 return false;
199 }
200
201 NinjaBuildWriter gen(build_settings, all_settings, default_toolchain, 165 NinjaBuildWriter gen(build_settings, all_settings, default_toolchain,
202 default_toolchain_targets, file, depfile); 166 default_toolchain_targets, file, depfile);
203 return gen.Run(err); 167 if (!gen.Run(err))
168 return false;
169
170 base::FilePath ninja_file_name(build_settings->GetFullPath(
171 SourceFile(build_settings->build_dir().value() + "build.ninja")));
172 base::FilePath dep_file_name(build_settings->GetFullPath(
173 SourceFile(build_settings->build_dir().value() + "build.ninja.d")));
174 base::CreateDirectory(ninja_file_name.DirName());
175
176 if (!WriteFileIfChanged(ninja_file_name, file.str(), err) ||
177 !WriteFileIfChanged(dep_file_name, depfile.str(), err))
178 return false;
179
180 return true;
204 } 181 }
205 182
206 void NinjaBuildWriter::WriteNinjaRules() { 183 void NinjaBuildWriter::WriteNinjaRules() {
207 out_ << "rule gn\n"; 184 out_ << "rule gn\n";
208 out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n"; 185 out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n";
209 out_ << " description = Regenerating ninja files\n\n"; 186 out_ << " description = Regenerating ninja files\n\n";
210 187
211 // This rule will regenerate the ninja files when any input file has changed. 188 // This rule will regenerate the ninja files when any input file has changed.
212 out_ << "build build.ninja: gn\n" 189 out_ << "build build.ninja: gn\n"
213 << " generator = 1\n" 190 << " generator = 1\n"
(...skipping 30 matching lines...) Expand all
244 void NinjaBuildWriter::WriteSubninjas() { 221 void NinjaBuildWriter::WriteSubninjas() {
245 for (const auto& elem : all_settings_) { 222 for (const auto& elem : all_settings_) {
246 out_ << "subninja "; 223 out_ << "subninja ";
247 path_output_.WriteFile(out_, GetNinjaFileForToolchain(elem)); 224 path_output_.WriteFile(out_, GetNinjaFileForToolchain(elem));
248 out_ << std::endl; 225 out_ << std::endl;
249 } 226 }
250 out_ << std::endl; 227 out_ << std::endl;
251 } 228 }
252 229
253 bool NinjaBuildWriter::WritePhonyAndAllRules(Err* err) { 230 bool NinjaBuildWriter::WritePhonyAndAllRules(Err* err) {
254 std::string all_rules;
255
256 // Track rules as we generate them so we don't accidentally write a phony 231 // Track rules as we generate them so we don't accidentally write a phony
257 // rule that collides with something else. 232 // rule that collides with something else.
258 // GN internally generates an "all" target, so don't duplicate it. 233 // GN internally generates an "all" target, so don't duplicate it.
259 std::set<std::string> written_rules; 234 base::hash_set<std::string> written_rules;
260 written_rules.insert("all"); 235 written_rules.insert("all");
261 236
262 // Write phony rules for all uniquely-named targets in the default toolchain. 237 // Set if we encounter a target named "//:default".
263 // Don't do other toolchains or we'll get naming conflicts, and if the name 238 bool default_target_exists = false;
264 // isn't unique, also skip it. The exception is for the toplevel targets 239
265 // which we also find. 240 // Targets in the root build file.
266 std::map<std::string, int> small_name_count;
267 std::map<std::string, int> exe_count;
268 std::vector<const Target*> toplevel_targets; 241 std::vector<const Target*> toplevel_targets;
269 base::hash_set<std::string> target_files; 242
270 for (const auto& target : default_toolchain_targets_) { 243 // Targets with names matching their toplevel directories. For example
244 // "//foo:foo". Expect this is the naming scheme for "big components."
245 std::vector<const Target*> toplevel_dir_targets;
246
247 // Tracks the number of each target with the given short name, as well
248 // as the short names of executables (which will be a subset of short_names).
249 std::map<std::string, Counts> short_names;
250 std::map<std::string, Counts> exes;
251
252 for (const Target* target : default_toolchain_targets_) {
271 const Label& label = target->label(); 253 const Label& label = target->label();
272 small_name_count[label.name()]++; 254 const std::string& short_name = label.name();
273 255
274 // Look for targets with a name of the form 256 if (label.dir().value() == "//" && label.name() == "default")
275 // dir = "//foo/", name = "foo" 257 default_target_exists = true;
276 // i.e. where the target name matches the top level directory. We will 258
277 // always write phony rules for these even if there is another target with 259 // Count the number of targets with the given short name.
278 // the same short name. 260 Counts& short_names_counts = short_names[short_name];
261 short_names_counts.count++;
262 short_names_counts.last_seen = target;
263
264 // Count executables with the given short name.
265 if (target->output_type() == Target::EXECUTABLE) {
266 Counts& exes_counts = exes[short_name];
267 exes_counts.count++;
268 exes_counts.last_seen = target;
269 }
270
271 // Find targets in "important" directories.
279 const std::string& dir_string = label.dir().value(); 272 const std::string& dir_string = label.dir().value();
280 if (dir_string.size() == label.name().size() + 3 && // Size matches. 273 if (dir_string.size() == 2 &&
274 dir_string[0] == '/' && dir_string[1] == '/') {
275 toplevel_targets.push_back(target);
276 } else if (
277 dir_string.size() == label.name().size() + 3 && // Size matches.
281 dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning. 278 dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning.
282 dir_string[dir_string.size() - 1] == '/' && // "/" at end. 279 dir_string[dir_string.size() - 1] == '/' && // "/" at end.
283 dir_string.compare(2, label.name().size(), label.name()) == 0) 280 dir_string.compare(2, label.name().size(), label.name()) == 0) {
284 toplevel_targets.push_back(target); 281 toplevel_dir_targets.push_back(target);
282 }
285 283
286 // Look for executables; later we will generate phony rules for them 284 // Add the output files from each target to the written rules so that
287 // even if there are non-executable targets with the same name. 285 // we don't write phony rules that collide with anything generated by the
288 if (target->output_type() == Target::EXECUTABLE) 286 // build.
289 exe_count[label.name()]++; 287 //
290 288 // If at this point there is a collision (no phony rules have been
291 // Add the files to the list of generated targets so we don't write phony 289 // generated yet), two targets make the same output so throw an error.
292 // rules that collide.
293 std::string target_file(target->dependency_output_file().value());
294 NormalizePath(&target_file);
295 written_rules.insert(target_file);
296 }
297
298 for (const auto& target : default_toolchain_targets_) {
299 const Label& label = target->label();
300 for (const auto& output : target->computed_outputs()) { 290 for (const auto& output : target->computed_outputs()) {
301 if (!target_files.insert(output.value()).second) { 291 // Need to normalize because many toolchain outputs will be preceeded
292 // with "./".
293 std::string output_string(output.value());
294 NormalizePath(&output_string);
295 if (!written_rules.insert(output_string).second) {
302 *err = GetDuplicateOutputError(default_toolchain_targets_, output); 296 *err = GetDuplicateOutputError(default_toolchain_targets_, output);
303 return false; 297 return false;
304 } 298 }
305 } 299 }
300 }
306 301
307 OutputFile target_file = GetTargetOutputFile(target); 302 // First prefer the short names of toplevel targets.
303 for (const Target* target : toplevel_targets) {
304 if (written_rules.insert(target->label().name()).second)
305 WritePhonyRule(target, target->label().name());
306 }
307
308 // Next prefer short names of toplevel dir targets.
309 for (const Target* target : toplevel_dir_targets) {
310 if (written_rules.insert(target->label().name()).second)
311 WritePhonyRule(target, target->label().name());
312 }
313
314 // Write out the names labels of executables. Many toolchains will produce
315 // executables in the root build directory with no extensions, so the names
316 // will already exist and this will be a no-op. But on Windows such programs
317 // will have extensions, and executables may override the output directory to
318 // go into some other place.
319 //
320 // Putting this after the "toplevel" rules above also means that you can
321 // steal the short name from an executable by outputting the executable to
322 // a different directory or using a different output name, and writing a
323 // toplevel build rule.
324 for (const auto& pair : exes) {
325 const Counts& counts = pair.second;
326 const std::string& short_name = counts.last_seen->label().name();
327 if (counts.count == 1 && written_rules.insert(short_name).second)
328 WritePhonyRule(counts.last_seen, short_name);
329 }
330
331 // Write short names when those names are unique and not already taken.
332 for (const auto& pair : short_names) {
333 const Counts& counts = pair.second;
334 const std::string& short_name = counts.last_seen->label().name();
335 if (counts.count == 1 && written_rules.insert(short_name).second)
336 WritePhonyRule(counts.last_seen, short_name);
337 }
338
339 // Write the label variants of the target name.
340 for (const Target* target : default_toolchain_targets_) {
341 const Label& label = target->label();
342
308 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz". 343 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz".
309 std::string long_name = label.GetUserVisibleName(false); 344 std::string long_name = label.GetUserVisibleName(false);
310 base::TrimString(long_name, "/", &long_name); 345 base::TrimString(long_name, "/", &long_name);
311 WritePhonyRule(target, target_file, long_name, &written_rules); 346 if (written_rules.insert(long_name).second)
347 WritePhonyRule(target, long_name);
312 348
313 // Write the directory name with no target name if they match 349 // Write the directory name with no target name if they match
314 // (e.g. "//foo/bar:bar" -> "foo/bar"). 350 // (e.g. "//foo/bar:bar" -> "foo/bar").
315 if (FindLastDirComponent(label.dir()) == label.name()) { 351 if (FindLastDirComponent(label.dir()) == label.name()) {
316 std::string medium_name = DirectoryWithNoLastSlash(label.dir()); 352 std::string medium_name = DirectoryWithNoLastSlash(label.dir());
317 base::TrimString(medium_name, "/", &medium_name); 353 base::TrimString(medium_name, "/", &medium_name);
318 // That may have generated a name the same as the short name of the 354 // That may have generated a name the same as the short name of the
319 // target which we already wrote. 355 // target which we already wrote.
320 if (medium_name != label.name()) 356 if (medium_name != label.name() &&
321 WritePhonyRule(target, target_file, medium_name, &written_rules); 357 written_rules.insert(medium_name).second)
358 WritePhonyRule(target, medium_name);
322 } 359 }
323 360
324 // Write short names for ones which are either completely unique or there 361 // Write the short name if no other target shares that short name and
325 // at least only one of them in the default toolchain that is an exe. 362 // non of the higher-priority rules above claimed it.
326 if (small_name_count[label.name()] == 1 || 363 if (short_names[label.name()].count == 1 &&
327 (target->output_type() == Target::EXECUTABLE && 364 written_rules.insert(label.name()).second)
328 exe_count[label.name()] == 1)) { 365 WritePhonyRule(target, label.name());
329 // It's reasonable to generate output files in the root build directory
330 // with the same name as the target. Don't generate phony rules for
331 // these cases.
332 //
333 // All of this does not do the general checking of all target's outputs
334 // which may theoretically collide. But it's not very reasonable for
335 // a script target named "foo" to generate a file named "bar" with no
336 // extension in the root build directory while another target is named
337 // "bar". If this does occur, the user is likely to be confused when
338 // building "bar" that is builds foo anyway, so you probably just
339 // shouldn't do that.
340 //
341 // We should fix this however, and build up all generated script outputs
342 // and check everything against that. There are other edge cases that the
343 // current phony rule generator doesn't check. We may need to make a big
344 // set of every possible generated file in the build for this purpose.
345 if (!HasOutputIdenticalToLabel(target, label.name()))
346 WritePhonyRule(target, target_file, label.name(), &written_rules);
347 }
348
349 if (!all_rules.empty())
350 all_rules.append(" $\n ");
351 all_rules.append(target_file.value());
352 } 366 }
353 367
354 // Pick up phony rules for the toplevel targets with non-unique names (which 368 // Write the autogenerated "all" rule.
355 // would have been skipped in the above loop). 369 if (!default_toolchain_targets_.empty()) {
356 for (const auto& toplevel_target : toplevel_targets) { 370 out_ << "\nbuild all: phony";
357 if (small_name_count[toplevel_target->label().name()] > 1) { 371
358 WritePhonyRule(toplevel_target, toplevel_target->dependency_output_file(), 372 EscapeOptions ninja_escape;
359 toplevel_target->label().name(), &written_rules); 373 ninja_escape.mode = ESCAPE_NINJA;
374 for (const Target* target : default_toolchain_targets_) {
375 out_ << " $\n ";
376 path_output_.WriteFile(out_, target->dependency_output_file());
360 } 377 }
361 } 378 }
379 out_ << std::endl;
362 380
363 // Figure out if the BUILD file wants to declare a custom "default" 381 if (default_target_exists)
364 // target (rather than building 'all' by default). By convention 382 out_ << "\ndefault default" << std::endl;
365 // we use group("default") but it doesn't have to be a group. 383 else if (!default_toolchain_targets_.empty())
366 bool default_target_exists = false; 384 out_ << "\ndefault all" << std::endl;
367 for (const auto& target : default_toolchain_targets_) {
368 const Label& label = target->label();
369 if (label.dir().value() == "//" && label.name() == "default")
370 default_target_exists = true;
371 }
372
373 if (!all_rules.empty()) {
374 out_ << "\nbuild all: phony " << all_rules << std::endl;
375 }
376
377 if (default_target_exists) {
378 out_ << "default default" << std::endl;
379 } else if (!all_rules.empty()) {
380 out_ << "default all" << std::endl;
381 }
382 385
383 return true; 386 return true;
384 } 387 }
385 388
386 void NinjaBuildWriter::WritePhonyRule(const Target* target, 389 void NinjaBuildWriter::WritePhonyRule(const Target* target,
387 const OutputFile& target_file, 390 const std::string& phony_name) {
388 const std::string& phony_name,
389 std::set<std::string>* written_rules) {
390 if (target_file.value() == phony_name)
391 return; // No need for a phony rule.
392
393 if (written_rules->find(phony_name) != written_rules->end())
394 return; // Already exists.
395 written_rules->insert(phony_name);
396
397 EscapeOptions ninja_escape; 391 EscapeOptions ninja_escape;
398 ninja_escape.mode = ESCAPE_NINJA; 392 ninja_escape.mode = ESCAPE_NINJA;
399 393
400 // Escape for special chars Ninja will handle. 394 // Escape for special chars Ninja will handle.
401 std::string escaped = EscapeString(phony_name, ninja_escape, nullptr); 395 std::string escaped = EscapeString(phony_name, ninja_escape, nullptr);
402 396
403 out_ << "build " << escaped << ": phony "; 397 out_ << "build " << escaped << ": phony ";
404 path_output_.WriteFile(out_, target_file); 398 path_output_.WriteFile(out_, target->dependency_output_file());
405 out_ << std::endl; 399 out_ << std::endl;
406 } 400 }
OLDNEW
« no previous file with comments | « tools/gn/ninja_build_writer.h ('k') | tools/gn/ninja_build_writer_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698