flags.py revision f2a3ef46f75d2196a93d3ed27f4d1fcf22b54fbe
1# Copyright (c) 2013 The Chromium OS 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"""Manage bundles of flags used for the optimizing of ChromeOS.
5
6Part of the Chrome build flags optimization.
7
8The content of this module is adapted from the Trakhelp JVM project. This module
9contains the basic Class Flag and the Class FlagSet. The core abstractions are:
10
11The class Flag, which takes a domain specific language describing how to fill
12the flags with values.
13
14The class FlagSet, which contains a number of flags and can create new FlagSets
15by mixing with other FlagSets.
16
17The Flag DSL works by replacing value ranges in [x-y] with numbers in the range
18x through y.
19
20Examples:
21  "foo[0-9]bar" will expand to e.g. "foo5bar".
22"""
23
24__author__ = 'yuhenglong@google.com (Yuheng Long)'
25
26import random
27import re
28
29#
30# This matches a [...] group in the internal representation for a flag
31# specification, and is used in "filling out" flags - placing values inside
32# the flag_spec.  The internal flag_spec format is like "foo[0]", with
33# values filled out like 5; this would be transformed by
34# FormattedForUse() into "foo5".
35_FLAG_FILLOUT_VALUE_RE = re.compile(r'\[([^\]]*)\]')
36
37# This matches a numeric flag flag=[start-end].
38rx = re.compile(r'\[(?P<start>\d+)-(?P<end>\d+)\]')
39
40
41# Search the numeric flag pattern.
42def Search(spec):
43  return rx.search(spec)
44
45
46class NoSuchFileError(Exception):
47  """Define an Exception class for user providing invalid input file."""
48  pass
49
50
51def ReadConf(file_name):
52  """Parse the configuration file.
53
54  The configuration contains one flag specification in each line.
55
56  Args:
57    file_name: The name of the configuration file.
58
59  Returns:
60    A list of specs in the configuration file.
61
62  Raises:
63    NoSuchFileError: The caller should provide a valid configuration file.
64  """
65
66  with open(file_name, 'r') as input_file:
67    lines = input_file.readlines()
68
69    return sorted([line.strip() for line in lines if line.strip()])
70
71  raise NoSuchFileError()
72
73
74class Flag(object):
75  """A class representing a particular command line flag argument.
76
77  The Flag consists of two parts: The spec and the value.
78  The spec is a definition of the following form: a string with escaped
79  sequences of the form [<start>-<end>] where start and end is an positive
80  integer for a fillable value.
81
82  An example of a spec is "foo[0-9]".
83  There are two kinds of flags, boolean flag and numeric flags. Boolean flags
84  can either be turned on or off, which numeric flags can have different
85  positive integer values. For example, -finline-limit=[1-1000] is a numeric
86  flag and -ftree-vectorize is a boolean flag.
87
88  A (boolean/numeric) flag is not turned on if it is not selected in the
89  FlagSet.
90  """
91
92  def __init__(self, spec, value=-1):
93    self._spec = spec
94
95    # If the value is not specified, generate a random value to use.
96    if value == -1:
97      # If creating a boolean flag, the value will be 0.
98      value = 0
99
100      # Parse the spec's expression for the flag value's numeric range.
101      numeric_flag_match = Search(spec)
102
103      # If this is a numeric flag, a value is chosen within start and end, start
104      # inclusive and end exclusive.
105      if numeric_flag_match:
106        start = int(numeric_flag_match.group('start'))
107        end = int(numeric_flag_match.group('end'))
108
109        assert start < end
110        value = random.randint(start, end)
111
112    self._value = value
113
114  def __eq__(self, other):
115    if isinstance(other, Flag):
116      return self._spec == other.GetSpec() and self._value == other.GetValue()
117    return False
118
119  def __hash__(self):
120    return hash(self._spec) + self._value
121
122  def GetValue(self):
123    """Get the value for this flag.
124
125    Returns:
126     The value.
127    """
128
129    return self._value
130
131  def GetSpec(self):
132    """Get the spec for this flag.
133
134    Returns:
135     The spec.
136    """
137
138    return self._spec
139
140  def FormattedForUse(self):
141    """Calculate the combination of flag_spec and values.
142
143    For e.g. the flag_spec 'foo[0-9]' and the value equals to 5, this will
144    return 'foo5'. The filled out version of the flag is the text string you use
145    when you actually want to pass the flag to some binary.
146
147    Returns:
148      A string that represent the filled out flag, e.g. the flag with the
149      FlagSpec '-X[0-9]Y' and value equals to 5 would return '-X5Y'.
150    """
151
152    return _FLAG_FILLOUT_VALUE_RE.sub(str(self._value), self._spec)
153
154
155class FlagSet(object):
156  """A dictionary of Flag objects.
157
158  The flags dictionary stores the spec and flag pair.
159  """
160
161  def __init__(self, flag_array):
162    # Store the flags as a dictionary mapping of spec -> flag object
163    self._flags = dict([(flag.GetSpec(), flag) for flag in flag_array])
164
165  def __eq__(self, other):
166    return isinstance(other, FlagSet) and self._flags == other.GetFlags()
167
168  def __hash__(self):
169    return sum([hash(flag) for flag in self._flags.values()])
170
171  def __getitem__(self, flag_spec):
172    """Get flag with a particular flag_spec.
173
174    Args:
175      flag_spec: The flag_spec to find.
176
177    Returns:
178      A flag.
179    """
180
181    return self._flags[flag_spec]
182
183  def __contains__(self, flag_spec):
184    return self._flags.has_key(flag_spec)
185
186  def GetFlags(self):
187    return self._flags
188
189  def FormattedForUse(self):
190    """Format this for use in an application.
191
192    Returns:
193      A list of flags, sorted alphabetically and filled in with the values
194      for each flag.
195    """
196
197    return sorted([f.FormattedForUse() for f in self._flags.values()])
198