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.
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"
34 const char kSwitchList
[] = "list";
35 const char kSwitchShort
[] = "short";
37 bool DoesLineBeginWithComment(const base::StringPiece
& line
) {
40 while (i
< line
.size() && IsAsciiWhitespace(line
[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
))
56 if (Tokenizer::IsNewline(data
, cur
))
57 return cur
+ 1; // Want the first character *after* the newline.
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();
81 *location_str
= file
->name().value() + ":" +
82 base::IntToString(location
.line_number());
84 const std::string
& data
= file
->contents();
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
))
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
);
111 OutputString(" (Internally set)\n");
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
, false) || !setup
->Run())
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()) {
131 for (Scope::KeyValueMap::const_iterator i
= build_args
.begin();
132 i
!= build_args
.end(); ++i
)
133 sorted_args
.insert(*i
);
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();
143 sorted_args
.insert(*found_arg
);
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());
152 OutputString(i
->second
.ToString(true));
159 for (std::map
<base::StringPiece
, Value
>::iterator i
= sorted_args
.begin();
160 i
!= sorted_args
.end(); ++i
) {
161 PrintArgHelp(i
->first
, i
->second
);
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();
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");
191 OutputString("Waiting for editor on \"" + FilePathToUTF8(file_to_edit
) +
193 ::WaitForSingleObject(info
.hProcess
, INFINITE
);
194 ::CloseHandle(info
.hProcess
);
201 bool RunEditor(const base::FilePath
& file_to_edit
) {
202 // Prefer $VISUAL, then $EDITOR, then vi.
203 const char* editor_ptr
= getenv("VISUAL");
205 editor_ptr
= getenv("EDITOR");
209 std::string
cmd(editor_ptr
);
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
215 std::string escaped_name
= file_to_edit
.value();
216 ReplaceSubstringsAfterOffset(&escaped_name
, 0, "\"", "\\\"");
217 cmd
.append(escaped_name
);
220 OutputString("Waiting for editor on \"" + file_to_edit
.value() +
222 return system(cmd
.c_str()) == 0;
227 int EditArgsFile(const std::string
& build_dir
) {
229 // Scope the setup. We only use it for some basic state. We'll do the
230 // "real" build below in the gen command.
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
, true))
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
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";
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");
255 base::CreateDirectory(arg_file
.DirName());
256 base::WriteFile(arg_file
, argfile_default_contents
.c_str(),
257 static_cast<int>(argfile_default_contents
.size()));
260 ScopedTrace
editor_trace(TraceItem::TRACE_SETUP
, "Waiting for editor");
261 if (!RunEditor(arg_file
))
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
);
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"
280 " See also \"gn help buildargs\" for a more high-level overview of how\n"
281 " build arguments work.\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"
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"
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"
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"
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"
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"
318 " gn args out/Debug\n"
319 " Opens an editor with the args for out/Debug.\n"
321 " gn args out/Debug --list --short\n"
322 " Prints all arguments with their default values for the out/Debug\n"
325 " gn args out/Debug --list=cpu_arch\n"
326 " Prints information about the \"cpu_arch\" argument for the out/Debug\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"
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();
342 if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchList
))
343 return ListArgs(args
[0]);
344 return EditArgsFile(args
[0]);
347 } // namespace commands