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

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: 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"
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
72 } 73 }
73 } 74 }
74 75
75 #if defined(OS_WIN) 76 #if defined(OS_WIN)
76 return base::WideToUTF8(cmdline.GetCommandLineString()); 77 return base::WideToUTF8(cmdline.GetCommandLineString());
77 #else 78 #else
78 return cmdline.GetCommandLineString(); 79 return cmdline.GetCommandLineString();
79 #endif 80 #endif
80 } 81 }
81 82
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 83 // Given an output that appears more than once, generates an error message
112 // that describes the problem and which targets generate it. 84 // that describes the problem and which targets generate it.
113 Err GetDuplicateOutputError(const std::vector<const Target*>& all_targets, 85 Err GetDuplicateOutputError(const std::vector<const Target*>& all_targets,
114 const OutputFile& bad_output) { 86 const OutputFile& bad_output) {
115 std::vector<const Target*> matches; 87 std::vector<const Target*> matches;
116 for (const Target* target : all_targets) { 88 for (const Target* target : all_targets) {
117 for (const auto& output : target->computed_outputs()) { 89 for (const auto& output : target->computed_outputs()) {
118 if (output == bad_output) { 90 if (output == bad_output) {
119 matches.push_back(target); 91 matches.push_back(target);
120 break; 92 break;
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
171 143
172 // static 144 // static
173 bool NinjaBuildWriter::RunAndWriteFile( 145 bool NinjaBuildWriter::RunAndWriteFile(
174 const BuildSettings* build_settings, 146 const BuildSettings* build_settings,
175 const std::vector<const Settings*>& all_settings, 147 const std::vector<const Settings*>& all_settings,
176 const Toolchain* default_toolchain, 148 const Toolchain* default_toolchain,
177 const std::vector<const Target*>& default_toolchain_targets, 149 const std::vector<const Target*>& default_toolchain_targets,
178 Err* err) { 150 Err* err) {
179 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja"); 151 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja");
180 152
181 base::FilePath ninja_file(build_settings->GetFullPath( 153 std::stringstream file;
182 SourceFile(build_settings->build_dir().value() + "build.ninja"))); 154 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, 155 NinjaBuildWriter gen(build_settings, all_settings, default_toolchain,
202 default_toolchain_targets, file, depfile); 156 default_toolchain_targets, file, depfile);
203 return gen.Run(err); 157 if (!gen.Run(err))
158 return false;
159
160 base::FilePath ninja_file_name(build_settings->GetFullPath(
161 SourceFile(build_settings->build_dir().value() + "build.ninja")));
162 base::FilePath dep_file_name(build_settings->GetFullPath(
163 SourceFile(build_settings->build_dir().value() + "build.ninja.d")));
164 base::CreateDirectory(ninja_file_name.DirName());
165
166 if (!WriteFileIfChanged(ninja_file_name, file.str(), err) ||
167 !WriteFileIfChanged(dep_file_name, depfile.str(), err))
168 return false;
169
170 return true;
204 } 171 }
205 172
206 void NinjaBuildWriter::WriteNinjaRules() { 173 void NinjaBuildWriter::WriteNinjaRules() {
207 out_ << "rule gn\n"; 174 out_ << "rule gn\n";
208 out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n"; 175 out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n";
209 out_ << " description = Regenerating ninja files\n\n"; 176 out_ << " description = Regenerating ninja files\n\n";
210 177
211 // This rule will regenerate the ninja files when any input file has changed. 178 // This rule will regenerate the ninja files when any input file has changed.
212 out_ << "build build.ninja: gn\n" 179 out_ << "build build.ninja: gn\n"
213 << " generator = 1\n" 180 << " generator = 1\n"
(...skipping 29 matching lines...) Expand all
243 210
244 void NinjaBuildWriter::WriteSubninjas() { 211 void NinjaBuildWriter::WriteSubninjas() {
245 for (const auto& elem : all_settings_) { 212 for (const auto& elem : all_settings_) {
246 out_ << "subninja "; 213 out_ << "subninja ";
247 path_output_.WriteFile(out_, GetNinjaFileForToolchain(elem)); 214 path_output_.WriteFile(out_, GetNinjaFileForToolchain(elem));
248 out_ << std::endl; 215 out_ << std::endl;
249 } 216 }
250 out_ << std::endl; 217 out_ << std::endl;
251 } 218 }
252 219
220 namespace {
scottmg 2016/05/09 23:31:53 Unusual to have another one here instead of puttin
221
222 struct Counts {
223 Counts() : count(0), last_seen(nullptr) {}
224
225 // Number of targets of this type.
226 int count;
227
228 // The last one we encountered.
229 const Target* last_seen;
230 };
231
232 } // namesoace
scottmg 2016/05/09 23:31:52 "namespace"
233
253 bool NinjaBuildWriter::WritePhonyAndAllRules(Err* err) { 234 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 235 // Track rules as we generate them so we don't accidentally write a phony
257 // rule that collides with something else. 236 // rule that collides with something else.
258 // GN internally generates an "all" target, so don't duplicate it. 237 // GN internally generates an "all" target, so don't duplicate it.
259 std::set<std::string> written_rules; 238 base::hash_set<std::string> written_rules;
260 written_rules.insert("all"); 239 written_rules.insert("all");
261 240
262 // Write phony rules for all uniquely-named targets in the default toolchain. 241 // Set if we encounter a target named "//:default".
263 // Don't do other toolchains or we'll get naming conflicts, and if the name 242 bool default_target_exists = false;
264 // isn't unique, also skip it. The exception is for the toplevel targets 243
265 // which we also find. 244 // 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; 245 std::vector<const Target*> toplevel_targets;
269 base::hash_set<std::string> target_files; 246
270 for (const auto& target : default_toolchain_targets_) { 247 // Targets with names matching their toplevel directories. For example
248 // "//foo:foo". Expect this is the naming scheme for "big components."
249 std::vector<const Target*> toplevel_dir_targets;
250
251 // Tracks the number of each target with the given short name, as well
252 // as the short names of executables (which will be a subset of short_names).
253 std::map<std::string, Counts> short_names;
254 std::map<std::string, Counts> exes;
255
256 for (const Target* target : default_toolchain_targets_) {
271 const Label& label = target->label(); 257 const Label& label = target->label();
272 small_name_count[label.name()]++; 258 const std::string& short_name = label.name();
273 259
274 // Look for targets with a name of the form 260 if (label.dir().value() == "//" && label.name() == "default")
275 // dir = "//foo/", name = "foo" 261 default_target_exists = true;
276 // i.e. where the target name matches the top level directory. We will 262
277 // always write phony rules for these even if there is another target with 263 // Count the number of targets with the given short name.
278 // the same short name. 264 Counts& short_names_counts = short_names[short_name];
265 short_names_counts.count++;
266 short_names_counts.last_seen = target;
267
268 // Count executables with the given short name.
269 if (target->output_type() == Target::EXECUTABLE) {
270 Counts& exes_counts = exes[short_name];
271 exes_counts.count++;
272 exes_counts.last_seen = target;
273 }
274
275 // Find targets in "important" directories.
279 const std::string& dir_string = label.dir().value(); 276 const std::string& dir_string = label.dir().value();
280 if (dir_string.size() == label.name().size() + 3 && // Size matches. 277 if (dir_string.size() == 2 &&
278 dir_string[0] == '/' && dir_string[1] == '/') {
279 toplevel_targets.push_back(target);
280 } else if (
281 dir_string.size() == label.name().size() + 3 && // Size matches.
281 dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning. 282 dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning.
282 dir_string[dir_string.size() - 1] == '/' && // "/" at end. 283 dir_string[dir_string.size() - 1] == '/' && // "/" at end.
283 dir_string.compare(2, label.name().size(), label.name()) == 0) 284 dir_string.compare(2, label.name().size(), label.name()) == 0) {
284 toplevel_targets.push_back(target); 285 toplevel_dir_targets.push_back(target);
286 }
285 287
286 // Look for executables; later we will generate phony rules for them 288 // 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. 289 // we don't write phony rules that collide with anything generated by the
288 if (target->output_type() == Target::EXECUTABLE) 290 // build.
289 exe_count[label.name()]++; 291 //
290 292 // 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 293 // 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()) { 294 for (const auto& output : target->computed_outputs()) {
301 if (!target_files.insert(output.value()).second) { 295 // Need to normalize because many toolchain outputs will be preceeded
296 // with "./".
297 std::string output_string(output.value());
298 NormalizePath(&output_string);
299 if (!written_rules.insert(output_string).second) {
302 *err = GetDuplicateOutputError(default_toolchain_targets_, output); 300 *err = GetDuplicateOutputError(default_toolchain_targets_, output);
303 return false; 301 return false;
304 } 302 }
305 } 303 }
304 }
306 305
307 OutputFile target_file = GetTargetOutputFile(target); 306 // First prefer the short names of toplevel targets.
307 for (const Target* target : toplevel_targets) {
308 if (written_rules.insert(target->label().name()).second)
309 WritePhonyRule(target, target->label().name());
310 }
311
312 // Next prefer short names of toplevel dir targets.
313 for (const Target* target : toplevel_dir_targets) {
314 if (written_rules.insert(target->label().name()).second)
315 WritePhonyRule(target, target->label().name());
316 }
317
318 // Write out the names labels of executables. Many toolchains will produce
319 // executables in the root build directory with no extensions, so the names
320 // will already exist and this will be a no-op. But on Windows such programs
321 // will have extensions, and executables may override the output directory to
322 // go into some other place.
323 //
324 // Putting this after the "toplevel" rules above also means that you can
325 // steal the short name from an executable by outputting the executable to
326 // a different directory or using a different output name, and writing a
327 // toplevel build rule.
328 for (const auto& pair : exes) {
329 const Counts& counts = pair.second;
330 const std::string& short_name = counts.last_seen->label().name();
331 if (counts.count == 1 && written_rules.insert(short_name).second)
332 WritePhonyRule(counts.last_seen, short_name);
333 }
334
335 // Write short names when those names are unique and not already taken.
336 for (const auto& pair : short_names) {
337 const Counts& counts = pair.second;
338 const std::string& short_name = counts.last_seen->label().name();
339 if (counts.count == 1 && written_rules.insert(short_name).second)
340 WritePhonyRule(counts.last_seen, short_name);
341 }
342
343 // Write the label variants of the target name.
344 for (const Target* target : default_toolchain_targets_) {
345 const Label& label = target->label();
346
308 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz". 347 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz".
309 std::string long_name = label.GetUserVisibleName(false); 348 std::string long_name = label.GetUserVisibleName(false);
310 base::TrimString(long_name, "/", &long_name); 349 base::TrimString(long_name, "/", &long_name);
311 WritePhonyRule(target, target_file, long_name, &written_rules); 350 if (written_rules.insert(long_name).second)
351 WritePhonyRule(target, long_name);
312 352
313 // Write the directory name with no target name if they match 353 // Write the directory name with no target name if they match
314 // (e.g. "//foo/bar:bar" -> "foo/bar"). 354 // (e.g. "//foo/bar:bar" -> "foo/bar").
315 if (FindLastDirComponent(label.dir()) == label.name()) { 355 if (FindLastDirComponent(label.dir()) == label.name()) {
316 std::string medium_name = DirectoryWithNoLastSlash(label.dir()); 356 std::string medium_name = DirectoryWithNoLastSlash(label.dir());
317 base::TrimString(medium_name, "/", &medium_name); 357 base::TrimString(medium_name, "/", &medium_name);
318 // That may have generated a name the same as the short name of the 358 // That may have generated a name the same as the short name of the
319 // target which we already wrote. 359 // target which we already wrote.
320 if (medium_name != label.name()) 360 if (medium_name != label.name() &&
321 WritePhonyRule(target, target_file, medium_name, &written_rules); 361 written_rules.insert(medium_name).second)
362 WritePhonyRule(target, medium_name);
322 } 363 }
323 364
324 // Write short names for ones which are either completely unique or there 365 // 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. 366 // non of the higher-priority rules above claimed it.
scottmg 2016/05/09 23:31:53 non->none
326 if (small_name_count[label.name()] == 1 || 367 if (short_names[label.name()].count == 1 &&
327 (target->output_type() == Target::EXECUTABLE && 368 written_rules.insert(label.name()).second)
328 exe_count[label.name()] == 1)) { 369 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 } 370 }
353 371
354 // Pick up phony rules for the toplevel targets with non-unique names (which 372 // Write the autogenerated "all" rule.
355 // would have been skipped in the above loop). 373 if (!default_toolchain_targets_.empty()) {
356 for (const auto& toplevel_target : toplevel_targets) { 374 out_ << "\nbuild all: phony";
357 if (small_name_count[toplevel_target->label().name()] > 1) { 375
358 WritePhonyRule(toplevel_target, toplevel_target->dependency_output_file(), 376 EscapeOptions ninja_escape;
359 toplevel_target->label().name(), &written_rules); 377 ninja_escape.mode = ESCAPE_NINJA;
378 for (const Target* target : default_toolchain_targets_) {
379 out_ << (" $\n ");
scottmg 2016/05/09 23:31:53 no ()
380 path_output_.WriteFile(out_, target->dependency_output_file());
360 } 381 }
361 } 382 }
383 out_ << std::endl;
362 384
363 // Figure out if the BUILD file wants to declare a custom "default" 385 if (default_target_exists)
364 // target (rather than building 'all' by default). By convention 386 out_ << "\ndefault default" << std::endl;
365 // we use group("default") but it doesn't have to be a group. 387 else if (!default_toolchain_targets_.empty())
366 bool default_target_exists = false; 388 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 389
383 return true; 390 return true;
384 } 391 }
385 392
386 void NinjaBuildWriter::WritePhonyRule(const Target* target, 393 void NinjaBuildWriter::WritePhonyRule(const Target* target,
387 const OutputFile& target_file, 394 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; 395 EscapeOptions ninja_escape;
398 ninja_escape.mode = ESCAPE_NINJA; 396 ninja_escape.mode = ESCAPE_NINJA;
399 397
400 // Escape for special chars Ninja will handle. 398 // Escape for special chars Ninja will handle.
401 std::string escaped = EscapeString(phony_name, ninja_escape, nullptr); 399 std::string escaped = EscapeString(phony_name, ninja_escape, nullptr);
402 400
403 out_ << "build " << escaped << ": phony "; 401 out_ << "build " << escaped << ": phony ";
404 path_output_.WriteFile(out_, target_file); 402 path_output_.WriteFile(out_, target->dependency_output_file());
405 out_ << std::endl; 403 out_ << std::endl;
406 } 404 }
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