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 <mutex>
6
7 #include "base/command_line.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/timer/elapsed_timer.h"
11 #include "gn/build_settings.h"
12 #include "gn/commands.h"
13 #include "gn/compile_commands_writer.h"
14 #include "gn/eclipse_writer.h"
15 #include "gn/json_project_writer.h"
16 #include "gn/ninja_target_writer.h"
17 #include "gn/ninja_writer.h"
18 #include "gn/qt_creator_writer.h"
19 #include "gn/runtime_deps.h"
20 #include "gn/scheduler.h"
21 #include "gn/setup.h"
22 #include "gn/standard_out.h"
23 #include "gn/switches.h"
24 #include "gn/target.h"
25 #include "gn/visual_studio_writer.h"
26 #include "gn/xcode_writer.h"
27
28 namespace commands {
29
30 namespace {
31
32 const char kSwitchCheck[] = "check";
33 const char kSwitchFilters[] = "filters";
34 const char kSwitchIde[] = "ide";
35 const char kSwitchIdeValueEclipse[] = "eclipse";
36 const char kSwitchIdeValueQtCreator[] = "qtcreator";
37 const char kSwitchIdeValueVs[] = "vs";
38 const char kSwitchIdeValueVs2013[] = "vs2013";
39 const char kSwitchIdeValueVs2015[] = "vs2015";
40 const char kSwitchIdeValueVs2017[] = "vs2017";
41 const char kSwitchIdeValueVs2019[] = "vs2019";
42 const char kSwitchIdeValueWinSdk[] = "winsdk";
43 const char kSwitchIdeValueXcode[] = "xcode";
44 const char kSwitchIdeValueJson[] = "json";
45 const char kSwitchNinjaExecutable[] = "ninja-executable";
46 const char kSwitchNinjaExtraArgs[] = "ninja-extra-args";
47 const char kSwitchNoDeps[] = "no-deps";
48 const char kSwitchRootTarget[] = "root-target";
49 const char kSwitchSln[] = "sln";
50 const char kSwitchWorkspace[] = "workspace";
51 const char kSwitchJsonFileName[] = "json-file-name";
52 const char kSwitchJsonIdeScript[] = "json-ide-script";
53 const char kSwitchJsonIdeScriptArgs[] = "json-ide-script-args";
54 const char kSwitchExportCompileCommands[] = "export-compile-commands";
55
56 // Collects Ninja rules for each toolchain. The lock protectes the rules.
57 struct TargetWriteInfo {
58 std::mutex lock;
59 NinjaWriter::PerToolchainRules rules;
60 };
61
62 // Called on worker thread to write the ninja file.
BackgroundDoWrite(TargetWriteInfo * write_info,const Target * target)63 void BackgroundDoWrite(TargetWriteInfo* write_info, const Target* target) {
64 std::string rule = NinjaTargetWriter::RunAndWriteFile(target);
65 DCHECK(!rule.empty());
66
67 {
68 std::lock_guard<std::mutex> lock(write_info->lock);
69 write_info->rules[target->toolchain()].emplace_back(target,
70 std::move(rule));
71 }
72 }
73
74 // Called on the main thread.
ItemResolvedAndGeneratedCallback(TargetWriteInfo * write_info,const BuilderRecord * record)75 void ItemResolvedAndGeneratedCallback(TargetWriteInfo* write_info,
76 const BuilderRecord* record) {
77 const Item* item = record->item();
78 const Target* target = item->AsTarget();
79 if (target) {
80 g_scheduler->ScheduleWork(
81 [write_info, target]() { BackgroundDoWrite(write_info, target); });
82 }
83 }
84
85 // Returns a pointer to the target with the given file as an output, or null
86 // if no targets generate the file. This is brute force since this is an
87 // error condition and performance shouldn't matter.
FindTargetThatGeneratesFile(const Builder & builder,const SourceFile & file)88 const Target* FindTargetThatGeneratesFile(const Builder& builder,
89 const SourceFile& file) {
90 std::vector<const Target*> targets = builder.GetAllResolvedTargets();
91 if (targets.empty())
92 return nullptr;
93
94 OutputFile output_file(targets[0]->settings()->build_settings(), file);
95 for (const Target* target : targets) {
96 for (const auto& cur_output : target->computed_outputs()) {
97 if (cur_output == output_file)
98 return target;
99 }
100 }
101 return nullptr;
102 }
103
104 // Prints an error that the given file was present as a source or input in
105 // the given target(s) but was not generated by any of its dependencies.
PrintInvalidGeneratedInput(const Builder & builder,const SourceFile & file,const std::vector<const Target * > & targets)106 void PrintInvalidGeneratedInput(const Builder& builder,
107 const SourceFile& file,
108 const std::vector<const Target*>& targets) {
109 std::string err;
110
111 // Only show the toolchain labels (which can be confusing) if something
112 // isn't the default.
113 bool show_toolchains = false;
114 const Label& default_toolchain =
115 targets[0]->settings()->default_toolchain_label();
116 for (const Target* target : targets) {
117 if (target->settings()->toolchain_label() != default_toolchain) {
118 show_toolchains = true;
119 break;
120 }
121 }
122
123 const Target* generator = FindTargetThatGeneratesFile(builder, file);
124 if (generator &&
125 generator->settings()->toolchain_label() != default_toolchain)
126 show_toolchains = true;
127
128 const std::string target_str = targets.size() > 1 ? "targets" : "target";
129 err += "The file:\n";
130 err += " " + file.value() + "\n";
131 err += "is listed as an input or source for the " + target_str + ":\n";
132 for (const Target* target : targets)
133 err += " " + target->label().GetUserVisibleName(show_toolchains) + "\n";
134
135 if (generator) {
136 err += "but this file was not generated by any dependencies of the " +
137 target_str + ". The target\nthat generates the file is:\n ";
138 err += generator->label().GetUserVisibleName(show_toolchains);
139 } else {
140 err += "but no targets in the build generate that file.";
141 }
142
143 Err(Location(), "Input to " + target_str + " not generated by a dependency.",
144 err)
145 .PrintToStdout();
146 }
147
CheckForInvalidGeneratedInputs(Setup * setup)148 bool CheckForInvalidGeneratedInputs(Setup* setup) {
149 std::multimap<SourceFile, const Target*> unknown_inputs =
150 g_scheduler->GetUnknownGeneratedInputs();
151 if (unknown_inputs.empty())
152 return true; // No bad files.
153
154 int errors_found = 0;
155 auto cur = unknown_inputs.begin();
156 while (cur != unknown_inputs.end()) {
157 errors_found++;
158 auto end_of_range = unknown_inputs.upper_bound(cur->first);
159
160 // Package the values more conveniently for printing.
161 SourceFile bad_input = cur->first;
162 std::vector<const Target*> targets;
163 while (cur != end_of_range)
164 targets.push_back((cur++)->second);
165
166 PrintInvalidGeneratedInput(setup->builder(), bad_input, targets);
167 OutputString("\n");
168 }
169
170 OutputString(
171 "If you have generated inputs, there needs to be a dependency path "
172 "between the\ntwo targets in addition to just listing the files. For "
173 "indirect dependencies,\nthe intermediate ones must be public_deps. "
174 "data_deps don't count since they're\nonly runtime dependencies. If "
175 "you think a dependency chain exists, it might be\nbecause the chain "
176 "is private. Try \"gn path\" to analyze.\n");
177
178 if (errors_found > 1) {
179 OutputString(base::StringPrintf("\n%d generated input errors found.\n",
180 errors_found),
181 DECORATION_YELLOW);
182 }
183 return false;
184 }
185
RunIdeWriter(const std::string & ide,const BuildSettings * build_settings,const Builder & builder,Err * err)186 bool RunIdeWriter(const std::string& ide,
187 const BuildSettings* build_settings,
188 const Builder& builder,
189 Err* err) {
190 const base::CommandLine* command_line =
191 base::CommandLine::ForCurrentProcess();
192 bool quiet = command_line->HasSwitch(switches::kQuiet);
193 base::ElapsedTimer timer;
194
195 if (ide == kSwitchIdeValueEclipse) {
196 bool res = EclipseWriter::RunAndWriteFile(build_settings, builder, err);
197 if (res && !quiet) {
198 OutputString("Generating Eclipse settings took " +
199 base::Int64ToString(timer.Elapsed().InMilliseconds()) +
200 "ms\n");
201 }
202 return res;
203 } else if (ide == kSwitchIdeValueVs || ide == kSwitchIdeValueVs2013 ||
204 ide == kSwitchIdeValueVs2015 || ide == kSwitchIdeValueVs2017 ||
205 ide == kSwitchIdeValueVs2019) {
206 VisualStudioWriter::Version version = VisualStudioWriter::Version::Vs2019;
207 if (ide == kSwitchIdeValueVs2013)
208 version = VisualStudioWriter::Version::Vs2013;
209 else if (ide == kSwitchIdeValueVs2015)
210 version = VisualStudioWriter::Version::Vs2015;
211 else if (ide == kSwitchIdeValueVs2017)
212 version = VisualStudioWriter::Version::Vs2017;
213
214 std::string sln_name;
215 if (command_line->HasSwitch(kSwitchSln))
216 sln_name = command_line->GetSwitchValueASCII(kSwitchSln);
217 std::string filters;
218 if (command_line->HasSwitch(kSwitchFilters))
219 filters = command_line->GetSwitchValueASCII(kSwitchFilters);
220 std::string win_kit;
221 if (command_line->HasSwitch(kSwitchIdeValueWinSdk))
222 win_kit = command_line->GetSwitchValueASCII(kSwitchIdeValueWinSdk);
223 std::string ninja_extra_args;
224 if (command_line->HasSwitch(kSwitchNinjaExtraArgs))
225 ninja_extra_args =
226 command_line->GetSwitchValueASCII(kSwitchNinjaExtraArgs);
227 bool no_deps = command_line->HasSwitch(kSwitchNoDeps);
228 bool res = VisualStudioWriter::RunAndWriteFiles(
229 build_settings, builder, version, sln_name, filters, win_kit,
230 ninja_extra_args, no_deps, err);
231 if (res && !quiet) {
232 OutputString("Generating Visual Studio projects took " +
233 base::Int64ToString(timer.Elapsed().InMilliseconds()) +
234 "ms\n");
235 }
236 return res;
237 } else if (ide == kSwitchIdeValueXcode) {
238 bool res = XcodeWriter::RunAndWriteFiles(
239 command_line->GetSwitchValueASCII(kSwitchWorkspace),
240 command_line->GetSwitchValueASCII(kSwitchRootTarget),
241 command_line->GetSwitchValueASCII(kSwitchNinjaExecutable),
242 command_line->GetSwitchValueASCII(kSwitchNinjaExtraArgs),
243 command_line->GetSwitchValueASCII(kSwitchFilters), build_settings,
244 builder, err);
245 if (res && !quiet) {
246 OutputString("Generating Xcode projects took " +
247 base::Int64ToString(timer.Elapsed().InMilliseconds()) +
248 "ms\n");
249 }
250 return res;
251 } else if (ide == kSwitchIdeValueQtCreator) {
252 std::string root_target;
253 if (command_line->HasSwitch(kSwitchRootTarget))
254 root_target = command_line->GetSwitchValueASCII(kSwitchRootTarget);
255 bool res = QtCreatorWriter::RunAndWriteFile(build_settings, builder, err,
256 root_target);
257 if (res && !quiet) {
258 OutputString("Generating QtCreator projects took " +
259 base::Int64ToString(timer.Elapsed().InMilliseconds()) +
260 "ms\n");
261 }
262 return res;
263 } else if (ide == kSwitchIdeValueJson) {
264 std::string file_name =
265 command_line->GetSwitchValueASCII(kSwitchJsonFileName);
266 if (file_name.empty())
267 file_name = "project.json";
268 std::string exec_script =
269 command_line->GetSwitchValueASCII(kSwitchJsonIdeScript);
270 std::string exec_script_extra_args =
271 command_line->GetSwitchValueASCII(kSwitchJsonIdeScriptArgs);
272 std::string filters = command_line->GetSwitchValueASCII(kSwitchFilters);
273
274 bool res = JSONProjectWriter::RunAndWriteFiles(
275 build_settings, builder, file_name, exec_script, exec_script_extra_args,
276 filters, quiet, err);
277 if (res && !quiet) {
278 OutputString("Generating JSON projects took " +
279 base::Int64ToString(timer.Elapsed().InMilliseconds()) +
280 "ms\n");
281 }
282 return res;
283 }
284
285 *err = Err(Location(), "Unknown IDE: " + ide);
286 return false;
287 }
288
RunCompileCommandsWriter(const BuildSettings * build_settings,const Builder & builder,Err * err)289 bool RunCompileCommandsWriter(const BuildSettings* build_settings,
290 const Builder& builder,
291 Err* err) {
292 const base::CommandLine* command_line =
293 base::CommandLine::ForCurrentProcess();
294 bool quiet = command_line->HasSwitch(switches::kQuiet);
295 base::ElapsedTimer timer;
296
297 std::string file_name = "compile_commands.json";
298 std::string target_filters =
299 command_line->GetSwitchValueASCII(kSwitchExportCompileCommands);
300
301 bool res = CompileCommandsWriter::RunAndWriteFiles(
302 build_settings, builder, file_name, target_filters, quiet, err);
303 if (res && !quiet) {
304 OutputString("Generating compile_commands took " +
305 base::Int64ToString(timer.Elapsed().InMilliseconds()) +
306 "ms\n");
307 }
308 return res;
309 }
310
311 } // namespace
312
313 const char kGen[] = "gen";
314 const char kGen_HelpShort[] = "gen: Generate ninja files.";
315 const char kGen_Help[] =
316 R"(gn gen [--check] [<ide options>] <out_dir>
317
318 Generates ninja files from the current tree and puts them in the given output
319 directory.
320
321 The output directory can be a source-repo-absolute path name such as:
322 //out/foo
323 Or it can be a directory relative to the current directory such as:
324 out/foo
325
326 "gn gen --check" is the same as running "gn check". "gn gen --check=system" is
327 the same as running "gn check --check-system". See "gn help check" for
328 documentation on that mode.
329
330 See "gn help switches" for the common command-line switches.
331
332 IDE options
333
334 GN optionally generates files for IDE. Possibilities for <ide options>
335
336 --ide=<ide_name>
337 Generate files for an IDE. Currently supported values:
338 "eclipse" - Eclipse CDT settings file.
339 "vs" - Visual Studio project/solution files.
340 (default Visual Studio version: 2019)
341 "vs2013" - Visual Studio 2013 project/solution files.
342 "vs2015" - Visual Studio 2015 project/solution files.
343 "vs2017" - Visual Studio 2017 project/solution files.
344 "vs2019" - Visual Studio 2019 project/solution files.
345 "xcode" - Xcode workspace/solution files.
346 "qtcreator" - QtCreator project files.
347 "json" - JSON file containing target information
348
349 --filters=<path_prefixes>
350 Semicolon-separated list of label patterns used to limit the set of
351 generated projects (see "gn help label_pattern"). Only matching targets
352 and their dependencies will be included in the solution. Only used for
353 Visual Studio, Xcode and JSON.
354
355 Visual Studio Flags
356
357 --sln=<file_name>
358 Override default sln file name ("all"). Solution file is written to the
359 root build directory.
360
361 --no-deps
362 Don't include targets dependencies to the solution. Changes the way how
363 --filters option works. Only directly matching targets are included.
364
365 --winsdk=<sdk_version>
366 Use the specified Windows 10 SDK version to generate project files.
367 As an example, "10.0.15063.0" can be specified to use Creators Update SDK
368 instead of the default one.
369
370 --ninja-extra-args=<string>
371 This string is passed without any quoting to the ninja invocation
372 command-line. Can be used to configure ninja flags, like "-j".
373
374 Xcode Flags
375
376 --workspace=<file_name>
377 Override defaut workspace file name ("all"). The workspace file is
378 written to the root build directory.
379
380 --ninja-executable=<string>
381 Can be used to specify the ninja executable to use when building.
382
383 --ninja-extra-args=<string>
384 This string is passed without any quoting to the ninja invocation
385 command-line. Can be used to configure ninja flags, like "-j".
386
387 --root-target=<target_name>
388 Name of the target corresponding to "All" target in Xcode. If unset,
389 "All" invokes ninja without any target and builds everything.
390
391 QtCreator Flags
392
393 --root-target=<target_name>
394 Name of the root target for which the QtCreator project will be generated
395 to contain files of it and its dependencies. If unset, the whole build
396 graph will be emitted.
397
398
399 Eclipse IDE Support
400
401 GN DOES NOT generate Eclipse CDT projects. Instead, it generates a settings
402 file which can be imported into an Eclipse CDT project. The XML file contains
403 a list of include paths and defines. Because GN does not generate a full
404 .cproject definition, it is not possible to properly define includes/defines
405 for each file individually. Instead, one set of includes/defines is generated
406 for the entire project. This works fairly well but may still result in a few
407 indexer issues here and there.
408
409 Generic JSON Output
410
411 Dumps target information to a JSON file and optionally invokes a
412 python script on the generated file. See the comments at the beginning
413 of json_project_writer.cc and desc_builder.cc for an overview of the JSON
414 file format.
415
416 --json-file-name=<json_file_name>
417 Overrides default file name (project.json) of generated JSON file.
418
419 --json-ide-script=<path_to_python_script>
420 Executes python script after the JSON file is generated. Path can be
421 project absolute (//), system absolute (/) or relative, in which case the
422 output directory will be base. Path to generated JSON file will be first
423 argument when invoking script.
424
425 --json-ide-script-args=<argument>
426 Optional second argument that will passed to executed script.
427
428 Compilation Database
429
430 --export-compile-commands[=<target_name1,target_name2...>]
431 Produces a compile_commands.json file in the root of the build directory
432 containing an array of “command objects”, where each command object
433 specifies one way a translation unit is compiled in the project. If a list
434 of target_name is supplied, only targets that are reachable from the list
435 of target_name will be used for “command objects” generation, otherwise
436 all available targets will be used. This is used for various Clang-based
437 tooling, allowing for the replay of individual compilations independent
438 of the build system.
439 )";
440
RunGen(const std::vector<std::string> & args)441 int RunGen(const std::vector<std::string>& args) {
442 base::ElapsedTimer timer;
443
444 if (args.size() != 1) {
445 Err(Location(), "Need exactly one build directory to generate.",
446 "I expected something more like \"gn gen out/foo\"\n"
447 "You can also see \"gn help gen\".")
448 .PrintToStdout();
449 return 1;
450 }
451
452 // Deliberately leaked to avoid expensive process teardown.
453 Setup* setup = new Setup();
454 // Generate an empty args.gn file if it does not exists
455 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kArgs)) {
456 setup->set_gen_empty_args(true);
457 }
458 if (!setup->DoSetup(args[0], true))
459 return 1;
460
461 const base::CommandLine* command_line =
462 base::CommandLine::ForCurrentProcess();
463 if (command_line->HasSwitch(kSwitchCheck)) {
464 setup->set_check_public_headers(true);
465 if (command_line->GetSwitchValueASCII(kSwitchCheck) == "system")
466 setup->set_check_system_includes(true);
467 }
468
469 // Cause the load to also generate the ninja files for each target.
470 TargetWriteInfo write_info;
471 setup->builder().set_resolved_and_generated_callback(
472 [&write_info](const BuilderRecord* record) {
473 ItemResolvedAndGeneratedCallback(&write_info, record);
474 });
475
476 // Do the actual load. This will also write out the target ninja files.
477 if (!setup->Run())
478 return 1;
479
480 // Sort the targets in each toolchain according to their label. This makes
481 // the ninja files have deterministic content.
482 for (auto& cur_toolchain : write_info.rules) {
483 std::sort(cur_toolchain.second.begin(), cur_toolchain.second.end(),
484 [](const NinjaWriter::TargetRulePair& a,
485 const NinjaWriter::TargetRulePair& b) {
486 return a.first->label() < b.first->label();
487 });
488 }
489
490 Err err;
491 // Write the root ninja files.
492 if (!NinjaWriter::RunAndWriteFiles(&setup->build_settings(), setup->builder(),
493 write_info.rules, &err)) {
494 err.PrintToStdout();
495 return 1;
496 }
497
498 if (!WriteRuntimeDepsFilesIfNecessary(&setup->build_settings(),
499 setup->builder(), &err)) {
500 err.PrintToStdout();
501 return 1;
502 }
503
504 if (!CheckForInvalidGeneratedInputs(setup))
505 return 1;
506
507 if (command_line->HasSwitch(kSwitchIde) &&
508 !RunIdeWriter(command_line->GetSwitchValueASCII(kSwitchIde),
509 &setup->build_settings(), setup->builder(), &err)) {
510 err.PrintToStdout();
511 return 1;
512 }
513
514 if (command_line->HasSwitch(kSwitchExportCompileCommands) &&
515 !RunCompileCommandsWriter(&setup->build_settings(), setup->builder(),
516 &err)) {
517 err.PrintToStdout();
518 return 1;
519 }
520
521 TickDelta elapsed_time = timer.Elapsed();
522
523 if (!command_line->HasSwitch(switches::kQuiet)) {
524 OutputString("Done. ", DECORATION_GREEN);
525
526 size_t targets_collected = 0;
527 for (const auto& rules : write_info.rules)
528 targets_collected += rules.second.size();
529
530 std::string stats =
531 "Made " + base::NumberToString(targets_collected) + " targets from " +
532 base::IntToString(
533 setup->scheduler().input_file_manager()->GetInputFileCount()) +
534 " files in " + base::Int64ToString(elapsed_time.InMilliseconds()) +
535 "ms\n";
536 OutputString(stats);
537 }
538
539 return 0;
540 }
541
542 } // namespace commands
543