1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
10 #include "base/command_line.h"
11 #include "base/environment.h"
12 #include "base/files/file_util.h"
13 #include "base/process/launch.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "tools/gn/commands.h"
17 #include "tools/gn/filesystem_utils.h"
18 #include "tools/gn/input_file.h"
19 #include "tools/gn/parse_tree.h"
20 #include "tools/gn/setup.h"
21 #include "tools/gn/standard_out.h"
22 #include "tools/gn/tokenizer.h"
23 #include "tools/gn/trace.h"
34 const char kSwitchList
[] = "list";
35 const char kSwitchShort
[] = "short";
37 bool DoesLineBeginWithComment(const base::StringPiece
& line
) {
40 while (i
< line
.size() && IsAsciiWhitespace(line
[i
]))
43 return i
< line
.size() && line
[i
] == '#';
46 // Returns the offset of the beginning of the line identified by |offset|.
47 size_t BackUpToLineBegin(const std::string
& data
, size_t offset
) {
48 // Degenerate case of an empty line. Below we'll try to return the
49 // character after the newline, but that will be incorrect in this case.
50 if (offset
== 0 || Tokenizer::IsNewline(data
, offset
))
56 if (Tokenizer::IsNewline(data
, cur
))
57 return cur
+ 1; // Want the first character *after* the newline.
62 // Assumes DoesLineBeginWithComment(), this strips the # character from the
63 // beginning and normalizes preceeding whitespace.
64 std::string
StripHashFromLine(const base::StringPiece
& line
) {
65 // Replace the # sign and everything before it with 3 spaces, so that a
66 // normal comment that has a space after the # will be indented 4 spaces
67 // (which makes our formatting come out nicely). If the comment is indented
68 // from there, we want to preserve that indenting.
69 return " " + line
.substr(line
.find('#') + 1).as_string();
72 // Tries to find the comment before the setting of the given value.
73 void GetContextForValue(const Value
& value
,
74 std::string
* location_str
,
75 std::string
* comment
) {
76 Location location
= value
.origin()->GetRange().begin();
77 const InputFile
* file
= location
.file();
81 *location_str
= file
->name().value() + ":" +
82 base::IntToString(location
.line_number());
84 const std::string
& data
= file
->contents();
86 Tokenizer::ByteOffsetOfNthLine(data
, location
.line_number());
88 while (line_off
> 1) {
89 line_off
-= 2; // Back up to end of previous line.
90 size_t previous_line_offset
= BackUpToLineBegin(data
, line_off
);
92 base::StringPiece
line(&data
[previous_line_offset
],
93 line_off
- previous_line_offset
+ 1);
94 if (!DoesLineBeginWithComment(line
))
97 comment
->insert(0, StripHashFromLine(line
) + "\n");
98 line_off
= previous_line_offset
;
102 void PrintArgHelp(const base::StringPiece
& name
, const Value
& value
) {
103 OutputString(name
.as_string(), DECORATION_YELLOW
);
104 OutputString(" Default = " + value
.ToString(true) + "\n");
106 if (value
.origin()) {
107 std::string location
, comment
;
108 GetContextForValue(value
, &location
, &comment
);
109 OutputString(" " + location
+ "\n" + comment
);
111 OutputString(" (Internally set)\n");
115 int ListArgs(const std::string
& build_dir
) {
116 Setup
* setup
= new Setup
;
117 setup
->set_check_for_bad_items(false);
118 if (!setup
->DoSetup(build_dir
, false) || !setup
->Run())
121 Scope::KeyValueMap build_args
;
122 setup
->build_settings().build_args().MergeDeclaredArguments(&build_args
);
124 // Find all of the arguments we care about. Use a regular map so they're
125 // sorted nicely when we write them out.
126 std::map
<base::StringPiece
, Value
> sorted_args
;
127 std::string list_value
=
128 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchList
);
129 if (list_value
.empty()) {
131 for (const auto& arg
: build_args
)
132 sorted_args
.insert(arg
);
134 // List just the one specified as the parameter to --list.
135 Scope::KeyValueMap::const_iterator found_arg
= build_args
.find(list_value
);
136 if (found_arg
== build_args
.end()) {
137 Err(Location(), "Unknown build argument.",
138 "You asked for \"" + list_value
+ "\" which I didn't find in any "
139 "build file\nassociated with this build.").PrintToStdout();
142 sorted_args
.insert(*found_arg
);
145 if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchShort
)) {
146 // Short key=value output.
147 for (const auto& arg
: sorted_args
) {
148 OutputString(arg
.first
.as_string());
150 OutputString(arg
.second
.ToString(true));
157 for (const auto& arg
: sorted_args
) {
158 PrintArgHelp(arg
.first
, arg
.second
);
167 bool RunEditor(const base::FilePath
& file_to_edit
) {
168 SHELLEXECUTEINFO info
;
169 memset(&info
, 0, sizeof(info
));
170 info
.cbSize
= sizeof(info
);
171 info
.fMask
= SEE_MASK_NOCLOSEPROCESS
| SEE_MASK_CLASSNAME
;
172 info
.lpFile
= file_to_edit
.value().c_str();
173 info
.nShow
= SW_SHOW
;
174 info
.lpClass
= L
".txt";
175 if (!::ShellExecuteEx(&info
)) {
176 Err(Location(), "Couldn't run editor.",
177 "Just edit \"" + FilePathToUTF8(file_to_edit
) +
178 "\" manually instead.").PrintToStdout();
182 if (!info
.hProcess
) {
183 // Windows re-used an existing process.
184 OutputString("\"" + FilePathToUTF8(file_to_edit
) +
185 "\" opened in editor, save it and press <Enter> when done.\n");
188 OutputString("Waiting for editor on \"" + FilePathToUTF8(file_to_edit
) +
190 ::WaitForSingleObject(info
.hProcess
, INFINITE
);
191 ::CloseHandle(info
.hProcess
);
198 bool RunEditor(const base::FilePath
& file_to_edit
) {
199 const char* editor_ptr
= getenv("VISUAL");
201 editor_ptr
= getenv("GN_EDITOR");
203 editor_ptr
= getenv("EDITOR");
207 std::string
cmd(editor_ptr
);
210 // Its impossible to do this properly since we don't know the user's shell,
211 // but quoting and escaping internal quotes should handle 99.999% of all
213 std::string escaped_name
= file_to_edit
.value();
214 ReplaceSubstringsAfterOffset(&escaped_name
, 0, "\"", "\\\"");
215 cmd
.append(escaped_name
);
218 OutputString("Waiting for editor on \"" + file_to_edit
.value() +
220 return system(cmd
.c_str()) == 0;
225 int EditArgsFile(const std::string
& build_dir
) {
227 // Scope the setup. We only use it for some basic state. We'll do the
228 // "real" build below in the gen command.
230 setup
.set_check_for_bad_items(false);
231 // Don't fill build arguments. We're about to edit the file which supplies
232 // these in the first place.
233 setup
.set_fill_arguments(false);
234 if (!setup
.DoSetup(build_dir
, true))
237 // Ensure the file exists. Need to normalize path separators since on
238 // Windows they can come out as forward slashes here, and that confuses some
240 base::FilePath arg_file
=
241 setup
.build_settings().GetFullPath(setup
.GetBuildArgFile())
242 .NormalizePathSeparators();
243 if (!base::PathExists(arg_file
)) {
244 std::string argfile_default_contents
=
245 "# Build arguments go here. Examples:\n"
246 "# enable_doom_melon = true\n"
247 "# crazy_something = \"absolutely\"\n";
249 // Use Windows lineendings for this file since it will often open in
250 // Notepad which can't handle Unix ones.
251 ReplaceSubstringsAfterOffset(&argfile_default_contents
, 0, "\n", "\r\n");
253 base::CreateDirectory(arg_file
.DirName());
254 base::WriteFile(arg_file
, argfile_default_contents
.c_str(),
255 static_cast<int>(argfile_default_contents
.size()));
258 ScopedTrace
editor_trace(TraceItem::TRACE_SETUP
, "Waiting for editor");
259 if (!RunEditor(arg_file
))
263 // Now do a normal "gen" command.
264 OutputString("Generating files...\n");
265 std::vector
<std::string
> gen_commands
;
266 gen_commands
.push_back(build_dir
);
267 return RunGen(gen_commands
);
272 extern const char kArgs
[] = "args";
273 extern const char kArgs_HelpShort
[] =
274 "args: Display or configure arguments declared by the build.";
275 extern const char kArgs_Help
[] =
276 "gn args [arg name]\n"
278 " See also \"gn help buildargs\" for a more high-level overview of how\n"
279 " build arguments work.\n"
282 " gn args <dir_name>\n"
283 " Open the arguments for the given build directory in an editor\n"
284 " (as specified by the EDITOR environment variable). If the given\n"
285 " build directory doesn't exist, it will be created and an empty\n"
286 " args file will be opened in the editor. You would type something\n"
287 " like this into that file:\n"
288 " enable_doom_melon=false\n"
291 " Note: you can edit the build args manually by editing the file\n"
292 " \"args.gn\" in the build directory and then running\n"
293 " \"gn gen <build_dir>\".\n"
295 " gn args <dir_name> --list[=<exact_arg>] [--short]\n"
296 " Lists all build arguments available in the current configuration,\n"
297 " or, if an exact_arg is specified for the list flag, just that one\n"
300 " The output will list the declaration location, default value, and\n"
301 " comment preceeding the declaration. If --short is specified,\n"
302 " only the names and values will be printed.\n"
304 " If the dir_name is specified, the build configuration will be\n"
305 " taken from that build directory. The reason this is needed is that\n"
306 " the definition of some arguments is dependent on the build\n"
307 " configuration, so setting some values might add, remove, or change\n"
308 " the default values for other arguments. Specifying your exact\n"
309 " configuration allows the proper arguments to be displayed.\n"
311 " Instead of specifying the dir_name, you can also use the\n"
312 " command-line flag to specify the build configuration:\n"
313 " --args=<exact list of args to use>\n"
316 " gn args out/Debug\n"
317 " Opens an editor with the args for out/Debug.\n"
319 " gn args out/Debug --list --short\n"
320 " Prints all arguments with their default values for the out/Debug\n"
323 " gn args out/Debug --list=cpu_arch\n"
324 " Prints information about the \"cpu_arch\" argument for the out/Debug\n"
327 " gn args --list --args=\"os=\\\"android\\\" enable_doom_melon=true\"\n"
328 " Prints all arguments with the default values for a build with the\n"
329 " given arguments set (which may affect the values of other\n"
332 int RunArgs(const std::vector
<std::string
>& args
) {
333 if (args
.size() != 1) {
334 Err(Location(), "Exactly one build dir needed.",
335 "Usage: \"gn args <build_dir>\"\n"
336 "Or see \"gn help args\" for more variants.").PrintToStdout();
340 if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchList
))
341 return ListArgs(args
[0]);
342 return EditArgsFile(args
[0]);
345 } // namespace commands