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