blob: 6ee05f7229499a5bc9b593a5577c69f813343d02 [file] [log] [blame]
// 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 <stdio.h>
#include <stdlib.h>
#include <map>
#include "base/command_line.h"
#include "base/environment.h"
#include "base/file_util.h"
#include "base/process/launch.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"
#if defined(OS_WIN)
#include <windows.h>
#include <shellapi.h>
#endif
namespace commands {
namespace {
const char kSwitchList[] = "list";
const char kSwitchShort[] = "short";
bool DoesLineBeginWithComment(const base::StringPiece& line) {
// Skip whitespace.
size_t i = 0;
while (i < line.size() && 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 preceeding whitespace.
std::string StripHashFromLine(const base::StringPiece& line) {
// 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.
return " " + line.substr(line.find('#') + 1).as_string();
}
// Tries to find the comment before the setting of the given value.
void GetContextForValue(const Value& value,
std::string* location_str,
std::string* comment) {
Location location = value.origin()->GetRange().begin();
const InputFile* file = location.file();
if (!file)
return;
*location_str = file->name().value() + ":" +
base::IntToString(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) + "\n");
line_off = previous_line_offset;
}
}
void PrintArgHelp(const base::StringPiece& name, const Value& value) {
OutputString(name.as_string(), DECORATION_YELLOW);
OutputString(" Default = " + value.ToString(true) + "\n");
if (value.origin()) {
std::string location, comment;
GetContextForValue(value, &location, &comment);
OutputString(" " + location + "\n" + comment);
} else {
OutputString(" (Internally set)\n");
}
}
int ListArgs(const std::string& build_dir) {
Setup* setup = new Setup;
setup->set_check_for_bad_items(false);
if (!setup->DoSetup(build_dir) || !setup->Run())
return 1;
Scope::KeyValueMap build_args;
setup->build_settings().build_args().MergeDeclaredArguments(&build_args);
// Find all of the arguments we care about. Use a regular map so they're
// sorted nicely when we write them out.
std::map<base::StringPiece, Value> sorted_args;
std::string list_value =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchList);
if (list_value.empty()) {
// List all values.
for (Scope::KeyValueMap::const_iterator i = build_args.begin();
i != build_args.end(); ++i)
sorted_args.insert(*i);
} else {
// List just the one specified as the parameter to --list.
Scope::KeyValueMap::const_iterator found_arg = build_args.find(list_value);
if (found_arg == build_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;
}
sorted_args.insert(*found_arg);
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchShort)) {
// Short key=value output.
for (std::map<base::StringPiece, Value>::iterator i = sorted_args.begin();
i != sorted_args.end(); ++i) {
OutputString(i->first.as_string());
OutputString(" = ");
OutputString(i->second.ToString(true));
OutputString("\n");
}
return 0;
}
// Long output.
for (std::map<base::StringPiece, Value>::iterator i = sorted_args.begin();
i != sorted_args.end(); ++i) {
PrintArgHelp(i->first, i->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) {
// Prefer $VISUAL, then $EDITOR, then vi.
const char* 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();
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;
setup.set_check_for_bad_items(false);
// 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))
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.
base::FilePath arg_file =
setup.build_settings().GetFullPath(setup.GetBuildArgFile())
.NormalizePathSeparators();
if (!base::PathExists(arg_file)) {
std::string argfile_default_contents =
"# Build arguments go here. Examples:\n"
"# enable_doom_melon = true\n"
"# crazy_something = \"absolutely\"\n";
#if defined(OS_WIN)
// Use Windows lineendings for this file since it will often open in
// Notepad which can't handle Unix ones.
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
extern const char kArgs[] = "args";
extern const char kArgs_HelpShort[] =
"args: Display or configure arguments declared by the build.";
extern const char kArgs_Help[] =
"gn args [arg name]\n"
"\n"
" See also \"gn help buildargs\" for a more high-level overview of how\n"
" build arguments work.\n"
"\n"
"Usage\n"
" gn args <dir_name>\n"
" Open the arguments for the given build directory in an editor\n"
" (as specified by the EDITOR environment variable). If the given\n"
" build directory doesn't exist, it will be created and an empty\n"
" args file will be opened in the editor. You would type something\n"
" like this into that file:\n"
" enable_doom_melon=false\n"
" os=\"android\"\n"
"\n"
" Note: you can edit the build args manually by editing the file\n"
" \"args.gn\" in the build directory and then running\n"
" \"gn gen <build_dir>\".\n"
"\n"
" gn args <dir_name> --list[=<exact_arg>] [--short]\n"
" Lists all build arguments available in the current configuration,\n"
" or, if an exact_arg is specified for the list flag, just that one\n"
" build argument.\n"
"\n"
" The output will list the declaration location, default value, and\n"
" comment preceeding the declaration. If --short is specified,\n"
" only the names and values will be printed.\n"
"\n"
" If the dir_name is specified, the build configuration will be\n"
" taken from that build directory. The reason this is needed is that\n"
" the definition of some arguments is dependent on the build\n"
" configuration, so setting some values might add, remove, or change\n"
" the default values for other arguments. Specifying your exact\n"
" configuration allows the proper arguments to be displayed.\n"
"\n"
" Instead of specifying the dir_name, you can also use the\n"
" command-line flag to specify the build configuration:\n"
" --args=<exact list of args to use>\n"
"\n"
"Examples\n"
" gn args out/Debug\n"
" Opens an editor with the args for out/Debug.\n"
"\n"
" gn args out/Debug --list --short\n"
" Prints all arguments with their default values for the out/Debug\n"
" build.\n"
"\n"
" gn args out/Debug --list=cpu_arch\n"
" Prints information about the \"cpu_arch\" argument for the out/Debug\n"
" build.\n"
"\n"
" gn args --list --args=\"os=\\\"android\\\" enable_doom_melon=true\"\n"
" Prints all arguments with the default values for a build with the\n"
" given arguments set (which may affect the values of other\n"
" arguments).\n";
int RunArgs(const std::vector<std::string>& args) {
if (args.size() != 1) {
Err(Location(), "Exactly one build dir needed.",
"Usage: \"gn args <build_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