Home | History | Annotate | Download | only in bestflags
      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 
      6 Part of the Chrome build flags optimization.
      7 
      8 The content of this module is adapted from the Trakhelp JVM project. This module
      9 contains the basic Class Flag and the Class FlagSet. The core abstractions are:
     10 
     11 The class Flag, which takes a domain specific language describing how to fill
     12 the flags with values.
     13 
     14 The class FlagSet, which contains a number of flags and can create new FlagSets
     15 by mixing with other FlagSets.
     16 
     17 The Flag DSL works by replacing value ranges in [x-y] with numbers in the range
     18 x through y.
     19 
     20 Examples:
     21   "foo[0-9]bar" will expand to e.g. "foo5bar".
     22 """
     23 
     24 __author__ = 'yuhenglong (at] google.com (Yuheng Long)'
     25 
     26 import random
     27 import 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].
     38 rx = re.compile(r'\[(?P<start>\d+)-(?P<end>\d+)\]')
     39 
     40 
     41 # Search the numeric flag pattern.
     42 def Search(spec):
     43   return rx.search(spec)
     44 
     45 
     46 class NoSuchFileError(Exception):
     47   """Define an Exception class for user providing invalid input file."""
     48   pass
     49 
     50 
     51 def 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 
     74 class 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 
    155 class 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