• 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(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