summaryrefslogtreecommitdiff
path: root/chromium/tools/gn/command_args.cc
blob: 1b81135be3d9ff23e632380568d956c8ec4661b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <map>

#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "tools/gn/commands.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/input_file.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/setup.h"
#include "tools/gn/standard_out.h"
#include "tools/gn/tokenizer.h"
#include "tools/gn/trace.h"

#if defined(OS_WIN)
#include <windows.h>
#include <shellapi.h>
#endif

namespace commands {

namespace {

const char kSwitchList[] = "list";
const char kSwitchShort[] = "short";

bool DoesLineBeginWithComment(const base::StringPiece& line) {
  // Skip whitespace.
  size_t i = 0;
  while (i < line.size() && base::IsAsciiWhitespace(line[i]))
    i++;

  return i < line.size() && line[i] == '#';
}

// Returns the offset of the beginning of the line identified by |offset|.
size_t BackUpToLineBegin(const std::string& data, size_t offset) {
  // Degenerate case of an empty line. Below we'll try to return the
  // character after the newline, but that will be incorrect in this case.
  if (offset == 0 || Tokenizer::IsNewline(data, offset))
    return offset;

  size_t cur = offset;
  do {
    cur --;
    if (Tokenizer::IsNewline(data, cur))
      return cur + 1;  // Want the first character *after* the newline.
  } while (cur > 0);
  return 0;
}

// Assumes DoesLineBeginWithComment(), this strips the # character from the
// beginning and normalizes preceeding whitespace.
std::string StripHashFromLine(const base::StringPiece& line) {
  // Replace the # sign and everything before it with 3 spaces, so that a
  // normal comment that has a space after the # will be indented 4 spaces
  // (which makes our formatting come out nicely). If the comment is indented
  // from there, we want to preserve that indenting.
  return "   " + line.substr(line.find('#') + 1).as_string();
}

// Tries to find the comment before the setting of the given value.
void GetContextForValue(const Value& value,
                        std::string* location_str,
                        std::string* comment) {
  Location location = value.origin()->GetRange().begin();
  const InputFile* file = location.file();
  if (!file)
    return;

  *location_str = file->name().value() + ":" +
      base::IntToString(location.line_number());

  const std::string& data = file->contents();
  size_t line_off =
      Tokenizer::ByteOffsetOfNthLine(data, location.line_number());

  while (line_off > 1) {
    line_off -= 2;  // Back up to end of previous line.
    size_t previous_line_offset = BackUpToLineBegin(data, line_off);

    base::StringPiece line(&data[previous_line_offset],
                           line_off - previous_line_offset + 1);
    if (!DoesLineBeginWithComment(line))
      break;

    comment->insert(0, StripHashFromLine(line) + "\n");
    line_off = previous_line_offset;
  }
}

void PrintArgHelp(const base::StringPiece& name, const Value& value) {
  OutputString(name.as_string(), DECORATION_YELLOW);
  OutputString("  Default = " + value.ToString(true) + "\n");

  if (value.origin()) {
    std::string location, comment;
    GetContextForValue(value, &location, &comment);
    OutputString("    " + location + "\n" + comment);
  } else {
    OutputString("    (Internally set; try `gn help " + name.as_string() +
                 "`.)\n");
  }
}

int ListArgs(const std::string& build_dir) {
  Setup* setup = new Setup;
  if (!setup->DoSetup(build_dir, false) || !setup->Run())
    return 1;

  Scope::KeyValueMap build_args;
  setup->build_settings().build_args().MergeDeclaredArguments(&build_args);

  // Find all of the arguments we care about. Use a regular map so they're
  // sorted nicely when we write them out.
  std::map<base::StringPiece, Value> sorted_args;
  std::string list_value =
      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchList);
  if (list_value.empty()) {
    // List all values.
    for (const auto& arg : build_args)
      sorted_args.insert(arg);
  } else {
    // List just the one specified as the parameter to --list.
    Scope::KeyValueMap::const_iterator found_arg = build_args.find(list_value);
    if (found_arg == build_args.end()) {
      Err(Location(), "Unknown build argument.",
          "You asked for \"" + list_value + "\" which I didn't find in any "
          "build file\nassociated with this build.").PrintToStdout();
      return 1;
    }
    sorted_args.insert(*found_arg);
  }

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchShort)) {
    // Short key=value output.
    for (const auto& arg : sorted_args) {
      OutputString(arg.first.as_string());
      OutputString(" = ");
      OutputString(arg.second.ToString(true));
      OutputString("\n");
    }
    return 0;
  }

  // Long output.
  for (const auto& arg : sorted_args) {
    PrintArgHelp(arg.first, arg.second);
    OutputString("\n");
  }

  return 0;
}

#if defined(OS_WIN)

bool RunEditor(const base::FilePath& file_to_edit) {
  SHELLEXECUTEINFO info;
  memset(&info, 0, sizeof(info));
  info.cbSize = sizeof(info);
  info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_CLASSNAME;
  info.lpFile = file_to_edit.value().c_str();
  info.nShow = SW_SHOW;
  info.lpClass = L".txt";
  if (!::ShellExecuteEx(&info)) {
    Err(Location(), "Couldn't run editor.",
        "Just edit \"" + FilePathToUTF8(file_to_edit) +
        "\" manually instead.").PrintToStdout();
    return false;
  }

  if (!info.hProcess) {
    // Windows re-used an existing process.
    OutputString("\"" + FilePathToUTF8(file_to_edit) +
                 "\" opened in editor, save it and press <Enter> when done.\n");
    getchar();
  } else {
    OutputString("Waiting for editor on \"" + FilePathToUTF8(file_to_edit) +
                 "\"...\n");
    ::WaitForSingleObject(info.hProcess, INFINITE);
    ::CloseHandle(info.hProcess);
  }
  return true;
}

