• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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