Home | History | Annotate | Download | only in dev
      1 #!/usr/bin/env python
      2 # Copyright 2017 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 Convenience wrapper for compiling V8 with gn/ninja and running tests.
      7 Sets up build output directories if they don't exist.
      8 Produces simulator builds for non-Intel target architectures.
      9 Uses Goma by default if it is detected (at output directory setup time).
     10 Expects to be run from the root of a V8 checkout.
     11 
     12 Usage:
     13     gm.py [<arch>].[<mode>].[<target>] [testname...]
     14 
     15 All arguments are optional. Most combinations should work, e.g.:
     16     gm.py ia32.debug x64.release d8
     17     gm.py x64 mjsunit/foo cctest/test-bar/*
     18 """
     19 # See HELP below for additional documentation.
     20 
     21 import os
     22 import subprocess
     23 import sys
     24 
     25 BUILD_OPTS_DEFAULT = ""
     26 BUILD_OPTS_GOMA = "-j1000 -l50"
     27 BUILD_TARGETS_TEST = ["d8", "cctest", "unittests"]
     28 BUILD_TARGETS_ALL = ["all"]
     29 
     30 # All arches that this script understands.
     31 ARCHES = ["ia32", "x64", "arm", "arm64", "mipsel", "mips64el", "ppc", "ppc64",
     32           "s390", "s390x", "x87"]
     33 # Arches that get built/run when you don't specify any.
     34 DEFAULT_ARCHES = ["ia32", "x64", "arm", "arm64"]
     35 # Modes that this script understands.
     36 MODES = ["release", "debug", "optdebug"]
     37 # Modes that get built/run when you don't specify any.
     38 DEFAULT_MODES = ["release", "debug"]
     39 # Build targets that can be manually specified.
     40 TARGETS = ["d8", "cctest", "unittests", "v8_fuzzers"]
     41 # Build targets that get built when you don't specify any (and specified tests
     42 # don't imply any other targets).
     43 DEFAULT_TARGETS = ["d8"]
     44 # Tests that run-tests.py would run by default that can be run with
     45 # BUILD_TARGETS_TESTS.
     46 DEFAULT_TESTS = ["cctest", "debugger", "intl", "message", "mjsunit",
     47                  "preparser", "unittests"]
     48 # These can be suffixed to any <arch>.<mode> combo, or used standalone,
     49 # or used as global modifiers (affecting all <arch>.<mode> combos).
     50 ACTIONS = {
     51   "all": {"targets": BUILD_TARGETS_ALL, "tests": []},
     52   "tests": {"targets": BUILD_TARGETS_TEST, "tests": []},
     53   "check": {"targets": BUILD_TARGETS_TEST, "tests": DEFAULT_TESTS},
     54   "checkall": {"targets": BUILD_TARGETS_ALL, "tests": ["ALL"]},
     55 }
     56 
     57 HELP = """<arch> can be any of: %(arches)s
     58 <mode> can be any of: %(modes)s
     59 <target> can be any of:
     60  - cctest, d8, unittests, v8_fuzzers (build respective binary)
     61  - all (build all binaries)
     62  - tests (build test binaries)
     63  - check (build test binaries, run most tests)
     64  - checkall (build all binaries, run more tests)
     65 """ % {"arches": " ".join(ARCHES),
     66        "modes": " ".join(MODES)}
     67 
     68 TESTSUITES_TARGETS = {"benchmarks": "d8",
     69               "cctest": "cctest",
     70               "debugger": "d8",
     71               "fuzzer": "v8_fuzzers",
     72               "intl": "d8",
     73               "message": "d8",
     74               "mjsunit": "d8",
     75               "mozilla": "d8",
     76               "preparser": "d8",
     77               "test262": "d8",
     78               "unittests": "unittests",
     79               "webkit": "d8"}
     80 
     81 OUTDIR = "out"
     82 
     83 IS_GOMA_MACHINE = (os.path.exists(os.path.expanduser("~/goma")) or
     84                    os.environ.get('GOMADIR'))
     85 
     86 USE_GOMA = "true" if IS_GOMA_MACHINE else "false"
     87 BUILD_OPTS = BUILD_OPTS_GOMA if IS_GOMA_MACHINE else BUILD_OPTS_DEFAULT
     88 
     89 RELEASE_ARGS_TEMPLATE = """\
     90 is_component_build = false
     91 is_debug = false
     92 %s
     93 use_goma = {GOMA}
     94 v8_enable_backtrace = true
     95 v8_enable_disassembler = true
     96 v8_enable_object_print = true
     97 v8_enable_verify_heap = true
     98 """.replace("{GOMA}", USE_GOMA)
     99 
    100 DEBUG_ARGS_TEMPLATE = """\
    101 gdb_index = true
    102 is_component_build = true
    103 is_debug = true
    104 symbol_level = 2
    105 %s
    106 use_goma = {GOMA}
    107 v8_enable_backtrace = true
    108 v8_enable_slow_dchecks = true
    109 v8_optimized_debug = false
    110 """.replace("{GOMA}", USE_GOMA)
    111 
    112 OPTDEBUG_ARGS_TEMPLATE = """\
    113 gdb_index = false
    114 is_component_build = true
    115 is_debug = true
    116 symbol_level = 1
    117 %s
    118 use_goma = {GOMA}
    119 v8_enable_backtrace = true
    120 v8_enable_verify_heap = true
    121 v8_optimized_debug = true
    122 """.replace("{GOMA}", USE_GOMA)
    123 
    124 ARGS_TEMPLATES = {
    125   "release": RELEASE_ARGS_TEMPLATE,
    126   "debug": DEBUG_ARGS_TEMPLATE,
    127   "optdebug": OPTDEBUG_ARGS_TEMPLATE
    128 }
    129 
    130 def PrintHelpAndExit():
    131   print(__doc__)
    132   print(HELP)
    133   sys.exit(0)
    134 
    135 def _Call(cmd, silent=False):
    136   if not silent: print("# %s" % cmd)
    137   return subprocess.call(cmd, shell=True)
    138 
    139 def _Write(filename, content):
    140   print("# echo > %s << EOF\n%sEOF" % (filename, content))
    141   with open(filename, "w") as f:
    142     f.write(content)
    143 
    144 def GetPath(arch, mode):
    145   subdir = "%s.%s" % (arch, mode)
    146   return os.path.join(OUTDIR, subdir)
    147 
    148 class Config(object):
    149   def __init__(self, arch, mode, targets, tests=[]):
    150     self.arch = arch
    151     self.mode = mode
    152     self.targets = set(targets)
    153     self.tests = set(tests)
    154 
    155   def Extend(self, targets, tests=[]):
    156     self.targets.update(targets)
    157     self.tests.update(tests)
    158 
    159   def GetTargetCpu(self):
    160     cpu = "x86"
    161     if self.arch.endswith("64") or self.arch == "s390x":
    162       cpu = "x64"
    163     return "target_cpu = \"%s\"" % cpu
    164 
    165   def GetV8TargetCpu(self):
    166     if self.arch in ("arm", "arm64", "mipsel", "mips64el", "ppc", "ppc64",
    167                      "s390", "s390x"):
    168       return "\nv8_target_cpu = \"%s\"" % self.arch
    169     return ""
    170 
    171   def GetGnArgs(self):
    172     template = ARGS_TEMPLATES[self.mode]
    173     arch_specific = self.GetTargetCpu() + self.GetV8TargetCpu()
    174     return template % arch_specific
    175 
    176   def Build(self):
    177     path = GetPath(self.arch, self.mode)
    178     args_gn = os.path.join(path, "args.gn")
    179     if not os.path.exists(path):
    180       print("# mkdir -p %s" % path)
    181       os.makedirs(path)
    182     if not os.path.exists(args_gn):
    183       _Write(args_gn, self.GetGnArgs())
    184       code = _Call("gn gen %s" % path)
    185       if code != 0: return code
    186     targets = " ".join(self.targets)
    187     return _Call("ninja -C %s %s %s" % (path, BUILD_OPTS, targets))
    188 
    189   def RunTests(self):
    190     if not self.tests: return 0
    191     if "ALL" in self.tests:
    192       tests = ""
    193     else:
    194       tests = " ".join(self.tests)
    195     return _Call("tools/run-tests.py --arch=%s --mode=%s %s" %
    196                  (self.arch, self.mode, tests))
    197 
    198 def GetTestBinary(argstring):
    199   for suite in TESTSUITES_TARGETS:
    200     if argstring.startswith(suite): return TESTSUITES_TARGETS[suite]
    201   return None
    202 
    203 class ArgumentParser(object):
    204   def __init__(self):
    205     self.global_targets = set()
    206     self.global_tests = set()
    207     self.global_actions = set()
    208     self.configs = {}
    209 
    210   def PopulateConfigs(self, arches, modes, targets, tests):
    211     for a in arches:
    212       for m in modes:
    213         path = GetPath(a, m)
    214         if path not in self.configs:
    215           self.configs[path] = Config(a, m, targets, tests)
    216         else:
    217           self.configs[path].Extend(targets, tests)
    218 
    219   def ProcessGlobalActions(self):
    220     have_configs = len(self.configs) > 0
    221     for action in self.global_actions:
    222       impact = ACTIONS[action]
    223       if (have_configs):
    224         for c in self.configs:
    225           self.configs[c].Extend(**impact)
    226       else:
    227         self.PopulateConfigs(DEFAULT_ARCHES, DEFAULT_MODES, **impact)
    228 
    229   def ParseArg(self, argstring):
    230     if argstring in ("-h", "--help", "help"):
    231       PrintHelpAndExit()
    232     arches = []
    233     modes = []
    234     targets = []
    235     actions = []
    236     tests = []
    237     words = argstring.split('.')
    238     if len(words) == 1:
    239       word = words[0]
    240       if word in ACTIONS:
    241         self.global_actions.add(word)
    242         return
    243       if word in TARGETS:
    244         self.global_targets.add(word)
    245         return
    246       maybe_target = GetTestBinary(word)
    247       if maybe_target is not None:
    248         self.global_tests.add(word)
    249         self.global_targets.add(maybe_target)
    250         return
    251     for word in words:
    252       if word in ARCHES:
    253         arches.append(word)
    254       elif word in MODES:
    255         modes.append(word)
    256       elif word in TARGETS:
    257         targets.append(word)
    258       elif word in ACTIONS:
    259         actions.append(word)
    260       else:
    261         print("Didn't understand: %s" % word)
    262         sys.exit(1)
    263     # Process actions.
    264     for action in actions:
    265       impact = ACTIONS[action]
    266       targets += impact["targets"]
    267       tests += impact["tests"]
    268     # Fill in defaults for things that weren't specified.
    269     arches = arches or DEFAULT_ARCHES
    270     modes = modes or DEFAULT_MODES
    271     targets = targets or DEFAULT_TARGETS
    272     # Produce configs.
    273     self.PopulateConfigs(arches, modes, targets, tests)
    274 
    275   def ParseArguments(self, argv):
    276     if len(argv) == 0:
    277       PrintHelpAndExit()
    278     for argstring in argv:
    279       self.ParseArg(argstring)
    280     self.ProcessGlobalActions()
    281     for c in self.configs:
    282       self.configs[c].Extend(self.global_targets, self.global_tests)
    283     return self.configs
    284 
    285 def Main(argv):
    286   parser = ArgumentParser()
    287   configs = parser.ParseArguments(argv[1:])
    288   return_code = 0
    289   for c in configs:
    290     return_code += configs[c].Build()
    291   for c in configs:
    292     return_code += configs[c].RunTests()
    293   if return_code == 0:
    294     _Call("notify-send 'Done!' 'V8 compilation finished successfully.'",
    295           silent=True)
    296   else:
    297     _Call("notify-send 'Error!' 'V8 compilation finished with errors.'",
    298           silent=True)
    299   return return_code
    300 
    301 if __name__ == "__main__":
    302   sys.exit(Main(sys.argv))
    303