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.
4
5 #include <stddef.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9
10 #include <map>
11
12 #include "base/command_line.h"
13 #include "base/environment.h"
14 #include "base/files/file_util.h"
15 #include "base/json/json_writer.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "gn/commands.h"
19 #include "gn/filesystem_utils.h"
20 #include "gn/input_file.h"
21 #include "gn/parse_tree.h"
22 #include "gn/setup.h"
23 #include "gn/standard_out.h"
24 #include "gn/tokenizer.h"
25 #include "gn/trace.h"
26 #include "util/build_config.h"
27
28 #if defined(OS_WIN)
29 #include <windows.h>
30
31 #include <shellapi.h>
32 #endif
33
34 namespace commands {
35
36 namespace {
37
38 const char kSwitchList[] = "list";
39 const char kSwitchShort[] = "short";
40 const char kSwitchOverridesOnly[] = "overrides-only";
41 const char kSwitchJson[] = "json";
42
DoesLineBeginWithComment(const std::string_view & line)43 bool DoesLineBeginWithComment(const std::string_view& line) {
44 // Skip whitespace.
45 size_t i = 0;
46 while (i < line.size() && base::IsAsciiWhitespace(line[i]))
47 i++;
48
49 return i < line.size() && line[i] == '#';
50 }
51
52 // Returns the offset of the beginning of the line identified by |offset|.
BackUpToLineBegin(const std::string & data,size_t offset)53 size_t BackUpToLineBegin(const std::string& data, size_t offset) {
54 // Degenerate case of an empty line. Below we'll try to return the
55 // character after the newline, but that will be incorrect in this case.
56 if (offset == 0 || Tokenizer::IsNewline(data, offset))
57 return offset;
58
59 size_t cur = offset;
60 do {
61 cur--;
62 if (Tokenizer::IsNewline(data, cur))
63 return cur + 1; // Want the first character *after* the newline.
64 } while (cur > 0);
65 return 0;
66 }
67
68 // Assumes DoesLineBeginWithComment(), this strips the # character from the
69 // beginning and normalizes preceding whitespace.
StripHashFromLine(const std::string_view & line,bool pad)70 std::string StripHashFromLine(const std::string_view& line, bool pad) {
71 // Replace the # sign and everything before it with 3 spaces, so that a
72 // normal comment that has a space after the # will be indented 4 spaces
73 // (which makes our formatting come out nicely). If the comment is indented
74 // from there, we want to preserve that indenting.
75 std::string line_stripped(line.substr(line.find('#') + 1));
76 if (pad)
77 return " " + line_stripped;
78
79 // If not padding, strip the leading space if present.
80 if (!line_stripped.empty() && line_stripped[0] == ' ')
81 return line_stripped.substr(1);
82 return line_stripped;
83 }
84
85 // Tries to find the comment before the setting of the given value.
GetContextForValue(const Value & value,std::string * location_str,int * line_no,std::string * comment,bool pad_comment=true)86 void GetContextForValue(const Value& value,
87 std::string* location_str,
88 int* line_no,
89 std::string* comment,
90 bool pad_comment = true) {
91 Location location = value.origin()->GetRange().begin();
92 const InputFile* file = location.file();
93 if (!file)
94 return;
95
96 *location_str = file->name().value();
97 *line_no = location.line_number();
98
99 const std::string& data = file->contents();
100 size_t line_off =
101 Tokenizer::ByteOffsetOfNthLine(data, location.line_number());
102
103 while (line_off > 1) {
104 line_off -= 2; // Back up to end of previous line.
105 size_t previous_line_offset = BackUpToLineBegin(data, line_off);
106
107 std::string_view line(&data[previous_line_offset],
108 line_off - previous_line_offset + 1);
109 if (!DoesLineBeginWithComment(line))
110 break;
111
112 comment->insert(0, StripHashFromLine(line, pad_comment) + "\n");
113 line_off = previous_line_offset;
114 }
115 }
116
117 // Prints the value and origin for a default value. Default values always list
118 // an origin and if there is no origin, print a message about it being
119 // internally set. Overrides can't be internally set so the location handling
120 // is a bit different.
121 //
122 // The default value also contains the docstring.
PrintDefaultValueInfo(std::string_view name,const Value & value)123 void PrintDefaultValueInfo(std::string_view name, const Value& value) {
124 OutputString(value.ToString(true) + "\n");
125 if (value.origin()) {
126 int line_no;
127 std::string location, comment;
128 GetContextForValue(value, &location, &line_no, &comment);
129 OutputString(" From " + location + ":" + base::IntToString(line_no) +
130 "\n");
131 if (!comment.empty())
132 OutputString("\n" + comment);
133 } else {
134 OutputString(" (Internally set; try `gn help " + std::string(name) +
135 "`.)\n");
136 }
137 }
138
139 // Override value is null if there is no override.
PrintArgHelp(const std::string_view & name,const Args::ValueWithOverride & val)140 void PrintArgHelp(const std::string_view& name,
141 const Args::ValueWithOverride& val) {
142 OutputString(std::string(name), DECORATION_YELLOW);
143 OutputString("\n");
144
145 if (val.has_override) {
146 // Override present, print both it and the default.
147 OutputString(" Current value = " + val.override_value.ToString(true) +
148 "\n");
149 if (val.override_value.origin()) {
150 int line_no;
151 std::string location, comment;
152 GetContextForValue(val.override_value, &location, &line_no, &comment);
153 OutputString(" From " + location + ":" + base::IntToString(line_no) +
154 "\n");
155 }
156 OutputString(" Overridden from the default = ");
157 PrintDefaultValueInfo(name, val.default_value);
158 } else {
159 // No override.
160 OutputString(" Current value (from the default) = ");
161 PrintDefaultValueInfo(name, val.default_value);
162 }
163 }
164
BuildArgJson(base::Value & dict,const std::string_view & name,const Args::ValueWithOverride & arg,bool short_only)165 void BuildArgJson(base::Value& dict,
166 const std::string_view& name,
167 const Args::ValueWithOverride& arg,
168 bool short_only) {
169 assert(dict.is_dict());
170
171 // Fetch argument name.
172 dict.SetKey("name", base::Value(name));
173
174 // Fetch overridden value inforrmation (if present).
175 if (arg.has_override) {
176 base::DictionaryValue override_dict;
177 override_dict.SetKey("value",
178 base::Value(arg.override_value.ToString(true)));
179 if (arg.override_value.origin() && !short_only) {
180 int line_no;
181 std::string location, comment;
182 GetContextForValue(arg.override_value, &location, &line_no, &comment,
183 /*pad_comment=*/false);
184 override_dict.SetKey("file", base::Value(location));
185 override_dict.SetKey("line", base::Value(line_no));
186 }
187 dict.SetKey("current", std::move(override_dict));
188 }
189
190 // Fetch default value information, and comment (if present).
191 base::DictionaryValue default_dict;
192 std::string comment;
193 default_dict.SetKey("value", base::Value(arg.default_value.ToString(true)));
194 if (arg.default_value.origin() && !short_only) {
195 int line_no;
196 std::string location;
197 GetContextForValue(arg.default_value, &location, &line_no, &comment,
198 /*pad_comment=*/false);
199 default_dict.SetKey("file", base::Value(location));
200 default_dict.SetKey("line", base::Value(line_no));
201 }
202 dict.SetKey("default", std::move(default_dict));
203 if (!comment.empty() && !short_only)
204 dict.SetKey("comment", base::Value(comment));
205 }
206
ListArgs(const std::string & build_dir)207 int ListArgs(const std::string& build_dir) {
208 // Deliberately leaked to avoid expensive process teardown.
209 Setup* setup = new Setup;
210 if (!setup->DoSetup(build_dir, false) || !setup->Run())
211 return 1;
212
213 Args::ValueWithOverrideMap args =
214 setup->build_settings().build_args().GetAllArguments();
215 std::string list_value =
216 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchList);
217 if (!list_value.empty()) {
218 // List just the one specified as the parameter to --list.
219 auto found = args.find(list_value);
220 if (found == args.end()) {
221 Err(Location(), "Unknown build argument.",
222 "You asked for \"" + list_value +
223 "\" which I didn't find in any "
224 "build file\nassociated with this build.")
225 .PrintToStdout();
226 return 1;
227 }
228
229 // Delete everything from the map except the one requested.
230 Args::ValueWithOverrideMap::value_type preserved = *found;
231 args.clear();
232 args.insert(preserved);
233 }
234
235 // Cache this to avoid looking it up for each |arg| in the loops below.
236 const bool overrides_only =
237 base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchOverridesOnly);
238 const bool short_only =
239 base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchShort);
240
241 if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchJson)) {
242 // Convert all args to JSON, serialize and print them
243 auto list = std::make_unique<base::ListValue>();
244 for (const auto& arg : args) {
245 if (overrides_only && !arg.second.has_override)
246 continue;
247 list->GetList().emplace_back(base::DictionaryValue());
248 BuildArgJson(list->GetList().back(), arg.first, arg.second, short_only);
249 }
250 std::string s;
251 base::JSONWriter::WriteWithOptions(
252 *list.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s);
253 OutputString(s);
254 return 0;
255 }
256
257 if (short_only) {
258 // Short <key>=<current_value> output.
259 for (const auto& arg : args) {
260 if (overrides_only && !arg.second.has_override)
261 continue;
262 OutputString(std::string(arg.first));
263 OutputString(" = ");
264 if (arg.second.has_override)
265 OutputString(arg.second.override_value.ToString(true));
266 else
267 OutputString(arg.second.default_value.ToString(true));
268 OutputString("\n");
269 }
270 return 0;
271 }
272
273 // Long output.
274 for (const auto& arg : args) {
275 if (overrides_only && !arg.second.has_override)
276 continue;
277 PrintArgHelp(arg.first, arg.second);
278 OutputString("\n");
279 }
280
281 return 0;
282 }
283
284 #if defined(OS_WIN)
285
RunEditor(const base::FilePath & file_to_edit)286 bool RunEditor(const base::FilePath& file_to_edit) {
287 SHELLEXECUTEINFO info;
288 memset(&info, 0, sizeof(info));
289 info.cbSize = sizeof(info);
290 info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_CLASSNAME;
291 info.lpFile = reinterpret_cast<LPCWSTR>(file_to_edit.value().c_str());
292 info.nShow = SW_SHOW;
293 info.lpClass = L".txt";
294 if (!::ShellExecuteEx(&info)) {
295 Err(Location(), "Couldn't run editor.",
296 "Just edit \"" + FilePathToUTF8(file_to_edit) + "\" manually instead.")
297 .PrintToStdout();
298 return false;
299 }
300
301 if (!info.hProcess) {
302 // Windows re-used an existing process.
303 OutputString("\"" + FilePathToUTF8(file_to_edit) +
304 "\" opened in editor, save it and press <Enter> when done.\n");
305 getchar();
306 } else {
307 OutputString("Waiting for editor on \"" + FilePathToUTF8(file_to_edit) +
308 "\"...\n");
309 ::WaitForSingleObject(info.hProcess, INFINITE);
310 ::CloseHandle(info.hProcess);
311 }
312 return true;
313 }
314
315 #else // POSIX
316
RunEditor(const base::FilePath & file_to_edit)317 bool RunEditor(const base::FilePath& file_to_edit) {
318 const char* editor_ptr = getenv("GN_EDITOR");
319 if (!editor_ptr)
320 editor_ptr = getenv("VISUAL");
321 if (!editor_ptr)
322 editor_ptr = getenv("EDITOR");
323 if (!editor_ptr)
324 editor_ptr = "vi";
325
326 std::string cmd(editor_ptr);
327 cmd.append(" \"");
328
329 // Its impossible to do this properly since we don't know the user's shell,
330 // but quoting and escaping internal quotes should handle 99.999% of all
331 // cases.
332 std::string escaped_name = file_to_edit.value();
333 base::ReplaceSubstringsAfterOffset(&escaped_name, 0, "\"", "\\\"");
334 cmd.append(escaped_name);
335 cmd.push_back('"');
336
337 OutputString("Waiting for editor on \"" + file_to_edit.value() + "\"...\n");
338 return system(cmd.c_str()) == 0;
339 }
340
341 #endif
342
EditArgsFile(const std::string & build_dir)343 int EditArgsFile(const std::string& build_dir) {
344 {
345 // Scope the setup. We only use it for some basic state. We'll do the
346 // "real" build below in the gen command.
347 Setup setup;
348 // Don't fill build arguments. We're about to edit the file which supplies
349 // these in the first place.
350 setup.set_fill_arguments(false);
351 if (!setup.DoSetup(build_dir, true))
352 return 1;
353
354 // Ensure the file exists. Need to normalize path separators since on
355 // Windows they can come out as forward slashes here, and that confuses some
356 // of the commands.
357 BuildSettings build_settings = setup.build_settings();
358 base::FilePath arg_file =
359 build_settings.GetFullPath(setup.GetBuildArgFile())
360 .NormalizePathSeparators();
361 if (!base::PathExists(arg_file)) {
362 std::string argfile_default_contents =
363 "# Build arguments go here.\n"
364 "# See \"gn args <out_dir> --list\" for available build "
365 "arguments.\n";
366
367 SourceFile template_path = build_settings.arg_file_template_path();
368 if (!template_path.is_null()) {
369 base::FilePath full_path =
370 build_settings.GetFullPath(template_path).NormalizePathSeparators();
371 if (!base::PathExists(full_path)) {
372 Err err =
373 Err(Location(), std::string("Can't load arg_file_template:\n ") +
374 template_path.value());
375 err.PrintToStdout();
376 return 1;
377 }
378
379 // Ignore the return code; if the read fails (unlikely), we'll just
380 // use the default contents.
381 base::ReadFileToString(full_path, &argfile_default_contents);
382 }
383 #if defined(OS_WIN)
384 // Use Windows lineendings for this file since it will often open in
385 // Notepad which can't handle Unix ones.
386 base::ReplaceSubstringsAfterOffset(&argfile_default_contents, 0, "\n",
387 "\r\n");
388 #endif
389 base::CreateDirectory(arg_file.DirName());
390 base::WriteFile(arg_file, argfile_default_contents.c_str(),
391 static_cast<int>(argfile_default_contents.size()));
392 }
393
394 ScopedTrace editor_trace(TraceItem::TRACE_SETUP, "Waiting for editor");
395 if (!RunEditor(arg_file))
396 return 1;
397 }
398
399 // Now do a normal "gen" command.
400 OutputString("Generating files...\n");
401 std::vector<std::string> gen_commands;
402 gen_commands.push_back(build_dir);
403 return RunGen(gen_commands);
404 }
405
406 } // namespace
407
408 const char kArgs[] = "args";
409 const char kArgs_HelpShort[] =
410 "args: Display or configure arguments declared by the build.";
411 const char kArgs_Help[] =
412 R"(gn args: (command-line tool)
413
414 Display or configure arguments declared by the build.
415
416 gn args <out_dir> [--list] [--short] [--args] [--overrides-only]
417
418 See also "gn help buildargs" for a more high-level overview of how
419 build arguments work.
420
421 Usage
422
423 gn args <out_dir>
424 Open the arguments for the given build directory in an editor. If the
425 given build directory doesn't exist, it will be created and an empty args
426 file will be opened in the editor. You would type something like this
427 into that file:
428 enable_doom_melon=false
429 os="android"
430
431 To find your editor on Posix, GN will search the environment variables in
432 order: GN_EDITOR, VISUAL, and EDITOR. On Windows GN will open the command
433 associated with .txt files.
434
435 Note: you can edit the build args manually by editing the file "args.gn"
436 in the build directory and then running "gn gen <out_dir>".
437
438 gn args <out_dir> --list[=<exact_arg>] [--short] [--overrides-only] [--json]
439 Lists all build arguments available in the current configuration, or, if
440 an exact_arg is specified for the list flag, just that one build
441 argument.
442
443 The output will list the declaration location, current value for the
444 build, default value (if different than the current value), and comment
445 preceding the declaration.
446
447 If --short is specified, only the names and current values will be
448 printed.
449
450 If --overrides-only is specified, only the names and current values of
451 arguments that have been overridden (i.e. non-default arguments) will
452 be printed. Overrides come from the <out_dir>/args.gn file and //.gn
453
454 If --json is specified, the output will be emitted in json format.
455 JSON schema for output:
456 [
457 {
458 "name": variable_name,
459 "current": {
460 "value": overridden_value,
461 "file": file_name,
462 "line": line_no
463 },
464 "default": {
465 "value": default_value,
466 "file": file_name,
467 "line": line_no
468 },
469 "comment": comment_string
470 },
471 ...
472 ]
473
474 Examples
475
476 gn args out/Debug
477 Opens an editor with the args for out/Debug.
478
479 gn args out/Debug --list --short
480 Prints all arguments with their default values for the out/Debug
481 build.
482
483 gn args out/Debug --list --short --overrides-only
484 Prints overridden arguments for the out/Debug build.
485
486 gn args out/Debug --list=target_cpu
487 Prints information about the "target_cpu" argument for the "
488 "out/Debug
489 build.
490
491 gn args --list --args="os=\"android\" enable_doom_melon=true"
492 Prints all arguments with the default values for a build with the
493 given arguments set (which may affect the values of other
494 arguments).
495 )";
496
RunArgs(const std::vector<std::string> & args)497 int RunArgs(const std::vector<std::string>& args) {
498 if (args.size() != 1) {
499 Err(Location(), "Exactly one build dir needed.",
500 "Usage: \"gn args <out_dir>\"\n"
501 "Or see \"gn help args\" for more variants.")
502 .PrintToStdout();
503 return 1;
504 }
505
506 if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchList))
507 return ListArgs(args[0]);
508 return EditArgsFile(args[0]);
509 }
510
511 } // namespace commands
512