| // 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 <stddef.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 |  | 
 | #include <map> | 
 |  | 
 | #include "base/command_line.h" | 
 | #include "base/environment.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/json/json_writer.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "tools/gn/commands.h" | 
 | #include "tools/gn/filesystem_utils.h" | 
 | #include "tools/gn/input_file.h" | 
 | #include "tools/gn/parse_tree.h" | 
 | #include "tools/gn/setup.h" | 
 | #include "tools/gn/standard_out.h" | 
 | #include "tools/gn/tokenizer.h" | 
 | #include "tools/gn/trace.h" | 
 | #include "util/build_config.h" | 
 |  | 
 | #if defined(OS_WIN) | 
 | #include <windows.h> | 
 |  | 
 | #include <shellapi.h> | 
 | #endif | 
 |  | 
 | namespace commands { | 
 |  | 
 | namespace { | 
 |  | 
 | const char kSwitchList[] = "list"; | 
 | const char kSwitchShort[] = "short"; | 
 | const char kSwitchOverridesOnly[] = "overrides-only"; | 
 | const char kSwitchJson[] = "json"; | 
 |  | 
 | bool DoesLineBeginWithComment(const base::StringPiece& line) { | 
 |   // Skip whitespace. | 
 |   size_t i = 0; | 
 |   while (i < line.size() && base::IsAsciiWhitespace(line[i])) | 
 |     i++; | 
 |  | 
 |   return i < line.size() && line[i] == '#'; | 
 | } | 
 |  | 
 | // Returns the offset of the beginning of the line identified by |offset|. | 
 | size_t BackUpToLineBegin(const std::string& data, size_t offset) { | 
 |   // Degenerate case of an empty line. Below we'll try to return the | 
 |   // character after the newline, but that will be incorrect in this case. | 
 |   if (offset == 0 || Tokenizer::IsNewline(data, offset)) | 
 |     return offset; | 
 |  | 
 |   size_t cur = offset; | 
 |   do { | 
 |     cur--; | 
 |     if (Tokenizer::IsNewline(data, cur)) | 
 |       return cur + 1;  // Want the first character *after* the newline. | 
 |   } while (cur > 0); | 
 |   return 0; | 
 | } | 
 |  | 
 | // Assumes DoesLineBeginWithComment(), this strips the # character from the | 
 | // beginning and normalizes preceding whitespace. | 
 | std::string StripHashFromLine(const base::StringPiece& line, bool pad) { | 
 |   // Replace the # sign and everything before it with 3 spaces, so that a | 
 |   // normal comment that has a space after the # will be indented 4 spaces | 
 |   // (which makes our formatting come out nicely). If the comment is indented | 
 |   // from there, we want to preserve that indenting. | 
 |   std::string line_stripped = line.substr(line.find('#') + 1).as_string(); | 
 |   if (pad) | 
 |     return "   " + line_stripped; | 
 |  | 
 |   // If not padding, strip the leading space if present. | 
 |   if (!line_stripped.empty() && line_stripped[0] == ' ') | 
 |     return line_stripped.substr(1); | 
 |   return line_stripped; | 
 | } | 
 |  | 
 | // Tries to find the comment before the setting of the given value. | 
 | void GetContextForValue(const Value& value, | 
 |                         std::string* location_str, | 
 |                         int* line_no, | 
 |                         std::string* comment, | 
 |                         bool pad_comment = true) { | 
 |   Location location = value.origin()->GetRange().begin(); | 
 |   const InputFile* file = location.file(); | 
 |   if (!file) | 
 |     return; | 
 |  | 
 |   *location_str = file->name().value(); | 
 |   *line_no = location.line_number(); | 
 |  | 
 |   const std::string& data = file->contents(); | 
 |   size_t line_off = | 
 |       Tokenizer::ByteOffsetOfNthLine(data, location.line_number()); | 
 |  | 
 |   while (line_off > 1) { | 
 |     line_off -= 2;  // Back up to end of previous line. | 
 |     size_t previous_line_offset = BackUpToLineBegin(data, line_off); | 
 |  | 
 |     base::StringPiece line(&data[previous_line_offset], | 
 |                            line_off - previous_line_offset + 1); | 
 |     if (!DoesLineBeginWithComment(line)) | 
 |       break; | 
 |  | 
 |     comment->insert(0, StripHashFromLine(line, pad_comment) + "\n"); | 
 |     line_off = previous_line_offset; | 
 |   } | 
 | } | 
 |  | 
 | // Prints the value and origin for a default value. Default values always list | 
 | // an origin and if there is no origin, print a message about it being | 
 | // internally set. Overrides can't be internally set so the location handling | 
 | // is a bit different. | 
 | // | 
 | // The default value also contains the docstring. | 
 | void PrintDefaultValueInfo(base::StringPiece name, const Value& value) { | 
 |   OutputString(value.ToString(true) + "\n"); | 
 |   if (value.origin()) { | 
 |     int line_no; | 
 |     std::string location, comment; | 
 |     GetContextForValue(value, &location, &line_no, &comment); | 
 |     OutputString("      From " + location + ":" + base::IntToString(line_no) + | 
 |                  "\n"); | 
 |     if (!comment.empty()) | 
 |       OutputString("\n" + comment); | 
 |   } else { | 
 |     OutputString("      (Internally set; try `gn help " + name.as_string() + | 
 |                  "`.)\n"); | 
 |   } | 
 | } | 
 |  | 
 | // Override value is null if there is no override. | 
 | void PrintArgHelp(const base::StringPiece& name, | 
 |                   const Args::ValueWithOverride& val) { | 
 |   OutputString(name.as_string(), DECORATION_YELLOW); | 
 |   OutputString("\n"); | 
 |  | 
 |   if (val.has_override) { | 
 |     // Override present, print both it and the default. | 
 |     OutputString("    Current value = " + val.override_value.ToString(true) + | 
 |                  "\n"); | 
 |     if (val.override_value.origin()) { | 
 |       int line_no; | 
 |       std::string location, comment; | 
 |       GetContextForValue(val.override_value, &location, &line_no, &comment); | 
 |       OutputString("      From " + location + ":" + base::IntToString(line_no) + | 
 |                    "\n"); | 
 |     } | 
 |     OutputString("    Overridden from the default = "); | 
 |     PrintDefaultValueInfo(name, val.default_value); | 
 |   } else { | 
 |     // No override. | 
 |     OutputString("    Current value (from the default) = "); | 
 |     PrintDefaultValueInfo(name, val.default_value); | 
 |   } | 
 | } | 
 |  | 
 | void BuildArgJson(base::Value& dict, | 
 |                   const base::StringPiece& name, | 
 |                   const Args::ValueWithOverride& arg, | 
 |                   bool short_only) { | 
 |   assert(dict.is_dict()); | 
 |  | 
 |   // Fetch argument name. | 
 |   dict.SetKey("name", base::Value(name)); | 
 |  | 
 |   // Fetch overridden value inforrmation (if present). | 
 |   if (arg.has_override) { | 
 |     base::DictionaryValue override_dict; | 
 |     override_dict.SetKey("value", | 
 |                          base::Value(arg.override_value.ToString(true))); | 
 |     if (arg.override_value.origin() && !short_only) { | 
 |       int line_no; | 
 |       std::string location, comment; | 
 |       GetContextForValue(arg.override_value, &location, &line_no, &comment, | 
 |                          /*pad_comment=*/false); | 
 |       override_dict.SetKey("file", base::Value(location)); | 
 |       override_dict.SetKey("line", base::Value(line_no)); | 
 |     } | 
 |     dict.SetKey("current", std::move(override_dict)); | 
 |   } | 
 |  | 
 |   // Fetch default value information, and comment (if present). | 
 |   base::DictionaryValue default_dict; | 
 |   std::string comment; | 
 |   default_dict.SetKey("value", base::Value(arg.default_value.ToString(true))); | 
 |   if (arg.default_value.origin() && !short_only) { | 
 |     int line_no; | 
 |     std::string location; | 
 |     GetContextForValue(arg.default_value, &location, &line_no, &comment, | 
 |                        /*pad_comment=*/false); | 
 |     default_dict.SetKey("file", base::Value(location)); | 
 |     default_dict.SetKey("line", base::Value(line_no)); | 
 |   } | 
 |   dict.SetKey("default", std::move(default_dict)); | 
 |   if (!comment.empty() && !short_only) | 
 |     dict.SetKey("comment", base::Value(comment)); | 
 | } | 
 |  | 
 | int ListArgs(const std::string& build_dir) { | 
 |   // Deliberately leaked to avoid expensive process teardown. | 
 |   Setup* setup = new Setup; | 
 |   if (!setup->DoSetup(build_dir, false) || !setup->Run()) | 
 |     return 1; | 
 |  | 
 |   Args::ValueWithOverrideMap args = | 
 |       setup->build_settings().build_args().GetAllArguments(); | 
 |   std::string list_value = | 
 |       base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchList); | 
 |   if (!list_value.empty()) { | 
 |     // List just the one specified as the parameter to --list. | 
 |     auto found = args.find(list_value); | 
 |     if (found == args.end()) { | 
 |       Err(Location(), "Unknown build argument.", | 
 |           "You asked for \"" + list_value + | 
 |               "\" which I didn't find in any " | 
 |               "build file\nassociated with this build.") | 
 |           .PrintToStdout(); | 
 |       return 1; | 
 |     } | 
 |  | 
 |     // Delete everything from the map except the one requested. | 
 |     Args::ValueWithOverrideMap::value_type preserved = *found; | 
 |     args.clear(); | 
 |     args.insert(preserved); | 
 |   } | 
 |  | 
 |   // Cache this to avoid looking it up for each |arg| in the loops below. | 
 |   const bool overrides_only = | 
 |       base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchOverridesOnly); | 
 |   const bool short_only = | 
 |       base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchShort); | 
 |  | 
 |   if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchJson)) { | 
 |     // Convert all args to JSON, serialize and print them | 
 |     auto list = std::make_unique<base::ListValue>(); | 
 |     for (const auto& arg : args) { | 
 |       if (overrides_only && !arg.second.has_override) | 
 |         continue; | 
 |       list->GetList().emplace_back(base::DictionaryValue()); | 
 |       BuildArgJson(list->GetList().back(), arg.first, arg.second, short_only); | 
 |     } | 
 |     std::string s; | 
 |     base::JSONWriter::WriteWithOptions( | 
 |         *list.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s); | 
 |     OutputString(s); | 
 |     return 0; | 
 |   } | 
 |  | 
 |   if (short_only) { | 
 |     // Short <key>=<current_value> output. | 
 |     for (const auto& arg : args) { | 
 |       if (overrides_only && !arg.second.has_override) | 
 |         continue; | 
 |       OutputString(arg.first.as_string()); | 
 |       OutputString(" = "); | 
 |       if (arg.second.has_override) | 
 |         OutputString(arg.second.override_value.ToString(true)); | 
 |       else | 
 |         OutputString(arg.second.default_value.ToString(true)); | 
 |       OutputString("\n"); | 
 |     } | 
 |     return 0; | 
 |   } | 
 |  | 
 |   // Long output. | 
 |   for (const auto& arg : args) { | 
 |     if (overrides_only && !arg.second.has_override) | 
 |       continue; | 
 |     PrintArgHelp(arg.first, arg.second); | 
 |     OutputString("\n"); | 
 |   } | 
 |  | 
 |   return 0; | 
 | } | 
 |  | 
 | #if defined(OS_WIN) | 
 |  | 
 | bool RunEditor(const base::FilePath& file_to_edit) { | 
 |   SHELLEXECUTEINFO info; | 
 |   memset(&info, 0, sizeof(info)); | 
 |   info.cbSize = sizeof(info); | 
 |   info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_CLASSNAME; | 
 |   info.lpFile = file_to_edit.value().c_str(); | 
 |   info.nShow = SW_SHOW; | 
 |   info.lpClass = L".txt"; | 
 |   if (!::ShellExecuteEx(&info)) { | 
 |     Err(Location(), "Couldn't run editor.", | 
 |         "Just edit \"" + FilePathToUTF8(file_to_edit) + "\" manually instead.") | 
 |         .PrintToStdout(); | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (!info.hProcess) { | 
 |     // Windows re-used an existing process. | 
 |     OutputString("\"" + FilePathToUTF8(file_to_edit) + | 
 |                  "\" opened in editor, save it and press <Enter> when done.\n"); | 
 |     getchar(); | 
 |   } else { | 
 |     OutputString("Waiting for editor on \"" + FilePathToUTF8(file_to_edit) + | 
 |                  "\"...\n"); | 
 |     ::WaitForSingleObject(info.hProcess, INFINITE); | 
 |     ::CloseHandle(info.hProcess); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | #else  // POSIX | 
 |  | 
 | bool RunEditor(const base::FilePath& file_to_edit) { | 
 |   const char* editor_ptr = getenv("GN_EDITOR"); | 
 |   if (!editor_ptr) | 
 |     editor_ptr = getenv("VISUAL"); | 
 |   if (!editor_ptr) | 
 |     editor_ptr = getenv("EDITOR"); | 
 |   if (!editor_ptr) | 
 |     editor_ptr = "vi"; | 
 |  | 
 |   std::string cmd(editor_ptr); | 
 |   cmd.append(" \""); | 
 |  | 
 |   // Its impossible to do this properly since we don't know the user's shell, | 
 |   // but quoting and escaping internal quotes should handle 99.999% of all | 
 |   // cases. | 
 |   std::string escaped_name = file_to_edit.value(); | 
 |   base::ReplaceSubstringsAfterOffset(&escaped_name, 0, "\"", "\\\""); | 
 |   cmd.append(escaped_name); | 
 |   cmd.push_back('"'); | 
 |  | 
 |   OutputString("Waiting for editor on \"" + file_to_edit.value() + "\"...\n"); | 
 |   return system(cmd.c_str()) == 0; | 
 | } | 
 |  | 
 | #endif | 
 |  | 
 | int EditArgsFile(const std::string& build_dir) { | 
 |   { | 
 |     // Scope the setup. We only use it for some basic state. We'll do the | 
 |     // "real" build below in the gen command. | 
 |     Setup setup; | 
 |     // Don't fill build arguments. We're about to edit the file which supplies | 
 |     // these in the first place. | 
 |     setup.set_fill_arguments(false); | 
 |     if (!setup.DoSetup(build_dir, true)) | 
 |       return 1; | 
 |  | 
 |     // Ensure the file exists. Need to normalize path separators since on | 
 |     // Windows they can come out as forward slashes here, and that confuses some | 
 |     // of the commands. | 
 |     BuildSettings build_settings = setup.build_settings(); | 
 |     base::FilePath arg_file = | 
 |         build_settings.GetFullPath(setup.GetBuildArgFile()) | 
 |             .NormalizePathSeparators(); | 
 |     if (!base::PathExists(arg_file)) { | 
 |       std::string argfile_default_contents = | 
 |           "# Build arguments go here.\n" | 
 |           "# See \"gn args <out_dir> --list\" for available build " | 
 |           "arguments.\n"; | 
 |  | 
 |       SourceFile template_path = build_settings.arg_file_template_path(); | 
 |       if (!template_path.is_null()) { | 
 |         base::FilePath full_path = | 
 |             build_settings.GetFullPath(template_path).NormalizePathSeparators(); | 
 |         if (!base::PathExists(full_path)) { | 
 |           Err err = | 
 |               Err(Location(), std::string("Can't load arg_file_template:\n  ") + | 
 |                                   template_path.value()); | 
 |           err.PrintToStdout(); | 
 |           return 1; | 
 |         } | 
 |  | 
 |         // Ignore the return code; if the read fails (unlikely), we'll just | 
 |         // use the default contents. | 
 |         base::ReadFileToString(full_path, &argfile_default_contents); | 
 |       } | 
 | #if defined(OS_WIN) | 
 |       // Use Windows lineendings for this file since it will often open in | 
 |       // Notepad which can't handle Unix ones. | 
 |       base::ReplaceSubstringsAfterOffset(&argfile_default_contents, 0, "\n", | 
 |                                          "\r\n"); | 
 | #endif | 
 |       base::CreateDirectory(arg_file.DirName()); | 
 |       base::WriteFile(arg_file, argfile_default_contents.c_str(), | 
 |                       static_cast<int>(argfile_default_contents.size())); | 
 |     } | 
 |  | 
 |     ScopedTrace editor_trace(TraceItem::TRACE_SETUP, "Waiting for editor"); | 
 |     if (!RunEditor(arg_file)) | 
 |       return 1; | 
 |   } | 
 |  | 
 |   // Now do a normal "gen" command. | 
 |   OutputString("Generating files...\n"); | 
 |   std::vector<std::string> gen_commands; | 
 |   gen_commands.push_back(build_dir); | 
 |   return RunGen(gen_commands); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | const char kArgs[] = "args"; | 
 | const char kArgs_HelpShort[] = | 
 |     "args: Display or configure arguments declared by the build."; | 
 | const char kArgs_Help[] = | 
 |     R"(gn args: (command-line tool) | 
 |  | 
 |   Display or configure arguments declared by the build. | 
 |  | 
 |     gn args <out_dir> [--list] [--short] [--args] [--overrides-only] | 
 |  | 
 |   See also "gn help buildargs" for a more high-level overview of how | 
 |   build arguments work. | 
 |  | 
 | Usage | 
 |  | 
 |   gn args <out_dir> | 
 |       Open the arguments for the given build directory in an editor. If the | 
 |       given build directory doesn't exist, it will be created and an empty args | 
 |       file will be opened in the editor. You would type something like this | 
 |       into that file: | 
 |           enable_doom_melon=false | 
 |           os="android" | 
 |  | 
 |       To find your editor on Posix, GN will search the environment variables in | 
 |       order: GN_EDITOR, VISUAL, and EDITOR. On Windows GN will open the command | 
 |       associated with .txt files. | 
 |  | 
 |       Note: you can edit the build args manually by editing the file "args.gn" | 
 |       in the build directory and then running "gn gen <out_dir>". | 
 |  | 
 |   gn args <out_dir> --list[=<exact_arg>] [--short] [--overrides-only] [--json] | 
 |       Lists all build arguments available in the current configuration, or, if | 
 |       an exact_arg is specified for the list flag, just that one build | 
 |       argument. | 
 |  | 
 |       The output will list the declaration location, current value for the | 
 |       build, default value (if different than the current value), and comment | 
 |       preceding the declaration. | 
 |  | 
 |       If --short is specified, only the names and current values will be | 
 |       printed. | 
 |  | 
 |       If --overrides-only is specified, only the names and current values of | 
 |       arguments that have been overridden (i.e. non-default arguments) will | 
 |       be printed. Overrides come from the <out_dir>/args.gn file and //.gn | 
 |  | 
 |       If --json is specified, the output will be emitted in json format. | 
 |       JSON schema for output: | 
 |       [ | 
 |         { | 
 |           "name": variable_name, | 
 |           "current": { | 
 |             "value": overridden_value, | 
 |             "file": file_name, | 
 |             "line": line_no | 
 |           }, | 
 |           "default": { | 
 |             "value": default_value, | 
 |             "file": file_name, | 
 |             "line": line_no | 
 |           }, | 
 |           "comment": comment_string | 
 |         }, | 
 |         ... | 
 |       ] | 
 |  | 
 | Examples | 
 |  | 
 |   gn args out/Debug | 
 |     Opens an editor with the args for out/Debug. | 
 |  | 
 |   gn args out/Debug --list --short | 
 |     Prints all arguments with their default values for the out/Debug | 
 |     build. | 
 |  | 
 |   gn args out/Debug --list --short --overrides-only | 
 |     Prints overridden arguments for the out/Debug build. | 
 |  | 
 |   gn args out/Debug --list=target_cpu | 
 |     Prints information about the "target_cpu" argument for the " | 
 |    "out/Debug | 
 |     build. | 
 |  | 
 |   gn args --list --args="os=\"android\" enable_doom_melon=true" | 
 |     Prints all arguments with the default values for a build with the | 
 |     given arguments set (which may affect the values of other | 
 |     arguments). | 
 | )"; | 
 |  | 
 | int RunArgs(const std::vector<std::string>& args) { | 
 |   if (args.size() != 1) { | 
 |     Err(Location(), "Exactly one build dir needed.", | 
 |         "Usage: \"gn args <out_dir>\"\n" | 
 |         "Or see \"gn help args\" for more variants.") | 
 |         .PrintToStdout(); | 
 |     return 1; | 
 |   } | 
 |  | 
 |   if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchList)) | 
 |     return ListArgs(args[0]); | 
 |   return EditArgsFile(args[0]); | 
 | } | 
 |  | 
 | }  // namespace commands |