Home | History | Annotate | Download | only in gn
      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