1# Copyright 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Module to hold the Target plugin."""
5
6import operator
7import re
8
9import cr
10import cr.base.context
11
12DEFAULT = cr.Config.From(
13    CR_DEFAULT_TARGET='chrome',
14)
15
16
17class Target(cr.base.context.Context, cr.AutoExport):
18  """Base class for implementing cr targets.
19
20  A target is something that can be built and run.
21  """
22
23  # The default base priority
24  PRIORITY = 0
25  # The default pattern used to try to detect whether a target is a test and
26  # should use the test runner.
27  TEST_PATTERN = re.compile('tests?$')
28  # The special "test type" that means it's not a test.
29  NOT_A_TEST = 'no'
30  # The default choice for the type of test when it can't be determined.
31  NORMAL_TEST = 'gtest'
32  # TODO(iancottrell): support the other test types
33  TEST_TYPES = [NOT_A_TEST, NORMAL_TEST]
34
35  def  __init__(self, target_name):
36    super(Target, self).__init__(target_name)
37    test_type = None
38    if self.TEST_PATTERN.search(target_name):
39      test_type = self.NORMAL_TEST
40    config = cr.Config('DEFAULTS').From(
41        CR_TARGET=target_name,
42        CR_TARGET_NAME='{CR_TARGET}',
43        CR_BUILD_TARGET=cr.Config.Optional(
44            '{CR_TARGET}{CR_TARGET_SUFFIX}', '{CR_TARGET}'),
45        CR_RUN_ARGUMENTS='',
46        CR_TEST_TYPE=test_type,
47    )
48    self._data = cr.context.data
49    self.AddChildren(config, cr.context)
50    if hasattr(self, 'CONFIG'):
51      self.AddChild(self.CONFIG)
52    if not self.valid:
53      self.Set(CR_TARGET_SUFFIX='')
54    self.test_type = self.Find('CR_TEST_TYPE')
55    self.target_name = self.Find('CR_TARGET_NAME')
56
57  @property
58  def build_target(self):
59    return self.Get('CR_BUILD_TARGET')
60
61  @property
62  def valid(self):
63    return cr.Builder.IsTarget(self.build_target)
64
65  @property
66  def is_test(self):
67    return self.test_type and self.test_type != self.NOT_A_TEST
68
69  @classmethod
70  def AddArguments(cls, command, parser, allow_multiple=False):
71    nargs = '?'
72    help_string = 'The target to {0}'
73    if allow_multiple:
74      nargs = '*'
75      help_string = 'The target(s) to {0}'
76    parser.add_argument(
77        '_targets', metavar='target',
78        help=help_string.format(command.name),
79        nargs=nargs
80    )
81
82  @classmethod
83  def AllTargets(cls):
84    yield cls
85    for child in cls.__subclasses__():
86      for t in child.AllTargets():
87        yield t
88
89  @classmethod
90  def CreateTarget(cls, target_name):
91    """Attempts to build a target by name.
92
93    This searches the set of installed targets in priority order to see if any
94    of them are willing to handle the supplied name.
95    If a target cannot be found, the program will be aborted.
96    Args:
97      target_name: The name of the target we are searching for.
98    Returns:
99      The target that matched.
100    """
101    target_clses = sorted(
102        cls.AllTargets(),
103        key=operator.attrgetter('PRIORITY'),
104        reverse=True
105    )
106    for handler in target_clses:
107      target = handler.Build(target_name)
108      if target:
109        if not target.valid:
110          print 'Invalid target {0} as {1}'.format(
111              target_name, target.build_target)
112          guesses = cr.Builder.GuessTargets(target_name)
113          if guesses:
114            print 'Did you mean {0}?'.format(
115                ', '.join(guesses[:-1]) + ' or ' + guesses[-1]
116                if len(guesses) > 1 else guesses[0])
117          exit(1)
118        return target
119    print 'Unknown target {0}'.format(target_name)
120    exit(1)
121
122  @classmethod
123  def GetTargets(cls):
124    target_names = getattr(cr.context.args, '_targets', None)
125    if not target_names:
126      target_names = [cr.context.Get('CR_DEFAULT_TARGET')]
127    elif hasattr(target_names, 'swapcase'):
128      # deal with the single target case
129      target_names = [target_names]
130    return [cls.CreateTarget(target_name)
131            for target_name in target_names]
132
133  @classmethod
134  def Build(cls, target_name):
135    return cls(target_name)
136
137
138class NamedTarget(Target):
139  """A base class for explicit named targets.
140
141  Only matches a target if the name is an exact match.
142  Up it's priority to come ahead of general purpose rule matches.
143  """
144  NAME = None
145  PRIORITY = Target.PRIORITY + 1
146
147  @classmethod
148  def Build(cls, target_name):
149    try:
150      if target_name == cls.NAME:
151        return cls(target_name)
152    except AttributeError:
153      pass
154    return None
155