#else  // POSIX

bool RunEditor(const base::FilePath& file_to_edit) {
  const char* editor_ptr = getenv("VISUAL");
  if (!editor_ptr)
    editor_ptr = getenv("GN_EDITOR");
  if (!editor_ptr)
    editor_ptr = getenv("EDITOR");
  if (!editor_ptr)
    editor_ptr = "vi";

  std::string cmd(editor_ptr);
  cmd.append(" \"");

  // Its impossible to do this properly since we don't know the user's shell,
  // but quoting and escaping internal quotes should handle 99.999% of all
  // cases.
  std::string escaped_name = file_to_edit.value();
  base::ReplaceSubstringsAfterOffset(&escaped_name, 0, "\"", "\\\"");
  cmd.append(escaped_name);
  cmd.push_back('"');

  OutputString("Waiting for editor on \"" + file_to_edit.value() +
               "\"...\n");
  return system(cmd.c_str()) == 0;
}

#endif

int EditArgsFile(const std::string& build_dir) {
  {
    // Scope the setup. We only use it for some basic state. We'll do the
    // "real" build below in the gen command.
    Setup setup;
    // Don't fill build arguments. We're about to edit the file which supplies
    // these in the first place.
    setup.set_fill_arguments(false);
    if (!setup.DoSetup(build_dir, true))
      return 1;

    // Ensure the file exists. Need to normalize path separators since on
    // Windows they can come out as forward slashes here, and that confuses some
    // of the commands.
    base::FilePath arg_file =
        setup.build_settings().GetFullPath(setup.GetBuildArgFile())
        .NormalizePathSeparators();
    if (!base::PathExists(arg_file)) {
      std::string argfile_default_contents =
          "# Build arguments go here. Examples:\n"
          "#   is_component_build = true\n"
          "#   is_debug = false\n"
          "# See \"gn args <out_dir> --list\" for available build "
          "arguments.\n";
#if defined(OS_WIN)
      // Use Windows lineendings for this file since it will often open in
      // Notepad which can't handle Unix ones.
      base::ReplaceSubstringsAfterOffset(
          &argfile_default_contents, 0, "\n", "\r\n");
#endif
      base::CreateDirectory(arg_file.DirName());
      base::WriteFile(arg_file, argfile_default_contents.c_str(),
                      static_cast<int>(argfile_default_contents.size()));
    }

    ScopedTrace editor_trace(TraceItem::TRACE_SETUP, "Waiting for editor");
    if (!RunEditor(arg_file))
      return 1;
  }

  // Now do a normal "gen" command.
  OutputString("Generating files...\n");
  std::vector<std::string> gen_commands;
  gen_commands.push_back(build_dir);
  return RunGen(gen_commands);
}

}  // namespace

extern const char kArgs[] = "args";
extern const char kArgs_HelpShort[] =
    "args: Display or configure arguments declared by the build.";
extern const char kArgs_Help[] =
    R"(gn args <out_dir> [--list] [--short] [--args]

  See also "gn help buildargs" for a more high-level overview of how
  build arguments work.

Usage
  gn args <out_dir>
      Open the arguments for the given build directory in an editor (as
      specified by the EDITOR environment variable). If the given build
      directory doesn't exist, it will be created and an empty args file will
      be opened in the editor. You would type something like this into that
      file:
          enable_doom_melon=false
          os="android"

      Note: you can edit the build args manually by editing the file "args.gn"
      in the build directory and then running "gn gen <out_dir>".

  gn args <out_dir> --list[=<exact_arg>] [--short]
      Lists all build arguments available in the current configuration, or, if
      an exact_arg is specified for the list flag, just that one build
      argument.

      The output will list the declaration location, default value, and comment
      preceeding the declaration. If --short is specified, only the names and
      values will be printed.

      If the out_dir is specified, the build configuration will be taken from
      that build directory. The reason this is needed is that the definition of
      some arguments is dependent on the build configuration, so setting some
      values might add, remove, or change the default values for other
      arguments. Specifying your exact configuration allows the proper
      arguments to be displayed.

      Instead of specifying the out_dir, you can also use the command-line flag
      to specify the build configuration:
        --args=<exact list of args to use>

Examples

  gn args out/Debug
    Opens an editor with the args for out/Debug.

  gn args out/Debug --list --short
    Prints all arguments with their default values for the out/Debug
    build.

  gn args out/Debug --list=target_cpu
    Prints information about the "target_cpu" argument for the "
   "out/Debug
    build.

  gn args --list --args="os=\"android\" enable_doom_melon=true"
    Prints all arguments with the default values for a build with the
    given arguments set (which may affect the values of other
    arguments).
)";

int RunArgs(const std::vector<std::string>& args) {
  if (args.size() != 1) {
    Err(Location(), "Exactly one build dir needed.",
        "Usage: \"gn args <out_dir>\"\n"
        "Or see \"gn help args\" for more variants.").PrintToStdout();
    return 1;
  }

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchList))
    return ListArgs(args[0]);
  return EditArgsFile(args[0]);
}

}  // namespace commands