chromium/tools/cr/cr/targets/target.py

# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module to hold the Target plugin."""

from __future__ import print_function

import operator
import re

import cr
import cr.base.context

DEFAULT = cr.Config.From(
    CR_DEFAULT_TARGET='chrome',
)


class Target(cr.base.context.Context, cr.AutoExport):
  """Base class for implementing cr targets.

  A target is something that can be built and run.
  """

  # The default base priority
  PRIORITY = 0
  # The default pattern used to try to detect whether a target is a test and
  # should use the test runner.
  TEST_PATTERN = re.compile('tests?$')
  # The special "test type" that means it's not a test.
  NOT_A_TEST = 'no'
  # The default choice for the type of test when it can't be determined.
  NORMAL_TEST = 'gtest'
  INSTRUMENTATION_TEST = 'instrumentation'
  # TODO(iancottrell): support the other test types
  TEST_TYPES = [NOT_A_TEST, NORMAL_TEST, INSTRUMENTATION_TEST]

  def  __init__(self, target_name):
    super(Target, self).__init__(target_name)
    test_type = None
    if self.TEST_PATTERN.search(target_name):
      test_type = self.NORMAL_TEST
    config = cr.Config('DEFAULTS').From(
        CR_TARGET=target_name,
        CR_TARGET_NAME='{CR_TARGET}',
        CR_BUILD_TARGET=cr.Config.Optional(
            '{CR_TARGET}{CR_TARGET_SUFFIX}', '{CR_TARGET}'),
        CR_RUN_ARGUMENTS='',
        CR_TEST_TYPE=test_type,
        CR_RUN_DEPENDENCIES=[],
    )
    self._data = cr.context.data
    self.AddChildren(config, cr.context)
    if hasattr(self, 'CONFIG'):
      self.AddChild(self.CONFIG)
    if not self.valid:
      self.Set(CR_TARGET_SUFFIX='')
    self.test_type = self.Find('CR_TEST_TYPE')
    self.target_name = self.Find('CR_TARGET_NAME')

  def GetRunDependencies(self):
    return map(Target.CreateTarget, self.Get('CR_RUN_DEPENDENCIES'))

  @property
  def build_target(self):
    return self.Get('CR_BUILD_TARGET')

  @property
  def valid(self):
    return cr.Builder.IsTarget(self.build_target)

  @property
  def is_test(self):
    return self.test_type and self.test_type != self.NOT_A_TEST

  @classmethod
  def AddArguments(cls, command, parser, allow_multiple=False):
    nargs = '?'
    help_string = 'The target to {0}'
    if allow_multiple:
      nargs = '*'
      help_string = 'The target(s) to {0}'
    parser.add_argument(
        '_targets', metavar='target',
        help=help_string.format(command.name),
        nargs=nargs
    )

  @classmethod
  def AllTargets(cls):
    yield cls
    for child in cls.__subclasses__():
      for t in child.AllTargets():
        yield t

  @classmethod
  def CreateTarget(cls, target_name):
    """Attempts to build a target by name.

    This searches the set of installed targets in priority order to see if any
    of them are willing to handle the supplied name.
    If a target cannot be found, the program will be aborted.
    Args:
      target_name: The name of the target we are searching for.
    Returns:
      The target that matched.
    """
    target_clses = sorted(
        cls.AllTargets(),
        key=operator.attrgetter('PRIORITY'),
        reverse=True
    )
    for handler in target_clses:
      target = handler.Build(target_name)
      if target:
        if not target.valid:
          print('Invalid target {0} as {1}'.format(target_name,
                                                   target.build_target))
          guesses = cr.Builder.GuessTargets(target_name)
          if guesses:
            print('Did you mean {0}?'
                  .format(', '.join(guesses[:-1]) + ' or ' +
                          guesses[-1] if len(guesses) > 1 else guesses[0]))
          exit(1)
        return target
    print('Unknown target {0}'.format(target_name))
    exit(1)

  @classmethod
  def GetTargets(cls):
    target_names = getattr(cr.context.args, '_targets', None)
    if not target_names:
      target_names = [cr.context.Get('CR_DEFAULT_TARGET')]
    elif hasattr(target_names, 'swapcase'):
      # deal with the single target case
      target_names = [target_names]
    return [cls.CreateTarget(target_name)
            for target_name in target_names]

  @classmethod
  def Build(cls, target_name):
    return cls(target_name)


class NamedTarget(Target):
  """A base class for explicit named targets.

  Only matches a target if the name is an exact match.
  Up it's priority to come ahead of general purpose rule matches.
  """
  NAME = None
  PRIORITY = Target.PRIORITY + 1

  @classmethod
  def Build(cls, target_name):
    try:
      if target_name == cls.NAME:
        return cls(target_name)
    except AttributeError:
      pass
    return None