| // 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 = reinterpret_cast<LPCWSTR>(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 |