| // 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 <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 |