Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / tools / gn / command_args.cc
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 <stdio.h>
6 #include <stdlib.h>
7
8 #include <map>
9
10 #include "base/command_line.h"
11 #include "base/environment.h"
12 #include "base/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"
24
25 #if defined(OS_WIN)
26 #include <windows.h>
27 #include <shellapi.h>
28 #endif
29
30 namespace commands {
31
32 namespace {
33
34 const char kSwitchList[] = "list";
35 const char kSwitchShort[] = "short";
36
37 bool DoesLineBeginWithComment(const base::StringPiece& line) {
38   // Skip whitespace.
39   size_t i = 0;
40   while (i < line.size() && IsAsciiWhitespace(line[i]))
41     i++;
42
43   return i < line.size() && line[i] == '#';
44 }
45
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))
51     return offset;
52
53   size_t cur = offset;
54   do {
55     cur --;
56     if (Tokenizer::IsNewline(data, cur))
57       return cur + 1;  // Want the first character *after* the newline.
58   } while (cur > 0);
59   return 0;
60 }
61
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();
70 }
71
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();
78   if (!file)
79     return;
80
81   *location_str = file->name().value() + ":" +
82       base::IntToString(location.line_number());
83
84   const std::string& data = file->contents();
85   size_t line_off =
86       Tokenizer::ByteOffsetOfNthLine(data, location.line_number());
87
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);
91
92     base::StringPiece line(&data[previous_line_offset],
93                            line_off - previous_line_offset + 1);
94     if (!DoesLineBeginWithComment(line))
95       break;
96
97     comment->insert(0, StripHashFromLine(line) + "\n");
98     line_off = previous_line_offset;
99   }
100 }
101
102 void PrintArgHelp(const base::StringPiece& name, const Value& value) {
103   OutputString(name.as_string(), DECORATION_YELLOW);
104   OutputString("  Default = " + value.ToString(true) + "\n");
105
106   if (value.origin()) {
107     std::string location, comment;
108     GetContextForValue(value, &location, &comment);
109     OutputString("    " + location + "\n" + comment);
110   } else {
111     OutputString("    (Internally set)\n");
112   }
113 }
114
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) || !setup->Run())
119     return 1;
120
121   Scope::KeyValueMap build_args;
122   setup->build_settings().build_args().MergeDeclaredArguments(&build_args);
123
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()) {
130     // List all values.
131     for (Scope::KeyValueMap::const_iterator i = build_args.begin();
132          i != build_args.end(); ++i)
133       sorted_args.insert(*i);
134   } else {
135     // List just the one specified as the parameter to --list.
136     Scope::KeyValueMap::const_iterator found_arg = build_args.find(list_value);
137     if (found_arg == build_args.end()) {
138       Err(Location(), "Unknown build argument.",
139           "You asked for \"" + list_value + "\" which I didn't find in any "
140           "build file\nassociated with this build.").PrintToStdout();
141       return 1;
142     }
143     sorted_args.insert(*found_arg);
144   }
145
146   if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchShort)) {
147     // Short key=value output.
148     for (std::map<base::StringPiece, Value>::iterator i = sorted_args.begin();
149          i != sorted_args.end(); ++i) {
150       OutputString(i->first.as_string());
151       OutputString(" = ");
152       OutputString(i->second.ToString(true));
153       OutputString("\n");
154     }
155     return 0;
156   }
157
158   // Long output.
159   for (std::map<base::StringPiece, Value>::iterator i = sorted_args.begin();
160        i != sorted_args.end(); ++i) {
161     PrintArgHelp(i->first, i->second);
162     OutputString("\n");
163   }
164
165   return 0;
166 }
167
168 #if defined(OS_WIN)
169
170 bool RunEditor(const base::FilePath& file_to_edit) {
171   SHELLEXECUTEINFO info;
172   memset(&info, 0, sizeof(info));
173   info.cbSize = sizeof(info);
174   info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_CLASSNAME;
175   info.lpFile = file_to_edit.value().c_str();
176   info.nShow = SW_SHOW;
177   info.lpClass = L".txt";
178   if (!::ShellExecuteEx(&info)) {
179     Err(Location(), "Couldn't run editor.",
180         "Just edit \"" + FilePathToUTF8(file_to_edit) +
181         "\" manually instead.").PrintToStdout();
182     return false;
183   }
184
185   if (!info.hProcess) {
186     // Windows re-used an existing process.
187     OutputString("\"" + FilePathToUTF8(file_to_edit) +
188                  "\" opened in editor, save it and press <Enter> when done.\n");
189     getchar();
190   } else {
191     OutputString("Waiting for editor on \"" + FilePathToUTF8(file_to_edit) +
192                  "\"...\n");
193     ::WaitForSingleObject(info.hProcess, INFINITE);
194     ::CloseHandle(info.hProcess);
195   }
196   return true;
197 }
198
199 #else  // POSIX
200
201 bool RunEditor(const base::FilePath& file_to_edit) {
202   // Prefer $VISUAL, then $EDITOR, then vi.
203   const char* editor_ptr = getenv("VISUAL");
204   if (!editor_ptr)
205     editor_ptr = getenv("EDITOR");
206   if (!editor_ptr)
207     editor_ptr = "vi";
208
209   std::string cmd(editor_ptr);
210   cmd.append(" \"");
211
212   // Its impossible to do this properly since we don't know the user's shell,
213   // but quoting and escaping internal quotes should handle 99.999% of all
214   // cases.
215   std::string escaped_name = file_to_edit.value();
216   ReplaceSubstringsAfterOffset(&escaped_name, 0, "\"", "\\\"");
217   cmd.append(escaped_name);
218   cmd.push_back('"');
219
220   OutputString("Waiting for editor on \"" + file_to_edit.value() +
221                "\"...\n");
222   return system(cmd.c_str()) == 0;
223 }
224
225 #endif
226
227 int EditArgsFile(const std::string& build_dir) {
228   {
229     // Scope the setup. We only use it for some basic state. We'll do the
230     // "real" build below in the gen command.
231     Setup setup;
232     setup.set_check_for_bad_items(false);
233     // Don't fill build arguments. We're about to edit the file which supplies
234     // these in the first place.
235     setup.set_fill_arguments(false);
236     if (!setup.DoSetup(build_dir))
237       return 1;
238
239     // Ensure the file exists. Need to normalize path separators since on
240     // Windows they can come out as forward slashes here, and that confuses some
241     // of the commands.
242     base::FilePath arg_file =
243         setup.build_settings().GetFullPath(setup.GetBuildArgFile())
244         .NormalizePathSeparators();
245     if (!base::PathExists(arg_file)) {
246       std::string argfile_default_contents =
247           "# Build arguments go here. Examples:\n"
248           "#   enable_doom_melon = true\n"
249           "#   crazy_something = \"absolutely\"\n";
250 #if defined(OS_WIN)
251       // Use Windows lineendings for this file since it will often open in
252       // Notepad which can't handle Unix ones.
253       ReplaceSubstringsAfterOffset(&argfile_default_contents, 0, "\n", "\r\n");
254 #endif
255       base::CreateDirectory(arg_file.DirName());
256       base::WriteFile(arg_file, argfile_default_contents.c_str(),
257                       static_cast<int>(argfile_default_contents.size()));
258     }
259
260     ScopedTrace editor_trace(TraceItem::TRACE_SETUP, "Waiting for editor");
261     if (!RunEditor(arg_file))
262       return 1;
263   }
264
265   // Now do a normal "gen" command.
266   OutputString("Generating files...\n");
267   std::vector<std::string> gen_commands;
268   gen_commands.push_back(build_dir);
269   return RunGen(gen_commands);
270 }
271
272 }  // namespace
273
274 extern const char kArgs[] = "args";
275 extern const char kArgs_HelpShort[] =
276     "args: Display or configure arguments declared by the build.";
277 extern const char kArgs_Help[] =
278     "gn args [arg name]\n"
279     "\n"
280     "  See also \"gn help buildargs\" for a more high-level overview of how\n"
281     "  build arguments work.\n"
282     "\n"
283     "Usage\n"
284     "  gn args <dir_name>\n"
285     "      Open the arguments for the given build directory in an editor\n"
286     "      (as specified by the EDITOR environment variable). If the given\n"
287     "      build directory doesn't exist, it will be created and an empty\n"
288     "      args file will be opened in the editor. You would type something\n"
289     "      like this into that file:\n"
290     "          enable_doom_melon=false\n"
291     "          os=\"android\"\n"
292     "\n"
293     "      Note: you can edit the build args manually by editing the file\n"
294     "      \"args.gn\" in the build directory and then running\n"
295     "      \"gn gen <build_dir>\".\n"
296     "\n"
297     "  gn args <dir_name> --list[=<exact_arg>] [--short]\n"
298     "      Lists all build arguments available in the current configuration,\n"
299     "      or, if an exact_arg is specified for the list flag, just that one\n"
300     "      build argument.\n"
301     "\n"
302     "      The output will list the declaration location, default value, and\n"
303     "      comment preceeding the declaration. If --short is specified,\n"
304     "      only the names and values will be printed.\n"
305     "\n"
306     "      If the dir_name is specified, the build configuration will be\n"
307     "      taken from that build directory. The reason this is needed is that\n"
308     "      the definition of some arguments is dependent on the build\n"
309     "      configuration, so setting some values might add, remove, or change\n"
310     "      the default values for other arguments. Specifying your exact\n"
311     "      configuration allows the proper arguments to be displayed.\n"
312     "\n"
313     "      Instead of specifying the dir_name, you can also use the\n"
314     "      command-line flag to specify the build configuration:\n"
315     "        --args=<exact list of args to use>\n"
316     "\n"
317     "Examples\n"
318     "  gn args out/Debug\n"
319     "    Opens an editor with the args for out/Debug.\n"
320     "\n"
321     "  gn args out/Debug --list --short\n"
322     "    Prints all arguments with their default values for the out/Debug\n"
323     "    build.\n"
324     "\n"
325     "  gn args out/Debug --list=cpu_arch\n"
326     "    Prints information about the \"cpu_arch\" argument for the out/Debug\n"
327     "    build.\n"
328     "\n"
329     "  gn args --list --args=\"os=\\\"android\\\" enable_doom_melon=true\"\n"
330     "    Prints all arguments with the default values for a build with the\n"
331     "    given arguments set (which may affect the values of other\n"
332     "    arguments).\n";
333
334 int RunArgs(const std::vector<std::string>& args) {
335   if (args.size() != 1) {
336     Err(Location(), "Exactly one build dir needed.",
337         "Usage: \"gn args <build_dir>\"\n"
338         "Or see \"gn help args\" for more variants.").PrintToStdout();
339     return 1;
340   }
341
342   if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchList))
343     return ListArgs(args[0]);
344   return EditArgsFile(args[0]);
345 }
346
347 }  // namespace commands