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