11cb0ef41Sopenharmony_ci#!/usr/bin/env python
21cb0ef41Sopenharmony_ci# Copyright 2016 the V8 project authors. All rights reserved.
31cb0ef41Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be
41cb0ef41Sopenharmony_ci# found in the LICENSE file.
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ci"""Script to generate V8's gn arguments based on common developer defaults
71cb0ef41Sopenharmony_cior builder configurations.
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ciGoma is used by default if detected. The compiler proxy is assumed to run.
101cb0ef41Sopenharmony_ci
111cb0ef41Sopenharmony_ciThis script can be added to the PATH and be used on other checkouts. It always
121cb0ef41Sopenharmony_ciruns for the checkout nesting the CWD.
131cb0ef41Sopenharmony_ci
141cb0ef41Sopenharmony_ciConfigurations of this script live in infra/mb/mb_config.pyl.
151cb0ef41Sopenharmony_ci
161cb0ef41Sopenharmony_ciAvailable actions are: {gen,list}. Omitting the action defaults to "gen".
171cb0ef41Sopenharmony_ci
181cb0ef41Sopenharmony_ci-------------------------------------------------------------------------------
191cb0ef41Sopenharmony_ci
201cb0ef41Sopenharmony_ciExamples:
211cb0ef41Sopenharmony_ci
221cb0ef41Sopenharmony_ci# Generate the ia32.release config in out.gn/ia32.release.
231cb0ef41Sopenharmony_civ8gen.py ia32.release
241cb0ef41Sopenharmony_ci
251cb0ef41Sopenharmony_ci# Generate into out.gn/foo without goma auto-detect.
261cb0ef41Sopenharmony_civ8gen.py gen -b ia32.release foo --no-goma
271cb0ef41Sopenharmony_ci
281cb0ef41Sopenharmony_ci# Pass additional gn arguments after -- (don't use spaces within gn args).
291cb0ef41Sopenharmony_civ8gen.py ia32.optdebug -- v8_enable_slow_dchecks=true
301cb0ef41Sopenharmony_ci
311cb0ef41Sopenharmony_ci# Generate gn arguments of 'V8 Linux64 - builder' from 'client.v8'. To switch
321cb0ef41Sopenharmony_ci# off goma usage here, the args.gn file must be edited manually.
331cb0ef41Sopenharmony_civ8gen.py -m client.v8 -b 'V8 Linux64 - builder'
341cb0ef41Sopenharmony_ci
351cb0ef41Sopenharmony_ci# Show available configurations.
361cb0ef41Sopenharmony_civ8gen.py list
371cb0ef41Sopenharmony_ci
381cb0ef41Sopenharmony_ci-------------------------------------------------------------------------------
391cb0ef41Sopenharmony_ci"""
401cb0ef41Sopenharmony_ci
411cb0ef41Sopenharmony_ci# for py2/py3 compatibility
421cb0ef41Sopenharmony_cifrom __future__ import print_function
431cb0ef41Sopenharmony_ci
441cb0ef41Sopenharmony_ciimport argparse
451cb0ef41Sopenharmony_ciimport os
461cb0ef41Sopenharmony_ciimport re
471cb0ef41Sopenharmony_ciimport subprocess
481cb0ef41Sopenharmony_ciimport sys
491cb0ef41Sopenharmony_ci
501cb0ef41Sopenharmony_ciCONFIG = os.path.join('infra', 'mb', 'mb_config.pyl')
511cb0ef41Sopenharmony_ciGOMA_DEFAULT = os.path.join(os.path.expanduser("~"), 'goma')
521cb0ef41Sopenharmony_ciOUT_DIR = 'out.gn'
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_ciTOOLS_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
551cb0ef41Sopenharmony_cisys.path.append(os.path.join(TOOLS_PATH, 'mb'))
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_ciimport mb
581cb0ef41Sopenharmony_ci
591cb0ef41Sopenharmony_ci
601cb0ef41Sopenharmony_cidef _sanitize_nonalpha(text):
611cb0ef41Sopenharmony_ci  return re.sub(r'[^a-zA-Z0-9.]', '_', text)
621cb0ef41Sopenharmony_ci
631cb0ef41Sopenharmony_ci
641cb0ef41Sopenharmony_ciclass GenerateGnArgs(object):
651cb0ef41Sopenharmony_ci  def __init__(self, args):
661cb0ef41Sopenharmony_ci    # Split args into this script's arguments and gn args passed to the
671cb0ef41Sopenharmony_ci    # wrapped gn.
681cb0ef41Sopenharmony_ci    index = args.index('--') if '--' in args else len(args)
691cb0ef41Sopenharmony_ci    self._options = self._parse_arguments(args[:index])
701cb0ef41Sopenharmony_ci    self._gn_args = args[index + 1:]
711cb0ef41Sopenharmony_ci
721cb0ef41Sopenharmony_ci  def _parse_arguments(self, args):
731cb0ef41Sopenharmony_ci    self.parser = argparse.ArgumentParser(
741cb0ef41Sopenharmony_ci      description=__doc__,
751cb0ef41Sopenharmony_ci      formatter_class=argparse.RawTextHelpFormatter,
761cb0ef41Sopenharmony_ci    )
771cb0ef41Sopenharmony_ci
781cb0ef41Sopenharmony_ci    def add_common_options(p):
791cb0ef41Sopenharmony_ci      p.add_argument(
801cb0ef41Sopenharmony_ci          '-m', '--master', default='developer_default',
811cb0ef41Sopenharmony_ci          help='config group or master from mb_config.pyl - default: '
821cb0ef41Sopenharmony_ci               'developer_default')
831cb0ef41Sopenharmony_ci      p.add_argument(
841cb0ef41Sopenharmony_ci          '-v', '--verbosity', action='count',
851cb0ef41Sopenharmony_ci          help='print wrapped commands (use -vv to print output of wrapped '
861cb0ef41Sopenharmony_ci               'commands)')
871cb0ef41Sopenharmony_ci
881cb0ef41Sopenharmony_ci    subps = self.parser.add_subparsers()
891cb0ef41Sopenharmony_ci
901cb0ef41Sopenharmony_ci    # Command: gen.
911cb0ef41Sopenharmony_ci    gen_cmd = subps.add_parser(
921cb0ef41Sopenharmony_ci        'gen', help='generate a new set of build files (default)')
931cb0ef41Sopenharmony_ci    gen_cmd.set_defaults(func=self.cmd_gen)
941cb0ef41Sopenharmony_ci    add_common_options(gen_cmd)
951cb0ef41Sopenharmony_ci    gen_cmd.add_argument(
961cb0ef41Sopenharmony_ci        'outdir', nargs='?',
971cb0ef41Sopenharmony_ci        help='optional gn output directory')
981cb0ef41Sopenharmony_ci    gen_cmd.add_argument(
991cb0ef41Sopenharmony_ci        '-b', '--builder',
1001cb0ef41Sopenharmony_ci        help='build configuration or builder name from mb_config.pyl, e.g. '
1011cb0ef41Sopenharmony_ci             'x64.release')
1021cb0ef41Sopenharmony_ci    gen_cmd.add_argument(
1031cb0ef41Sopenharmony_ci        '-p', '--pedantic', action='store_true',
1041cb0ef41Sopenharmony_ci        help='run gn over command-line gn args to catch errors early')
1051cb0ef41Sopenharmony_ci
1061cb0ef41Sopenharmony_ci    goma = gen_cmd.add_mutually_exclusive_group()
1071cb0ef41Sopenharmony_ci    goma.add_argument(
1081cb0ef41Sopenharmony_ci        '-g' , '--goma',
1091cb0ef41Sopenharmony_ci        action='store_true', default=None, dest='goma',
1101cb0ef41Sopenharmony_ci        help='force using goma')
1111cb0ef41Sopenharmony_ci    goma.add_argument(
1121cb0ef41Sopenharmony_ci        '--nogoma', '--no-goma',
1131cb0ef41Sopenharmony_ci        action='store_false', default=None, dest='goma',
1141cb0ef41Sopenharmony_ci        help='don\'t use goma auto detection - goma might still be used if '
1151cb0ef41Sopenharmony_ci             'specified as a gn arg')
1161cb0ef41Sopenharmony_ci
1171cb0ef41Sopenharmony_ci    # Command: list.
1181cb0ef41Sopenharmony_ci    list_cmd = subps.add_parser(
1191cb0ef41Sopenharmony_ci        'list', help='list available configurations')
1201cb0ef41Sopenharmony_ci    list_cmd.set_defaults(func=self.cmd_list)
1211cb0ef41Sopenharmony_ci    add_common_options(list_cmd)
1221cb0ef41Sopenharmony_ci
1231cb0ef41Sopenharmony_ci    # Default to "gen" unless global help is requested.
1241cb0ef41Sopenharmony_ci    if not args or args[0] not in list(subps.choices) + ['-h', '--help']:
1251cb0ef41Sopenharmony_ci      args = ['gen'] + args
1261cb0ef41Sopenharmony_ci
1271cb0ef41Sopenharmony_ci    return self.parser.parse_args(args)
1281cb0ef41Sopenharmony_ci
1291cb0ef41Sopenharmony_ci  def cmd_gen(self):
1301cb0ef41Sopenharmony_ci    if not self._options.outdir and not self._options.builder:
1311cb0ef41Sopenharmony_ci      self.parser.error('please specify either an output directory or '
1321cb0ef41Sopenharmony_ci                        'a builder/config name (-b), e.g. x64.release')
1331cb0ef41Sopenharmony_ci
1341cb0ef41Sopenharmony_ci    if not self._options.outdir:
1351cb0ef41Sopenharmony_ci      # Derive output directory from builder name.
1361cb0ef41Sopenharmony_ci      self._options.outdir = _sanitize_nonalpha(self._options.builder)
1371cb0ef41Sopenharmony_ci    else:
1381cb0ef41Sopenharmony_ci      # Also, if this should work on windows, we might need to use \ where
1391cb0ef41Sopenharmony_ci      # outdir is used as path, while using / if it's used in a gn context.
1401cb0ef41Sopenharmony_ci      if self._options.outdir.startswith('/'):
1411cb0ef41Sopenharmony_ci        self.parser.error(
1421cb0ef41Sopenharmony_ci            'only output directories relative to %s are supported' % OUT_DIR)
1431cb0ef41Sopenharmony_ci
1441cb0ef41Sopenharmony_ci    if not self._options.builder:
1451cb0ef41Sopenharmony_ci      # Derive builder from output directory.
1461cb0ef41Sopenharmony_ci      self._options.builder = self._options.outdir
1471cb0ef41Sopenharmony_ci
1481cb0ef41Sopenharmony_ci    # Check for builder/config in mb config.
1491cb0ef41Sopenharmony_ci    if self._options.builder not in self._mbw.builder_groups[self._options.master]:
1501cb0ef41Sopenharmony_ci      print('%s does not exist in %s for %s' % (
1511cb0ef41Sopenharmony_ci          self._options.builder, CONFIG, self._options.master))
1521cb0ef41Sopenharmony_ci      return 1
1531cb0ef41Sopenharmony_ci
1541cb0ef41Sopenharmony_ci    # TODO(machenbach): Check if the requested configurations has switched to
1551cb0ef41Sopenharmony_ci    # gn at all.
1561cb0ef41Sopenharmony_ci
1571cb0ef41Sopenharmony_ci    # The directories are separated with slashes in a gn context (platform
1581cb0ef41Sopenharmony_ci    # independent).
1591cb0ef41Sopenharmony_ci    gn_outdir = '/'.join([OUT_DIR, self._options.outdir])
1601cb0ef41Sopenharmony_ci
1611cb0ef41Sopenharmony_ci    # Call MB to generate the basic configuration.
1621cb0ef41Sopenharmony_ci    self._call_cmd([
1631cb0ef41Sopenharmony_ci      sys.executable,
1641cb0ef41Sopenharmony_ci      '-u', os.path.join('tools', 'mb', 'mb.py'),
1651cb0ef41Sopenharmony_ci      'gen',
1661cb0ef41Sopenharmony_ci      '-f', CONFIG,
1671cb0ef41Sopenharmony_ci      '-m', self._options.master,
1681cb0ef41Sopenharmony_ci      '-b', self._options.builder,
1691cb0ef41Sopenharmony_ci      gn_outdir,
1701cb0ef41Sopenharmony_ci    ])
1711cb0ef41Sopenharmony_ci
1721cb0ef41Sopenharmony_ci    # Handle extra gn arguments.
1731cb0ef41Sopenharmony_ci    gn_args_path = os.path.join(OUT_DIR, self._options.outdir, 'args.gn')
1741cb0ef41Sopenharmony_ci
1751cb0ef41Sopenharmony_ci    # Append command-line args.
1761cb0ef41Sopenharmony_ci    modified = self._append_gn_args(
1771cb0ef41Sopenharmony_ci        'command-line', gn_args_path, '\n'.join(self._gn_args))
1781cb0ef41Sopenharmony_ci
1791cb0ef41Sopenharmony_ci    # Append goma args.
1801cb0ef41Sopenharmony_ci    # TODO(machenbach): We currently can't remove existing goma args from the
1811cb0ef41Sopenharmony_ci    # original config. E.g. to build like a bot that uses goma, but switch
1821cb0ef41Sopenharmony_ci    # goma off.
1831cb0ef41Sopenharmony_ci    modified |= self._append_gn_args(
1841cb0ef41Sopenharmony_ci        'goma', gn_args_path, self._goma_args)
1851cb0ef41Sopenharmony_ci
1861cb0ef41Sopenharmony_ci    # Regenerate ninja files to check for errors in the additional gn args.
1871cb0ef41Sopenharmony_ci    if modified and self._options.pedantic:
1881cb0ef41Sopenharmony_ci      self._call_cmd(['gn', 'gen', gn_outdir])
1891cb0ef41Sopenharmony_ci    return 0
1901cb0ef41Sopenharmony_ci
1911cb0ef41Sopenharmony_ci  def cmd_list(self):
1921cb0ef41Sopenharmony_ci    print('\n'.join(sorted(self._mbw.builder_groups[self._options.master])))
1931cb0ef41Sopenharmony_ci    return 0
1941cb0ef41Sopenharmony_ci
1951cb0ef41Sopenharmony_ci  def verbose_print_1(self, text):
1961cb0ef41Sopenharmony_ci    if self._options.verbosity and self._options.verbosity >= 1:
1971cb0ef41Sopenharmony_ci      print('#' * 80)
1981cb0ef41Sopenharmony_ci      print(text)
1991cb0ef41Sopenharmony_ci
2001cb0ef41Sopenharmony_ci  def verbose_print_2(self, text):
2011cb0ef41Sopenharmony_ci    if self._options.verbosity and self._options.verbosity >= 2:
2021cb0ef41Sopenharmony_ci      indent = ' ' * 2
2031cb0ef41Sopenharmony_ci      for l in text.splitlines():
2041cb0ef41Sopenharmony_ci        if type(l) == bytes:
2051cb0ef41Sopenharmony_ci          l = l.decode()
2061cb0ef41Sopenharmony_ci        print(indent + l)
2071cb0ef41Sopenharmony_ci
2081cb0ef41Sopenharmony_ci  def _call_cmd(self, args):
2091cb0ef41Sopenharmony_ci    self.verbose_print_1(' '.join(args))
2101cb0ef41Sopenharmony_ci    try:
2111cb0ef41Sopenharmony_ci      output = subprocess.check_output(
2121cb0ef41Sopenharmony_ci        args=args,
2131cb0ef41Sopenharmony_ci        stderr=subprocess.STDOUT,
2141cb0ef41Sopenharmony_ci      )
2151cb0ef41Sopenharmony_ci      self.verbose_print_2(output)
2161cb0ef41Sopenharmony_ci    except subprocess.CalledProcessError as e:
2171cb0ef41Sopenharmony_ci      self.verbose_print_2(e.output)
2181cb0ef41Sopenharmony_ci      raise
2191cb0ef41Sopenharmony_ci
2201cb0ef41Sopenharmony_ci  def _find_work_dir(self, path):
2211cb0ef41Sopenharmony_ci    """Find the closest v8 root to `path`."""
2221cb0ef41Sopenharmony_ci    if os.path.exists(os.path.join(path, 'tools', 'dev', 'v8gen.py')):
2231cb0ef41Sopenharmony_ci      # Approximate the v8 root dir by a folder where this script exists
2241cb0ef41Sopenharmony_ci      # in the expected place.
2251cb0ef41Sopenharmony_ci      return path
2261cb0ef41Sopenharmony_ci    elif os.path.dirname(path) == path:
2271cb0ef41Sopenharmony_ci      raise Exception(
2281cb0ef41Sopenharmony_ci          'This appears to not be called from a recent v8 checkout')
2291cb0ef41Sopenharmony_ci    else:
2301cb0ef41Sopenharmony_ci      return self._find_work_dir(os.path.dirname(path))
2311cb0ef41Sopenharmony_ci
2321cb0ef41Sopenharmony_ci  @property
2331cb0ef41Sopenharmony_ci  def _goma_dir(self):
2341cb0ef41Sopenharmony_ci    return os.path.normpath(os.environ.get('GOMA_DIR') or GOMA_DEFAULT)
2351cb0ef41Sopenharmony_ci
2361cb0ef41Sopenharmony_ci  @property
2371cb0ef41Sopenharmony_ci  def _need_goma_dir(self):
2381cb0ef41Sopenharmony_ci    return self._goma_dir != GOMA_DEFAULT
2391cb0ef41Sopenharmony_ci
2401cb0ef41Sopenharmony_ci  @property
2411cb0ef41Sopenharmony_ci  def _use_goma(self):
2421cb0ef41Sopenharmony_ci    if self._options.goma is None:
2431cb0ef41Sopenharmony_ci      # Auto-detect.
2441cb0ef41Sopenharmony_ci      return os.path.exists(self._goma_dir) and os.path.isdir(self._goma_dir)
2451cb0ef41Sopenharmony_ci    else:
2461cb0ef41Sopenharmony_ci      return self._options.goma
2471cb0ef41Sopenharmony_ci
2481cb0ef41Sopenharmony_ci  @property
2491cb0ef41Sopenharmony_ci  def _goma_args(self):
2501cb0ef41Sopenharmony_ci    """Gn args for using goma."""
2511cb0ef41Sopenharmony_ci    # Specify goma args if we want to use goma and if goma isn't specified
2521cb0ef41Sopenharmony_ci    # via command line already. The command-line always has precedence over
2531cb0ef41Sopenharmony_ci    # any other specification.
2541cb0ef41Sopenharmony_ci    if (self._use_goma and
2551cb0ef41Sopenharmony_ci        not any(re.match(r'use_goma\s*=.*', x) for x in self._gn_args)):
2561cb0ef41Sopenharmony_ci      if self._need_goma_dir:
2571cb0ef41Sopenharmony_ci        return 'use_goma=true\ngoma_dir="%s"' % self._goma_dir
2581cb0ef41Sopenharmony_ci      else:
2591cb0ef41Sopenharmony_ci        return 'use_goma=true'
2601cb0ef41Sopenharmony_ci    else:
2611cb0ef41Sopenharmony_ci      return ''
2621cb0ef41Sopenharmony_ci
2631cb0ef41Sopenharmony_ci  def _append_gn_args(self, type, gn_args_path, more_gn_args):
2641cb0ef41Sopenharmony_ci    """Append extra gn arguments to the generated args.gn file."""
2651cb0ef41Sopenharmony_ci    if not more_gn_args:
2661cb0ef41Sopenharmony_ci      return False
2671cb0ef41Sopenharmony_ci    self.verbose_print_1('Appending """\n%s\n""" to %s.' % (
2681cb0ef41Sopenharmony_ci        more_gn_args, os.path.abspath(gn_args_path)))
2691cb0ef41Sopenharmony_ci    with open(gn_args_path, 'a') as f:
2701cb0ef41Sopenharmony_ci      f.write('\n# Additional %s args:\n' % type)
2711cb0ef41Sopenharmony_ci      f.write(more_gn_args)
2721cb0ef41Sopenharmony_ci      f.write('\n')
2731cb0ef41Sopenharmony_ci
2741cb0ef41Sopenharmony_ci    # Artificially increment modification time as our modifications happen too
2751cb0ef41Sopenharmony_ci    # fast. This makes sure that gn is properly rebuilding the ninja files.
2761cb0ef41Sopenharmony_ci    mtime = os.path.getmtime(gn_args_path) + 1
2771cb0ef41Sopenharmony_ci    with open(gn_args_path, 'a'):
2781cb0ef41Sopenharmony_ci      os.utime(gn_args_path, (mtime, mtime))
2791cb0ef41Sopenharmony_ci
2801cb0ef41Sopenharmony_ci    return True
2811cb0ef41Sopenharmony_ci
2821cb0ef41Sopenharmony_ci  def main(self):
2831cb0ef41Sopenharmony_ci    # Always operate relative to the base directory for better relative-path
2841cb0ef41Sopenharmony_ci    # handling. This script can be used in any v8 checkout.
2851cb0ef41Sopenharmony_ci    workdir = self._find_work_dir(os.getcwd())
2861cb0ef41Sopenharmony_ci    if workdir != os.getcwd():
2871cb0ef41Sopenharmony_ci      self.verbose_print_1('cd ' + workdir)
2881cb0ef41Sopenharmony_ci      os.chdir(workdir)
2891cb0ef41Sopenharmony_ci
2901cb0ef41Sopenharmony_ci    # Initialize MB as a library.
2911cb0ef41Sopenharmony_ci    self._mbw = mb.MetaBuildWrapper()
2921cb0ef41Sopenharmony_ci
2931cb0ef41Sopenharmony_ci    # TODO(machenbach): Factor out common methods independent of mb arguments.
2941cb0ef41Sopenharmony_ci    self._mbw.ParseArgs(['lookup', '-f', CONFIG])
2951cb0ef41Sopenharmony_ci    self._mbw.ReadConfigFile()
2961cb0ef41Sopenharmony_ci
2971cb0ef41Sopenharmony_ci    if not self._options.master in self._mbw.builder_groups:
2981cb0ef41Sopenharmony_ci      print('%s not found in %s\n' % (self._options.master, CONFIG))
2991cb0ef41Sopenharmony_ci      print('Choose one of:\n%s\n' % (
3001cb0ef41Sopenharmony_ci          '\n'.join(sorted(self._mbw.builder_groups.keys()))))
3011cb0ef41Sopenharmony_ci      return 1
3021cb0ef41Sopenharmony_ci
3031cb0ef41Sopenharmony_ci    return self._options.func()
3041cb0ef41Sopenharmony_ci
3051cb0ef41Sopenharmony_ci
3061cb0ef41Sopenharmony_ciif __name__ == "__main__":
3071cb0ef41Sopenharmony_ci  gen = GenerateGnArgs(sys.argv[1:])
3081cb0ef41Sopenharmony_ci  try:
3091cb0ef41Sopenharmony_ci    sys.exit(gen.main())
3101cb0ef41Sopenharmony_ci  except Exception:
3111cb0ef41Sopenharmony_ci    if not gen._options.verbosity or gen._options.verbosity < 2:
3121cb0ef41Sopenharmony_ci      print ('\nHint: You can raise verbosity (-vv) to see the output of '
3131cb0ef41Sopenharmony_ci             'failed commands.\n')
3141cb0ef41Sopenharmony_ci    raise
315