• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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