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/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" 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, false) || !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, true)) 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 348