• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2020 the V8 project authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6# This is main driver for gcmole tool. See README for more details.
7# Usage: CLANG_BIN=clang-bin-dir python tools/gcmole/gcmole.py [arm|arm64|ia32|x64]
8
9from multiprocessing import cpu_count
10from pathlib import Path
11
12import collections
13import difflib
14import json
15import optparse
16import os
17import re
18import subprocess
19import sys
20import threading
21import queue
22
23
24ArchCfg = collections.namedtuple(
25    "ArchCfg", ["name", "cpu", "triple", "arch_define", "arch_options"])
26
27# TODO(cbruni): use gn desc by default for platform-specific settings
28OPTIONS_64BIT = [
29    "-DV8_COMPRESS_POINTERS",
30    "-DV8_COMPRESS_POINTERS_IN_SHARED_CAGE",
31    "-DV8_EXTERNAL_CODE_SPACE",
32    "-DV8_SHORT_BUILTIN_CALLS",
33    "-DV8_SHARED_RO_HEAP",
34]
35
36ARCHITECTURES = {
37    "ia32":
38        ArchCfg(
39            name="ia32",
40            cpu="x86",
41            triple="i586-unknown-linux",
42            arch_define="V8_TARGET_ARCH_IA32",
43            arch_options=["-m32"],
44        ),
45    "arm":
46        ArchCfg(
47            name="arm",
48            cpu="arm",
49            triple="i586-unknown-linux",
50            arch_define="V8_TARGET_ARCH_ARM",
51            arch_options=["-m32"],
52        ),
53    # TODO(cbruni): Use detailed settings:
54    #   arch_options = OPTIONS_64BIT + [ "-DV8_WIN64_UNWINDING_INFO" ]
55    "x64":
56        ArchCfg(
57            name="x64",
58            cpu="x64",
59            triple="x86_64-unknown-linux",
60            arch_define="V8_TARGET_ARCH_X64",
61            arch_options=[]),
62    "arm64":
63        ArchCfg(
64            name="arm64",
65            cpu="arm64",
66            triple="x86_64-unknown-linux",
67            arch_define="V8_TARGET_ARCH_ARM64",
68            arch_options=[],
69        ),
70}
71ARCHITECTURES['x86'] = ARCHITECTURES['ia32']
72
73
74def log(format, *args, **kwargs):
75  mark = ("#", "=", "-", ".")[kwargs.get("level", 0)]
76  print(mark * 2, str(format).format(*list(map(str, args))))
77
78
79def fatal(format):
80  log(format)
81  sys.exit(1)
82
83
84# -----------------------------------------------------------------------------
85# Clang invocation
86
87
88def make_clang_command_line(plugin, plugin_args, options):
89  arch_cfg = ARCHITECTURES[options.v8_target_cpu]
90  prefixed_plugin_args = []
91  if plugin_args:
92    for arg in plugin_args:
93      prefixed_plugin_args += [
94          "-Xclang",
95          "-plugin-arg-" + plugin,
96          "-Xclang",
97          arg,
98      ]
99  log("Using generated files in {}", options.v8_build_dir / 'gen')
100  icu_src_dir = options.v8_root_dir / 'third_party/icu/source'
101  return ([
102      options.clang_bin_dir / "clang++",
103      "-std=c++17",
104      "-c",
105      "-Xclang",
106      "-load",
107      "-Xclang",
108      options.clang_plugins_dir / "libgcmole.so",
109      "-Xclang",
110      "-plugin",
111      "-Xclang",
112      plugin,
113  ] + prefixed_plugin_args + [
114      "-Xclang",
115      "-triple",
116      "-Xclang",
117      arch_cfg.triple,
118      "-fno-exceptions",
119      "-Wno-everything",
120      "-D",
121      arch_cfg.arch_define,
122      "-DENABLE_DEBUGGER_SUPPORT",
123      "-DV8_ENABLE_WEBASSEMBLY",
124      "-DV8_GC_MOLE",
125      "-DV8_INTL_SUPPORT",
126      "-I{}".format(options.v8_root_dir),
127      "-I{}".format(options.v8_root_dir / 'include'),
128      "-I{}".format(options.v8_build_dir / 'gen'),
129      "-I{}".format(icu_src_dir / 'common'),
130      "-I{}".format(icu_src_dir / 'i18n'),
131  ] + arch_cfg.arch_options)
132
133
134def invoke_clang_plugin_for_file(filename, cmd_line, verbose):
135  args = cmd_line + [filename]
136  args = list(map(str, args))
137  if verbose:
138    print("popen ", " ".join(args))
139  p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
140  stdout, stderr = p.communicate()
141  return p.returncode, stdout.decode("utf-8"), stderr.decode("utf-8")
142
143
144def invoke_clang_plugin_for_files_in_queue(i, input_queue, output_queue,
145                                           cancel_event, cmd_line, verbose):
146  success = False
147  try:
148    while not cancel_event.is_set():
149      filename = input_queue.get_nowait()
150      ret, stdout, stderr = invoke_clang_plugin_for_file(
151          filename, cmd_line, verbose)
152      output_queue.put_nowait((filename, ret, stdout, stderr))
153      if ret != 0:
154        break
155  except KeyboardInterrupt:
156    log("[{}] Interrupting", i, level=1)
157  except queue.Empty:
158    success = True
159  finally:
160    # Emit a success bool so that the reader knows that there was either an
161    # error or all files were processed.
162    output_queue.put_nowait(success)
163
164
165def invoke_clang_plugin_for_each_file(filenames, plugin, plugin_args, options):
166  cmd_line = make_clang_command_line(plugin, plugin_args, options)
167  verbose = options.verbose
168  if options.sequential:
169    log("Sequential execution.")
170    for filename in filenames:
171      log(filename, level=1)
172      returncode, stdout, stderr = invoke_clang_plugin_for_file(
173          filename, cmd_line, verbose)
174      if returncode != 0:
175        sys.stderr.write(stderr)
176        sys.exit(returncode)
177      yield filename, stdout, stderr
178  else:
179    log("Parallel execution.")
180    cpus = cpu_count()
181    input_queue = queue.Queue()
182    output_queue = queue.Queue()
183    threads = []
184    try:
185      for filename in filenames:
186        input_queue.put(filename)
187
188      cancel_event = threading.Event()
189
190      for i in range(min(len(filenames), cpus)):
191        threads.append(
192            threading.Thread(
193                target=invoke_clang_plugin_for_files_in_queue,
194                args=(i, input_queue, output_queue, cancel_event, cmd_line,
195                      verbose)))
196
197      for t in threads:
198        t.start()
199
200      num_finished = 0
201      while num_finished < len(threads):
202        output = output_queue.get()
203        if type(output) == bool:
204          if output:
205            num_finished += 1
206            continue
207          else:
208            break
209        filename, returncode, stdout, stderr = output
210        log(filename, level=2)
211        if returncode != 0:
212          sys.stderr.write(stderr)
213          sys.exit(returncode)
214        yield filename, stdout, stderr
215
216    finally:
217      cancel_event.set()
218      for t in threads:
219        t.join()
220
221
222# -----------------------------------------------------------------------------
223
224
225def parse_gn_file(options, for_test):
226  if for_test:
227    return {"all": [options.v8_root_dir / "tools/gcmole/gcmole-test.cc"]}
228  result = {}
229  gn_files = [
230      ("BUILD.gn", re.compile('"([^"]*?\.cc)"'), ""),
231      ("test/cctest/BUILD.gn", re.compile('"(test-[^"]*?\.cc)"'),
232       Path("test/cctest/")),
233  ]
234  for filename, pattern, prefix in gn_files:
235    path = options.v8_root_dir / filename
236    with open(path) as gn_file:
237      gn = gn_file.read()
238      for condition, sources in re.findall("### gcmole\((.*?)\) ###(.*?)\]", gn,
239                                           re.MULTILINE | re.DOTALL):
240        if condition not in result:
241          result[condition] = []
242        for file in pattern.findall(sources):
243          result[condition].append(options.v8_root_dir / prefix / file)
244
245  return result
246
247
248def evaluate_condition(cond, props):
249  if cond == "all":
250    return True
251
252  m = re.match("(\w+):(\w+)", cond)
253  if m is None:
254    fatal("failed to parse condition: {}", cond)
255  p, v = m.groups()
256  if p not in props:
257    fatal("undefined configuration property: {}", p)
258
259  return props[p] == v
260
261
262def build_file_list(options, for_test):
263  sources = parse_gn_file(options, for_test)
264  props = {
265      "os": "linux",
266      "arch": options.v8_target_cpu,
267      "mode": "debug",
268      "simulator": ""
269  }
270  ret = []
271  for condition, files in list(sources.items()):
272    if evaluate_condition(condition, props):
273      ret += files
274  return ret
275
276
277# -----------------------------------------------------------------------------
278# GCSuspects Generation
279
280# Note that the gcsuspects file lists functions in the form:
281#  mangled_name,unmangled_function_name
282#
283# This means that we can match just the function name by matching only
284# after a comma.
285ALLOWLIST = [
286    # The following functions call CEntryStub which is always present.
287    "MacroAssembler.*,CallRuntime",
288    "CompileCallLoadPropertyWithInterceptor",
289    "CallIC.*,GenerateMiss",
290    # DirectCEntryStub is a special stub used on ARM.
291    # It is pinned and always present.
292    "DirectCEntryStub.*,GenerateCall",
293    # TODO GCMole currently is sensitive enough to understand that certain
294    #    functions only cause GC and return Failure simulataneously.
295    #    Callsites of such functions are safe as long as they are properly
296    #    check return value and propagate the Failure to the caller.
297    #    It should be possible to extend GCMole to understand this.
298    "Heap.*,TryEvacuateObject",
299    # Ignore all StateTag methods.
300    "StateTag",
301    # Ignore printing of elements transition.
302    "PrintElementsTransition",
303    # CodeCreateEvent receives AbstractCode (a raw ptr) as an argument.
304    "CodeCreateEvent",
305    "WriteField",
306]
307
308GC_PATTERN = ",.*Collect.*Garbage"
309SAFEPOINT_PATTERN = ",SafepointSlowPath"
310ALLOWLIST_PATTERN = "|".join("(?:{})".format(p) for p in ALLOWLIST)
311
312
313def merge_regexp(pattern_dict):
314  return re.compile("|".join("(?P<{}>{})".format(key, value)
315                             for (key, value) in list(pattern_dict.items())))
316
317
318IS_SPECIAL_WITHOUT_ALLOW_LIST = merge_regexp({
319    "gc": GC_PATTERN,
320    "safepoint": SAFEPOINT_PATTERN
321})
322IS_SPECIAL_WITH_ALLOW_LIST = merge_regexp({
323    "gc": GC_PATTERN,
324    "safepoint": SAFEPOINT_PATTERN,
325    "allow": ALLOWLIST_PATTERN
326})
327
328
329class GCSuspectsCollector:
330
331  def __init__(self, options):
332    self.gc = {}
333    self.gc_caused = collections.defaultdict(lambda: set())
334    self.funcs = {}
335    self.current_caller = None
336    self.allowlist = options.allowlist
337    self.is_special = IS_SPECIAL_WITH_ALLOW_LIST if self.allowlist else IS_SPECIAL_WITHOUT_ALLOW_LIST
338
339  def add_cause(self, name, cause):
340    self.gc_caused[name].add(cause)
341
342  def parse(self, lines):
343    for funcname in lines:
344      if not funcname:
345        continue
346
347      if funcname[0] != "\t":
348        self.resolve(funcname)
349        self.current_caller = funcname
350      else:
351        name = funcname[1:]
352        callers_for_name = self.resolve(name)
353        callers_for_name.add(self.current_caller)
354
355  def resolve(self, name):
356    if name not in self.funcs:
357      self.funcs[name] = set()
358      m = self.is_special.search(name)
359      if m:
360        if m.group("gc"):
361          self.gc[name] = True
362          self.add_cause(name, "<GC>")
363        elif m.group("safepoint"):
364          self.gc[name] = True
365          self.add_cause(name, "<Safepoint>")
366        elif m.group("allow"):
367          self.gc[name] = False
368
369    return self.funcs[name]
370
371  def propagate(self):
372    log("Propagating GC information")
373
374    def mark(funcname, callers):
375      for caller in callers:
376        if caller not in self.gc:
377          self.gc[caller] = True
378          mark(caller, self.funcs[caller])
379        self.add_cause(caller, funcname)
380
381    for funcname, callers in list(self.funcs.items()):
382      if self.gc.get(funcname, False):
383        mark(funcname, callers)
384
385
386def generate_gc_suspects(files, options):
387  # Reset the global state.
388  collector = GCSuspectsCollector(options)
389
390  log("Building GC Suspects for {}", options.v8_target_cpu)
391  for _, stdout, _ in invoke_clang_plugin_for_each_file(files, "dump-callees",
392                                                        [], options):
393    collector.parse(stdout.splitlines())
394  collector.propagate()
395  # TODO(cbruni): remove once gcmole.cc is migrated
396  write_gcmole_results(collector, options, options.v8_root_dir)
397  write_gcmole_results(collector, options, options.out_dir)
398
399
400def write_gcmole_results(collector, options, dst):
401  # gcsuspects contains a list("mangled_full_name,name") of all functions that
402  # could cause a gc (directly or indirectly).
403  #
404  # EXAMPLE
405  # _ZN2v88internal4Heap16CreateApiObjectsEv,CreateApiObjects
406  # _ZN2v88internal4Heap17CreateInitialMapsEv,CreateInitialMaps
407  # ...
408  with open(dst / "gcsuspects", "w") as out:
409    for name, value in list(collector.gc.items()):
410      if value:
411        out.write(name + "\n")
412  # gccauses contains a map["mangled_full_name,name"] => list(inner gcsuspects)
413  # Where the inner gcsuspects are functions directly called in the outer
414  # function that can cause a gc. The format is encoded for simplified
415  # deserialization in gcmole.cc.
416  #
417  # EXAMPLE:
418  # _ZN2v88internal4Heap17CreateHeapObjectsEv,CreateHeapObjects
419  # start,nested
420  # _ZN2v88internal4Heap16CreateApiObjectsEv,CreateApiObjects
421  # _ZN2v88internal4Heap17CreateInitialMapsEv,CreateInitialMaps
422  # ...
423  # end,nested
424  # ...
425  with open(dst / "gccauses", "w") as out:
426    for name, causes in list(collector.gc_caused.items()):
427      out.write("{}\n".format(name))
428      out.write("start,nested\n")
429      for cause in causes:
430        out.write("{}\n".format(cause))
431      out.write("end,nested\n")
432  log("GCSuspects and gccauses generated for {} in '{}'", options.v8_target_cpu,
433      dst)
434
435
436# ------------------------------------------------------------------------------
437# Analysis
438
439
440def check_correctness_for_arch(options, for_test):
441  files = build_file_list(options, for_test)
442
443  if not options.reuse_gcsuspects:
444    generate_gc_suspects(files, options)
445  else:
446    log("Reusing GCSuspects for {}", options.v8_target_cpu)
447
448  processed_files = 0
449  errors_found = False
450  output = ""
451
452  log("Searching for evaluation order problems " +
453      (' and dead variables' if options.dead_vars else '') + "for" +
454      options.v8_target_cpu)
455  plugin_args = []
456  if options.dead_vars:
457    plugin_args.append("--dead-vars")
458  if options.verbose:
459    plugin_args.append("--verbose")
460  if options.verbose_trace:
461    plugin_args.append("--verbose-trace")
462  for _, _, stderr in invoke_clang_plugin_for_each_file(files, "find-problems",
463                                                        plugin_args, options):
464    processed_files = processed_files + 1
465    if not errors_found:
466      errors_found = re.search("^[^:]+:\d+:\d+: (warning|error)", stderr,
467                               re.MULTILINE) is not None
468    if for_test:
469      output = output + stderr
470    else:
471      sys.stdout.write(stderr)
472
473  log("Done processing {} files.", processed_files)
474  log("Errors found" if errors_found else "No errors found")
475
476  return errors_found, output
477
478
479def test_run(options):
480  if not options.test_run:
481    return True
482  log("Test Run")
483  errors_found, output = check_correctness_for_arch(options, True)
484  if not errors_found:
485    log("Test file should produce errors, but none were found. Output:")
486    print(output)
487    return False
488
489  new_file = options.out_dir / "test-expectations-gen.txt"
490  with open(new_file, "w") as f:
491    f.write(output)
492  log("Wrote test-results: {}", new_file)
493
494  expected_file = options.v8_root_dir / "tools/gcmole/test-expectations.txt"
495  with open(expected_file) as exp_file:
496    expectations = exp_file.read()
497
498  if output != expectations:
499    diff_file = options.out_dir / "test_output.diff"
500    print("#" * 79)
501    log("Output mismatch from running tests.")
502    log("Please run gcmole manually with --test-run --verbose.")
503    log("Expected: " + expected_file)
504    log("New:      " + new_file)
505    log("*Diff:*   " + diff_file)
506    print("#" * 79)
507    for line in difflib.unified_diff(
508        expectations.splitlines(),
509        output.splitlines(),
510        fromfile=str(new_file),
511        tofile=str(diff_file),
512        lineterm="",
513    ):
514      print(line)
515
516    print("#" * 79)
517    log("Full output")
518    log("Expected: " + expected_file)
519    log("Diff:     " + diff_file)
520    log("*New:*    " + new_file)
521    print("#" * 79)
522    print(output)
523    print("#" * 79)
524
525    return False
526
527  log("Tests ran successfully")
528  return True
529
530
531# =============================================================================
532def relative_parents(path, level=0):
533  return Path(os.path.relpath(str(path.resolve().parents[level])))
534
535
536def main(args):
537  # Print arguments for better debugging on the bots
538  # Get a clean parent path relative to PWD
539  gcmole_dir = relative_parents(Path(args[0]))
540
541  parser = optparse.OptionParser()
542  archs = list(ARCHITECTURES.keys())
543  parser.add_option(
544      "--v8-target-cpu",
545      type="choice",
546      choices=archs,
547      help="Tested CPU architecture. Choices: {}".format(archs),
548      metavar="CPU")
549  default_clang_bin_dir = gcmole_dir / 'gcmole-tools/bin'
550  parser.add_option(
551      "--clang-bin-dir",
552      metavar="DIR",
553      help="Build dir of the custom clang version for gcmole." + \
554      "Default: env['CLANG_DIR'] or '{}'".format(default_clang_bin_dir))
555  parser.add_option(
556      "--clang-plugins-dir",
557      metavar="DIR",
558      help="Containing dir for libgcmole.so."
559      "Default: env['CLANG_PLUGINS'] or '{}'".format(gcmole_dir))
560  default_root_dir = relative_parents(gcmole_dir, 1)
561  parser.add_option(
562      "--v8-root-dir",
563      metavar="DIR",
564      default=default_root_dir,
565      help="V8 checkout directory. Default: '{}'".format(default_root_dir))
566  parser.add_option(
567      "--v8-build-dir",
568      metavar="BUILD_DIR",
569      help="GN build dir for v8. Default: 'out/CPU.Release'. "
570      "Config must match cpu specified by --v8-target-cpu")
571  parser.add_option(
572      "--out-dir",
573      metavar="DIR",
574      help="Output location for the gcsuspect and gcauses file."
575      "Default: BUILD_DIR/gen/tools/gcmole")
576  parser.add_option(
577      "--is-bot",
578      action="store_true",
579      default=False,
580      help="Flag for setting build bot specific settings.")
581
582  group = optparse.OptionGroup(parser, "GCMOLE options")
583  group.add_option(
584      "--reuse-gcsuspects",
585      action="store_true",
586      default=False,
587      help="Don't build gcsuspects file and reuse previously generated one.")
588  group.add_option(
589      "--sequential",
590      action="store_true",
591      default=False,
592      help="Don't use parallel python runner.")
593  group.add_option(
594      "--verbose",
595      action="store_true",
596      default=False,
597      help="Print commands to console before executing them.")
598  group.add_option(
599      "--no-dead-vars",
600      action="store_false",
601      dest="dead_vars",
602      default=True,
603      help="Don't perform dead variable analysis.")
604  group.add_option(
605      "--verbose-trace",
606      action="store_true",
607      default=False,
608      help="Enable verbose tracing from the plugin itself."
609      "This can be useful to debug finding dead variable.")
610  group.add_option(
611      "--no-allowlist",
612      action="store_true",
613      default=True,
614      dest="allowlist",
615      help="When building gcsuspects allowlist certain functions as if they can be "
616      "causing GC. Currently used to reduce number of false positives in dead "
617      "variables analysis. See TODO for ALLOWLIST in gcmole.py")
618  group.add_option(
619      "--test-run",
620      action="store_true",
621      default=False,
622      help="Test gcmole on tools/gcmole/gcmole-test.cc")
623  parser.add_option_group(group)
624
625  (options, args) = parser.parse_args()
626
627  if not options.v8_target_cpu:
628    # Backwards compatibility
629    if len(args[0]) > 0 and args[0] in archs:
630      options.v8_target_cpu = args[0]
631      log("Using --v8-target-cpu={}", options.v8_target_cpu)
632    else:
633      parser.error("Missing --v8-target-cpu option")
634
635  options.is_bot = False
636  verify_and_convert_dirs(parser, options, gcmole_dir, default_clang_bin_dir)
637  verify_clang_plugin(parser, options)
638  prepare_gcmole_files(options)
639  verify_build_config(parser, options)
640
641  any_errors_found = False
642  if not test_run(options):
643    any_errors_found = True
644  else:
645    errors_found, output = check_correctness_for_arch(options, False)
646    any_errors_found = any_errors_found or errors_found
647
648  sys.exit(1 if any_errors_found else 0)
649
650
651def verify_and_convert_dirs(parser, options, gcmole_dir, default_clang_bin_dir):
652  # Verify options for setting directors and convert the input strings to Path
653  # objects.
654  options.v8_root_dir = Path(options.v8_root_dir)
655
656  if not options.clang_bin_dir:
657    if os.getenv("CLANG_BIN"):
658      options.clang_bin_dir = Path(os.getenv("CLANG_BIN"))
659      options.is_bot = True
660    else:
661      options.clang_bin_dir = default_clang_bin_dir
662      if not (options.clang_bin_dir / 'clang++').exists():
663        options.clang_bin_dir = Path(gcmole_dir,
664                                     "tools/gcmole/bootstrap/build/bin")
665    log("Using --clang-bin-dir={}", options.clang_bin_dir)
666  else:
667    options.clang_bin_dir = Path(options.clang_bin_dir)
668
669  if not options.clang_plugins_dir:
670    if os.getenv("CLANG_PLUGINS"):
671      options.clang_plugins_dir = Path(os.getenv("CLANG_PLUGINS"))
672    else:
673      options.clang_plugins_dir = gcmole_dir.resolve()
674    log("Using --clang-plugins-dir={}", options.clang_plugins_dir)
675  else:
676    options.clang_plugins_dir = Path(options.clang_plugins_dir)
677
678  if not options.v8_build_dir:
679    config = ARCHITECTURES[options.v8_target_cpu]
680    options.v8_build_dir = options.v8_root_dir / ('out/%s.release' %
681                                                  config.name)
682    # Fallback for build bots.
683    if not options.v8_build_dir.exists() and os.getenv("CLANG_BIN"):
684      options.v8_build_dir = options.v8_root_dir / 'out/build'
685    log("Using --v8-build-dir={}", options.v8_build_dir)
686  else:
687    options.v8_build_dir = Path(options.v8_build_dir)
688
689  if not options.out_dir:
690    options.out_dir = options.v8_build_dir / 'gen/tools/gcmole'
691    if options.v8_build_dir.exists():
692      options.out_dir.mkdir(parents=True, exist_ok=True)
693  else:
694    options.out_dir = Path(options.out_dir)
695
696  for flag in [
697      "--v8-root-dir", "--v8-build-dir", "--clang-bin-dir",
698      "--clang-plugins-dir", "--out-dir"
699  ]:
700    dir = getattr(options, parser.get_option(flag).dest)
701    if not dir.is_dir():
702      parser.error("{}='{}' does not exist!".format(flag, dir))
703
704
705def verify_clang_plugin(parser, options):
706  libgcmole_path = options.clang_plugins_dir / "libgcmole.so"
707  if not libgcmole_path.is_file():
708    parser.error("'{}' does not exist. Please build gcmole first.".format(
709        libgcmole_path))
710  clang_path = options.clang_bin_dir / "clang++"
711  if not clang_path.is_file():
712    parser.error(
713        "'{}' does not exist. Please build gcmole first.".format(clang_path))
714
715
716def prepare_gcmole_files(options):
717  cmd = [
718      "ninja", "-C", options.v8_build_dir, "v8_gcmole_files",
719      "v8_dump_build_config"
720  ]
721  cmd = list(map(str, cmd))
722  log("Preparing files: {}", " ".join(cmd))
723  try:
724    subprocess.check_call(cmd)
725  except:
726    # Ignore ninja task errors on the bots
727    if options.is_bot:
728      log("Ninja command failed, ignoring errors.")
729    else:
730      raise
731
732
733def verify_build_config(parser, options):
734  if options.is_bot:
735    #TODO(cbruni): Fix, currently not supported on the bots
736    return
737  config_file = options.v8_build_dir / 'v8_build_config.json'
738  with open(config_file) as f:
739    config = json.load(f)
740  found_cpu = None
741  for key in ('v8_target_cpu', 'target_cpu', 'current_cpu'):
742    found_cpu = config.get('v8_target_cpu')
743    if found_cpu == options.v8_target_cpu:
744      return
745  parser.error("Build dir '{}' config doesn't match request cpu. {}: {}".format(
746      options.v8_build_dir, options.v8_target_cpu, found_cpu))
747
748
749if __name__ == "__main__":
750  main(sys.argv)
751