#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 "gn/commands.h"
#include "gn/filesystem_utils.h"
#include "gn/input_file.h"
#include "gn/parse_tree.h"
#include "gn/setup.h"
#include "gn/standard_out.h"
#include "gn/tokenizer.h"
#include "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(std::string_view line) {
size_t i = 0;
while (i < line.size() && base::IsAsciiWhitespace(line[i]))
i++;
return i < line.size() && line[i] == '#';
}
size_t BackUpToLineBegin(const std::string& data, size_t offset) {
if (offset == 0 || Tokenizer::IsNewline(data, offset))
return offset;
size_t cur = offset;
do {
cur--;
if (Tokenizer::IsNewline(data, cur))
return cur + 1;
} while (cur > 0);
return 0;
}
std::string StripHashFromLine(std::string_view line, bool pad) {
std::string line_stripped(line.substr(line.find('#') + 1));
if (pad)
return " " + line_stripped;
if (!line_stripped.empty() && line_stripped[0] == ' ')
return line_stripped.substr(1);
return line_stripped;
}
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;
size_t previous_line_offset = BackUpToLineBegin(data, line_off);
std::string_view 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;
}
}
void PrintDefaultValueInfo(std::string_view 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 " + std::string(name) +
"`.)\n");
}
}
void PrintArgHelp(std::string_view name, const Args::ValueWithOverride& val) {
OutputString(std::string(name), DECORATION_YELLOW);
OutputString("\n");
if (val.has_override) {
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 {
OutputString(" Current value (from the default) = ");
PrintDefaultValueInfo(name, val.default_value);
}
}
void BuildArgJson(base::Value& dict,
std::string_view name,
const Args::ValueWithOverride& arg,
bool short_only) {
assert(dict.is_dict());
dict.SetKey("name", base::Value(name));
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,
false);
override_dict.SetKey("file", base::Value(location));
override_dict.SetKey("line", base::Value(line_no));
}
dict.SetKey("current", std::move(override_dict));
}
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,
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) {
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()->GetSwitchValueString(kSwitchList);
if (!list_value.empty()) {
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;
}
Args::ValueWithOverrideMap::value_type preserved = *found;
args.clear();
args.insert(preserved);
}
const bool overrides_only =
base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchOverridesOnly);
const bool short_only =
base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchShort);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchJson)) {
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) {
for (const auto& arg : args) {
if (overrides_only && !arg.second.has_override)
continue;
OutputString(std::string(arg.first));
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;
}
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) {
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
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(" \"");
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) {
{
Setup setup;
setup.set_fill_arguments(false);
if (!setup.DoSetup(build_dir, true))
return 1;
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;
}
base::ReadFileToString(full_path, &argfile_default_contents);
}
#if defined(OS_WIN)
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;
}
OutputString("Generating files...\n");
std::vector<std::string> gen_commands;
gen_commands.push_back(build_dir);
return RunGen(gen_commands);
}
}
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]);
}
}