| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/atomicops.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/timer/elapsed_timer.h" |
| #include "tools/gn/build_settings.h" |
| #include "tools/gn/commands.h" |
| #include "tools/gn/eclipse_writer.h" |
| #include "tools/gn/json_project_writer.h" |
| #include "tools/gn/ninja_target_writer.h" |
| #include "tools/gn/ninja_writer.h" |
| #include "tools/gn/qt_creator_writer.h" |
| #include "tools/gn/runtime_deps.h" |
| #include "tools/gn/scheduler.h" |
| #include "tools/gn/setup.h" |
| #include "tools/gn/standard_out.h" |
| #include "tools/gn/switches.h" |
| #include "tools/gn/target.h" |
| #include "tools/gn/visual_studio_writer.h" |
| #include "tools/gn/xcode_writer.h" |
| |
| namespace commands { |
| |
| namespace { |
| |
| const char kSwitchCheck[] = "check"; |
| const char kSwitchFilters[] = "filters"; |
| const char kSwitchIde[] = "ide"; |
| const char kSwitchIdeValueEclipse[] = "eclipse"; |
| const char kSwitchIdeValueQtCreator[] = "qtcreator"; |
| const char kSwitchIdeValueVs[] = "vs"; |
| const char kSwitchIdeValueVs2013[] = "vs2013"; |
| const char kSwitchIdeValueVs2015[] = "vs2015"; |
| const char kSwitchIdeValueXcode[] = "xcode"; |
| const char kSwitchIdeValueJson[] = "json"; |
| const char kSwitchNinjaExtraArgs[] = "ninja-extra-args"; |
| const char kSwitchNoDeps[] = "no-deps"; |
| const char kSwitchRootTarget[] = "root-target"; |
| const char kSwitchSln[] = "sln"; |
| const char kSwitchWorkspace[] = "workspace"; |
| const char kSwitchJsonFileName[] = "json-file-name"; |
| const char kSwitchJsonIdeScript[] = "json-ide-script"; |
| const char kSwitchJsonIdeScriptArgs[] = "json-ide-script-args"; |
| |
| // Collects Ninja rules for each toolchain. The lock protectes the rules. |
| struct TargetWriteInfo { |
| base::Lock lock; |
| NinjaWriter::PerToolchainRules rules; |
| }; |
| |
| // Called on worker thread to write the ninja file. |
| void BackgroundDoWrite(TargetWriteInfo* write_info, const Target* target) { |
| std::string rule = NinjaTargetWriter::RunAndWriteFile(target); |
| DCHECK(!rule.empty()); |
| |
| { |
| base::AutoLock lock(write_info->lock); |
| write_info->rules[target->toolchain()].emplace_back( |
| target, std::move(rule)); |
| } |
| |
| g_scheduler->DecrementWorkCount(); |
| } |
| |
| // Called on the main thread. |
| void ItemResolvedAndGeneratedCallback(TargetWriteInfo* write_info, |
| const BuilderRecord* record) { |
| const Item* item = record->item(); |
| const Target* target = item->AsTarget(); |
| if (target) { |
| g_scheduler->IncrementWorkCount(); |
| g_scheduler->ScheduleWork(base::Bind(&BackgroundDoWrite, |
| write_info, target)); |
| } |
| } |
| |
| // Returns a pointer to the target with the given file as an output, or null |
| // if no targets generate the file. This is brute force since this is an |
| // error condition and performance shouldn't matter. |
| const Target* FindTargetThatGeneratesFile(const Builder& builder, |
| const SourceFile& file) { |
| std::vector<const Target*> targets = builder.GetAllResolvedTargets(); |
| if (targets.empty()) |
| return nullptr; |
| |
| OutputFile output_file(targets[0]->settings()->build_settings(), file); |
| for (const Target* target : targets) { |
| for (const auto& cur_output : target->computed_outputs()) { |
| if (cur_output == output_file) |
| return target; |
| } |
| } |
| return nullptr; |
| } |
| |
| // Prints an error that the given file was present as a source or input in |
| // the given target(s) but was not generated by any of its dependencies. |
| void PrintInvalidGeneratedInput(const Builder& builder, |
| const SourceFile& file, |
| const std::vector<const Target*>& targets) { |
| std::string err; |
| |
| // Only show the toolchain labels (which can be confusing) if something |
| // isn't the default. |
| bool show_toolchains = false; |
| const Label& default_toolchain = |
| targets[0]->settings()->default_toolchain_label(); |
| for (const Target* target : targets) { |
| if (target->settings()->toolchain_label() != default_toolchain) { |
| show_toolchains = true; |
| break; |
| } |
| } |
| |
| const Target* generator = FindTargetThatGeneratesFile(builder, file); |
| if (generator && |
| generator->settings()->toolchain_label() != default_toolchain) |
| show_toolchains = true; |
| |
| const std::string target_str = targets.size() > 1 ? "targets" : "target"; |
| err += "The file:\n"; |
| err += " " + file.value() + "\n"; |
| err += "is listed as an input or source for the " + target_str + ":\n"; |
| for (const Target* target : targets) |
| err += " " + target->label().GetUserVisibleName(show_toolchains) + "\n"; |
| |
| if (generator) { |
| err += "but this file was not generated by any dependencies of the " + |
| target_str + ". The target\nthat generates the file is:\n "; |
| err += generator->label().GetUserVisibleName(show_toolchains); |
| } else { |
| err += "but no targets in the build generate that file."; |
| } |
| |
| Err(Location(), "Input to " + target_str + " not generated by a dependency.", |
| err).PrintToStdout(); |
| } |
| |
| bool CheckForInvalidGeneratedInputs(Setup* setup) { |
| std::multimap<SourceFile, const Target*> unknown_inputs = |
| g_scheduler->GetUnknownGeneratedInputs(); |
| if (unknown_inputs.empty()) |
| return true; // No bad files. |
| |
| int errors_found = 0; |
| auto cur = unknown_inputs.begin(); |
| while (cur != unknown_inputs.end()) { |
| errors_found++; |
| auto end_of_range = unknown_inputs.upper_bound(cur->first); |
| |
| // Package the values more conveniently for printing. |
| SourceFile bad_input = cur->first; |
| std::vector<const Target*> targets; |
| while (cur != end_of_range) |
| targets.push_back((cur++)->second); |
| |
| PrintInvalidGeneratedInput(setup->builder(), bad_input, targets); |
| OutputString("\n"); |
| } |
| |
| OutputString( |
| "If you have generated inputs, there needs to be a dependency path " |
| "between the\ntwo targets in addition to just listing the files. For " |
| "indirect dependencies,\nthe intermediate ones must be public_deps. " |
| "data_deps don't count since they're\nonly runtime dependencies. If " |
| "you think a dependency chain exists, it might be\nbecause the chain " |
| "is private. Try \"gn path\" to analyze.\n"); |
| |
| if (errors_found > 1) { |
| OutputString(base::StringPrintf("\n%d generated input errors found.\n", |
| errors_found), DECORATION_YELLOW); |
| } |
| return false; |
| } |
| |
| bool RunIdeWriter(const std::string& ide, |
| const BuildSettings* build_settings, |
| const Builder& builder, |
| Err* err) { |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| bool quiet = command_line->HasSwitch(switches::kQuiet); |
| base::ElapsedTimer timer; |
| |
| if (ide == kSwitchIdeValueEclipse) { |
| bool res = EclipseWriter::RunAndWriteFile(build_settings, builder, err); |
| if (res && !quiet) { |
| OutputString("Generating Eclipse settings took " + |
| base::Int64ToString(timer.Elapsed().InMilliseconds()) + |
| "ms\n"); |
| } |
| return res; |
| } else if (ide == kSwitchIdeValueVs || ide == kSwitchIdeValueVs2013 || |
| ide == kSwitchIdeValueVs2015) { |
| VisualStudioWriter::Version version = |
| ide == kSwitchIdeValueVs2013 ? VisualStudioWriter::Version::Vs2013 |
| : VisualStudioWriter::Version::Vs2015; |
| std::string sln_name; |
| if (command_line->HasSwitch(kSwitchSln)) |
| sln_name = command_line->GetSwitchValueASCII(kSwitchSln); |
| std::string filters; |
| if (command_line->HasSwitch(kSwitchFilters)) |
| filters = command_line->GetSwitchValueASCII(kSwitchFilters); |
| bool no_deps = command_line->HasSwitch(kSwitchNoDeps); |
| bool res = VisualStudioWriter::RunAndWriteFiles( |
| build_settings, builder, version, sln_name, filters, no_deps, err); |
| if (res && !quiet) { |
| OutputString("Generating Visual Studio projects took " + |
| base::Int64ToString(timer.Elapsed().InMilliseconds()) + |
| "ms\n"); |
| } |
| return res; |
| } else if (ide == kSwitchIdeValueXcode) { |
| bool res = XcodeWriter::RunAndWriteFiles( |
| command_line->GetSwitchValueASCII(kSwitchWorkspace), |
| command_line->GetSwitchValueASCII(kSwitchRootTarget), |
| command_line->GetSwitchValueASCII(kSwitchNinjaExtraArgs), |
| command_line->GetSwitchValueASCII(kSwitchFilters), build_settings, |
| builder, err); |
| if (res && !quiet) { |
| OutputString("Generating Xcode projects took " + |
| base::Int64ToString(timer.Elapsed().InMilliseconds()) + |
| "ms\n"); |
| } |
| return res; |
| } else if (ide == kSwitchIdeValueQtCreator) { |
| std::string root_target; |
| if (command_line->HasSwitch(kSwitchRootTarget)) |
| root_target = command_line->GetSwitchValueASCII(kSwitchRootTarget); |
| bool res = QtCreatorWriter::RunAndWriteFile(build_settings, builder, err, |
| root_target); |
| if (res && !quiet) { |
| OutputString("Generating QtCreator projects took " + |
| base::Int64ToString(timer.Elapsed().InMilliseconds()) + |
| "ms\n"); |
| } |
| return res; |
| } else if (ide == kSwitchIdeValueJson) { |
| std::string file_name = |
| command_line->GetSwitchValueASCII(kSwitchJsonFileName); |
| if (file_name.empty()) |
| file_name = "project.json"; |
| std::string exec_script = |
| command_line->GetSwitchValueASCII(kSwitchJsonIdeScript); |
| std::string exec_script_extra_args = |
| command_line->GetSwitchValueASCII(kSwitchJsonIdeScriptArgs); |
| std::string filters = command_line->GetSwitchValueASCII(kSwitchFilters); |
| |
| bool res = JSONProjectWriter::RunAndWriteFiles( |
| build_settings, builder, file_name, exec_script, exec_script_extra_args, |
| filters, quiet, err); |
| if (res && !quiet) { |
| OutputString("Generating JSON projects took " + |
| base::Int64ToString(timer.Elapsed().InMilliseconds()) + |
| "ms\n"); |
| } |
| return res; |
| } |
| |
| *err = Err(Location(), "Unknown IDE: " + ide); |
| return false; |
| } |
| |
| } // namespace |
| |
| const char kGen[] = "gen"; |
| const char kGen_HelpShort[] = "gen: Generate ninja files."; |
| const char kGen_Help[] = |
| "gn gen: Generate ninja files.\n" |
| "\n" |
| " gn gen [<ide options>] <out_dir>\n" |
| "\n" |
| " Generates ninja files from the current tree and puts them in the given\n" |
| " output directory.\n" |
| "\n" |
| " The output directory can be a source-repo-absolute path name such as:\n" |
| " //out/foo\n" |
| " Or it can be a directory relative to the current directory such as:\n" |
| " out/foo\n" |
| "\n" |
| " See \"gn help switches\" for the common command-line switches.\n" |
| "\n" |
| "IDE options\n" |
| "\n" |
| " GN optionally generates files for IDE. Possibilities for <ide options>\n" |
| "\n" |
| " --ide=<ide_name>\n" |
| " Generate files for an IDE. Currently supported values:\n" |
| " \"eclipse\" - Eclipse CDT settings file.\n" |
| " \"vs\" - Visual Studio project/solution files.\n" |
| " (default Visual Studio version: 2015)\n" |
| " \"vs2013\" - Visual Studio 2013 project/solution files.\n" |
| " \"vs2015\" - Visual Studio 2015 project/solution files.\n" |
| " \"xcode\" - Xcode workspace/solution files.\n" |
| " \"qtcreator\" - QtCreator project files.\n" |
| " \"json\" - JSON file containing target information\n" |
| "\n" |
| " --filters=<path_prefixes>\n" |
| " Semicolon-separated list of label patterns used to limit the set\n" |
| " of generated projects (see \"gn help label_pattern\"). Only\n" |
| " matching targets and their dependencies will be included in the\n" |
| " solution. Only used for Visual Studio, Xcode and JSON.\n" |
| "\n" |
| "Visual Studio Flags\n" |
| "\n" |
| " --sln=<file_name>\n" |
| " Override default sln file name (\"all\"). Solution file is written\n" |
| " to the root build directory.\n" |
| "\n" |
| " --no-deps\n" |
| " Don't include targets dependencies to the solution. Changes the\n" |
| " way how --filters option works. Only directly matching targets are\n" |
| " included.\n" |
| "\n" |
| "Xcode Flags\n" |
| "\n" |
| " --workspace=<file_name>\n" |
| " Override defaut workspace file name (\"all\"). The workspace file\n" |
| " is written to the root build directory.\n" |
| "\n" |
| " --ninja-extra-args=<string>\n" |
| " This string is passed without any quoting to the ninja invocation\n" |
| " command-line. Can be used to configure ninja flags, like \"-j\" if\n" |
| " using goma for example.\n" |
| "\n" |
| " --root-target=<target_name>\n" |
| " Name of the target corresponding to \"All\" target in Xcode.\n" |
| " If unset, \"All\" invokes ninja without any target\n" |
| " and builds everything.\n" |
| "\n" |
| "QtCreator Flags\n" |
| "\n" |
| " --root-target=<target_name>\n" |
| " Name of the root target for which the QtCreator project will be\n" |
| " generated to contain files of it and its dependencies. If unset, \n" |
| " the whole build graph will be emitted.\n" |
| "\n" |
| "\n" |
| "Eclipse IDE Support\n" |
| "\n" |
| " GN DOES NOT generate Eclipse CDT projects. Instead, it generates a\n" |
| " settings file which can be imported into an Eclipse CDT project. The\n" |
| " XML file contains a list of include paths and defines. Because GN does\n" |
| " not generate a full .cproject definition, it is not possible to\n" |
| " properly define includes/defines for each file individually.\n" |
| " Instead, one set of includes/defines is generated for the entire\n" |
| " project. This works fairly well but may still result in a few indexer\n" |
| " issues here and there.\n" |
| "\n" |
| "Generic JSON Output\n" |
| "\n" |
| " Dumps target information to JSON file and optionally invokes python\n" |
| " script on generated file.\n" |
| " See comments at the beginning of json_project_writer.cc and\n" |
| " desc_builder.cc for overview of JSON file format.\n" |
| "\n" |
| " --json-file-name=<json_file_name>\n" |
| " Overrides default file name (project.json) of generated JSON file.\n" |
| "\n" |
| " --json-ide-script=<path_to_python_script>\n" |
| " Executes python script after the JSON file is generated.\n" |
| " Path can be project absolute (//), system absolute (/) or\n" |
| " relative, in which case the output directory will be base.\n" |
| " Path to generated JSON file will be first argument when invoking\n" |
| " script.\n" |
| "\n" |
| " --json-ide-script-args=<argument>\n" |
| " Optional second argument that will passed to executed script.\n"; |
| |
| int RunGen(const std::vector<std::string>& args) { |
| base::ElapsedTimer timer; |
| |
| if (args.size() != 1) { |
| Err(Location(), "Need exactly one build directory to generate.", |
| "I expected something more like \"gn gen out/foo\"\n" |
| "You can also see \"gn help gen\".").PrintToStdout(); |
| return 1; |
| } |
| |
| // Deliberately leaked to avoid expensive process teardown. |
| Setup* setup = new Setup(); |
| if (!setup->DoSetup(args[0], true)) |
| return 1; |
| |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(kSwitchCheck)) |
| setup->set_check_public_headers(true); |
| |
| // Cause the load to also generate the ninja files for each target. |
| TargetWriteInfo write_info; |
| setup->builder().set_resolved_and_generated_callback( |
| base::Bind(&ItemResolvedAndGeneratedCallback, &write_info)); |
| |
| // Do the actual load. This will also write out the target ninja files. |
| if (!setup->Run()) |
| return 1; |
| |
| // Sort the targets in each toolchain according to their label. This makes |
| // the ninja files have deterministic content. |
| for (auto& cur_toolchain : write_info.rules) { |
| std::sort(cur_toolchain.second.begin(), cur_toolchain.second.end(), |
| [](const NinjaWriter::TargetRulePair& a, |
| const NinjaWriter::TargetRulePair& b) { |
| return a.first->label() < b.first->label(); |
| }); |
| } |
| |
| Err err; |
| // Write the root ninja files. |
| if (!NinjaWriter::RunAndWriteFiles(&setup->build_settings(), |
| setup->builder(), |
| write_info.rules, |
| &err)) { |
| err.PrintToStdout(); |
| return 1; |
| } |
| |
| if (!WriteRuntimeDepsFilesIfNecessary(setup->builder(), &err)) { |
| err.PrintToStdout(); |
| return 1; |
| } |
| |
| if (!CheckForInvalidGeneratedInputs(setup)) |
| return 1; |
| |
| if (command_line->HasSwitch(kSwitchIde) && |
| !RunIdeWriter(command_line->GetSwitchValueASCII(kSwitchIde), |
| &setup->build_settings(), setup->builder(), &err)) { |
| err.PrintToStdout(); |
| return 1; |
| } |
| |
| base::TimeDelta elapsed_time = timer.Elapsed(); |
| |
| if (!command_line->HasSwitch(switches::kQuiet)) { |
| OutputString("Done. ", DECORATION_GREEN); |
| |
| size_t targets_collected = 0; |
| for (const auto& rules : write_info.rules) |
| targets_collected += rules.second.size(); |
| |
| std::string stats = "Made " + base::SizeTToString(targets_collected) + |
| " targets from " + |
| base::IntToString( |
| setup->scheduler().input_file_manager()->GetInputFileCount()) + |
| " files in " + |
| base::Int64ToString(elapsed_time.InMilliseconds()) + "ms\n"; |
| OutputString(stats); |
| } |
| |
| return 0; |
| } |
| |
| } // namespace commands |