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(std::string_view line)43 bool DoesLineBeginWithComment(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(std::string_view line,bool pad)70 std::string StripHashFromLine(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(std::string_view name,const Args::ValueWithOverride & val)140 void PrintArgHelp(std::string_view name, const Args::ValueWithOverride& val) {
141 OutputString(std::string(name), DECORATION_YELLOW);
142 OutputString("\n");
143
144 if (val.has_override) {
145 // Override present, print both it and the default.
146 OutputString(" Current value = " + val.override_value.ToString(true) +
147 "\n");
148 if (val.override_value.origin()) {
149 int line_no;
150 std::string location, comment;
151 GetContextForValue(val.override_value, &location, &line_no, &comment);
152 OutputString(" From " + location + ":" + base::IntToString(line_no) +
153 "\n");
154 }
155 OutputString(" Overridden from the default = ");
156 PrintDefaultValueInfo(name, val.default_value);
157 } else {
158 // No override.
159 OutputString(" Current value (from the default) = ");
160 PrintDefaultValueInfo(name, val.default_value);
161 }
162 }
163
BuildArgJson(base::Value & dict,std::string_view name,const Args::ValueWithOverride & arg,bool short_only)164 void BuildArgJson(base::Value& dict,
165 std::string_view name,
166 const Args::ValueWithOverride& arg,
167 bool short_only) {
168 assert(dict.is_dict());
169
170 // Fetch argument name.
171 dict.SetKey("name", base::Value(name));
172
173 // Fetch overridden value information (if present).
174 if (arg.has_override) {
175 base::DictionaryValue override_dict;
176 override_dict.SetKey("value",
177 base::Value(arg.override_value.ToString(true)));
178 if (arg.override_value.origin() && !short_only) {
179 int line_no;
180 std::string location, comment;
181 GetContextForValue(arg.override_value, &location, &line_no, &comment,
182 /*pad_comment=*/false);
183 override_dict.SetKey("file", base::Value(location));
184 override_dict.SetKey("line", base::Value(line_no));
185 }
186 dict.SetKey("current", std::move(override_dict));
187 }
188
189 // Fetch default value information, and comment (if present).
190 base::DictionaryValue default_dict;
191 std::string comment;
192 default_dict.SetKey("value", base::Value(arg.default_value.ToString(true)));
193 if (arg.default_value.origin() && !short_only) {
194 int line_no;
195 std::string location;
196 GetContextForValue(arg.default_value, &location, &line_no, &comment,
197 /*pad_comment=*/false);
198 default_dict.SetKey("file", base::Value(location));
199 default_dict.SetKey("line", base::Value(line_no));
200 }
201 dict.SetKey("default", std::move(default_dict));
202 if (!comment.empty() && !short_only)
203 dict.SetKey("comment", base::Value(comment));
204 }
205
ListArgs(const std::string & build_dir)206 int ListArgs(const std::string& build_dir) {
207 // Deliberately leaked to avoid expensive process teardown.
208 Setup* setup = new Setup;
209 if (!setup->DoSetup(build_dir, false) || !setup->Run())
210 return 1;
211
212 Args::ValueWithOverrideMap args =
213 setup->build_settings().build_args().GetAllArguments();
214 std::string list_value =
215 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchList);
216 if (!list_value.empty()) {
217 // List just the one specified as the parameter to --list.
218 auto found = args.find(list_value);
219 if (found == args.end()) {
220 Err(Location(), "Unknown build argument.",
221 "You asked for \"" + list_value +
222 "\" which I didn't find in any "
223 "build file\nassociated with this build.")
224 .PrintToStdout();
225 return 1;
226 }
227
228 // Delete everything from the map except the one requested.
229 Args::ValueWithOverrideMap::value_type preserved = *found;
230 args.clear();
231 args.insert(preserved);
232 }
233
234 // Cache this to avoid looking it up for each |arg| in the loops below.
235 const bool overrides_only =
236 base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchOverridesOnly);
237 const bool short_only =
238 base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchShort);
239
240 if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchJson)) {
241 // Convert all args to JSON, serialize and print them
242 auto list = std::make_unique<base::ListValue>();
243 for (const auto& arg : args) {
244 if (overrides_only && !arg.second.has_override)
245 continue;
246 list->GetList().emplace_back(base::DictionaryValue());
247 BuildArgJson(list->GetList().back(), arg.first, arg.second, short_only);
248 }
249 std::string s;
250 base::JSONWriter::WriteWithOptions(
251 *list.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s);
252 OutputString(s);
253 return 0;
254 }
255
256 if (short_only) {
257 // Short <key>=<current_value> output.
258 for (const auto& arg : args) {
259 if (overrides_only && !arg.second.has_override)
260 continue;
261 OutputString(std::string(arg.first));
262 OutputString(" = ");
263 if (arg.second.has_override)
264 OutputString(arg.second.override_value.ToString(true));
265 else
266 OutputString(arg.second.default_value.ToString(true));
267 OutputString("\n");
268 }
269 return 0;
270 }
271
272 // Long output.
273 for (const auto& arg : args) {
274 if (overrides_only && !arg.second.has_override)
275 continue;
276 PrintArgHelp(arg.first, arg.second);
277 OutputString("\n");
278 }
279
280 return 0;
281 }
282
283 #if defined(OS_WIN)
284
RunEditor(const base::FilePath & file_to_edit)285 bool RunEditor(const base::FilePath& file_to_edit) {
286 SHELLEXECUTEINFO info;
287 memset(&info, 0, sizeof(info));
288 info.cbSize = sizeof(info);
289 info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_CLASSNAME;
290 info.lpFile = reinterpret_cast<LPCWSTR>(file_to_edit.value().c_str());
291 info.nShow = SW_SHOW;
292 info.lpClass = L".txt";
293 if (!::ShellExecuteEx(&info)) {
294 Err(Location(), "Couldn't run editor.",
295 "Just edit \"" + FilePathToUTF8(file_to_edit) + "\" manually instead.")
296 .PrintToStdout();
297 return false;
298 }
299
300 if (!info.hProcess) {
301 // Windows re-used an existing process.
302 OutputString("\"" + FilePathToUTF8(file_to_edit) +
303 "\" opened in editor, save it and press <Enter> when done.\n");
304 getchar();
305 } else {
306 OutputString("Waiting for editor on \"" + FilePathToUTF8(file_to_edit) +
307 "\"...\n");
308 ::WaitForSingleObject(info.hProcess, INFINITE);
309 ::CloseHandle(info.hProcess);
310 }
311 return true;
312 }
313
314 #else // POSIX
315
RunEditor(const base::FilePath & file_to_edit)316 bool RunEditor(const base::FilePath& file_to_edit) {
317 const char* editor_ptr = getenv("GN_EDITOR");
318 if (!editor_ptr)
319 editor_ptr = getenv("VISUAL");
320 if (!editor_ptr)
321 editor_ptr = getenv("EDITOR");
322 if (!editor_ptr)
323 editor_ptr = "vi";
324
325 std::string cmd(editor_ptr);
326 cmd.append(" \"");
327
328 // Its impossible to do this properly since we don't know the user's shell,
329 // but quoting and escaping internal quotes should handle 99.999% of all
330 // cases.
331 std::string escaped_name = file_to_edit.value();
332 base::ReplaceSubstringsAfterOffset(&escaped_name, 0, "\"", "\\\"");
333 cmd.append(escaped_name);
334 cmd.push_back('"');
335
336 OutputString("Waiting for editor on \"" + file_to_edit.value() + "\"...\n");
337 return system(cmd.c_str()) == 0;
338 }
339
340 #endif
341
EditArgsFile(const std::string & build_dir)342 int EditArgsFile(const std::string& build_dir) {
343 {
344 // Scope the setup. We only use it for some basic state. We'll do the
345 // "real" build below in the gen command.
346 Setup setup;
347 // Don't fill build arguments. We're about to edit the file which supplies
348 // these in the first place.
349 setup.set_fill_arguments(false);
350 if (!setup.DoSetup(build_dir, true))
351 return 1;
352
353 // Ensure the file exists. Need to normalize path separators since on
354 // Windows they can come out as forward slashes here, and that confuses some
355 // of the commands.
356 BuildSettings build_settings = setup.build_settings();
357 base::FilePath arg_file =
358 build_settings.GetFullPath(setup.GetBuildArgFile())
359 .NormalizePathSeparators();
360 if (!base::PathExists(arg_file)) {
361 std::string argfile_default_contents =
362 "# Build arguments go here.\n"
363 "# See \"gn args <out_dir> --list\" for available build "
364 "arguments.\n";
365
366 SourceFile template_path = build_settings.arg_file_template_path();
367 if (!template_path.is_null()) {
368 base::FilePath full_path =
369 build_settings.GetFullPath(template_path).NormalizePathSeparators();
370 if (!base::PathExists(full_path)) {
371 Err err =
372 Err(Location(), std::string("Can't load arg_file_template:\n ") +
373 template_path.value());
374 err.PrintToStdout();
375 return 1;
376 }
377
378 // Ignore the return code; if the read fails (unlikely), we'll just
379 // use the default contents.
380 base::ReadFileToString(full_path, &argfile_default_contents);
381 }
382 #if defined(OS_WIN)
383 // Use Windows lineendings for this file since it will often open in
384 // Notepad which can't handle Unix ones.
385 base::ReplaceSubstringsAfterOffset(&argfile_default_contents, 0, "\n",
386 "\r\n");
387 #endif
388 base::CreateDirectory(arg_file.DirName());
389 base::WriteFile(arg_file, argfile_default_contents.c_str(),
390 static_cast<int>(argfile_default_contents.size()));
391 }
392
393 ScopedTrace editor_trace(TraceItem::TRACE_SETUP, "Waiting for editor");
394 if (!RunEditor(arg_file))
395 return 1;
396 }
397
398 // Now do a normal "gen" command.
399 OutputString("Generating files...\n");
400 std::vector<std::string> gen_commands;
401 gen_commands.push_back(build_dir);
402 return RunGen(gen_commands);
403 }
404
405 } // namespace
406
407 const char kArgs[] = "args";
408 const char kArgs_HelpShort[] =
409 "args: Display or configure arguments declared by the build.";
410 const char kArgs_Help[] =
411 R"(gn args: (command-line tool)
412
413 Display or configure arguments declared by the build.
414
415 gn args <out_dir> [--list] [--short] [--args] [--overrides-only]
416
417 See also "gn help buildargs" for a more high-level overview of how
418 build arguments work.
419
420 Usage
421
422 gn args <out_dir>
423 Open the arguments for the given build directory in an editor. If the
424 given build directory doesn't exist, it will be created and an empty args
425 file will be opened in the editor. You would type something like this
426 into that file:
427 enable_doom_melon=false
428 os="android"
429
430 To find your editor on Posix, GN will search the environment variables in
431 order: GN_EDITOR, VISUAL, and EDITOR. On Windows GN will open the command
432 associated with .txt files.
433
434 Note: you can edit the build args manually by editing the file "args.gn"
435 in the build directory and then running "gn gen <out_dir>".
436
437 gn args <out_dir> --list[=<exact_arg>] [--short] [--overrides-only] [--json]
438 Lists all build arguments available in the current configuration, or, if
439 an exact_arg is specified for the list flag, just that one build
440 argument.
441
442 The output will list the declaration location, current value for the
443 build, default value (if different than the current value), and comment
444 preceding the declaration.
445
446 If --short is specified, only the names and current values will be
447 printed.
448
449 If --overrides-only is specified, only the names and current values of
450 arguments that have been overridden (i.e. non-default arguments) will
451 be printed. Overrides come from the <out_dir>/args.gn file and //.gn
452
453 If --json is specified, the output will be emitted in json format.
454 JSON schema for output:
455 [
456 {
457 "name": variable_name,
458 "current": {
459 "value": overridden_value,
460 "file": file_name,
461 "line": line_no
462 },
463 "default": {
464 "value": default_value,
465 "file": file_name,
466 "line": line_no
467 },
468 "comment": comment_string
469 },
470 ...
471 ]
472
473 Examples
474
475 gn args out/Debug
476 Opens an editor with the args for out/Debug.
477
478 gn args out/Debug --list --short
479 Prints all arguments with their default values for the out/Debug
480 build.
481
482 gn args out/Debug --list --short --overrides-only
483 Prints overridden arguments for the out/Debug build.
484
485 gn args out/Debug --list=target_cpu
486 Prints information about the "target_cpu" argument for the "
487 "out/Debug
488 build.
489
490 gn args --list --args="os=\"android\" enable_doom_melon=true"
491 Prints all arguments with the default values for a build with the
492 given arguments set (which may affect the values of other
493 arguments).
494 )";
495
RunArgs(const std::vector<std::string> & args)496 int RunArgs(const std::vector<std::string>& args) {
497 if (args.size() != 1) {
498 Err(Location(), "Exactly one build dir needed.",
499 "Usage: \"gn args <out_dir>\"\n"
500 "Or see \"gn help args\" for more variants.")
501 .PrintToStdout();
502 return 1;
503 }
504
505 if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchList))
506 return ListArgs(args[0]);
507 return EditArgsFile(args[0]);
508 }
509
510 } // namespace commands
511