Home | History | Annotate | Download | only in util
      1 #!/usr/bin/env python
      2 # Copyright 2014 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """
      7 version.py -- Chromium version string substitution utility.
      8 """
      9 
     10 import argparse
     11 import os
     12 import sys
     13 
     14 
     15 def fetch_values_from_file(values_dict, file_name):
     16   """
     17   Fetches KEYWORD=VALUE settings from the specified file.
     18 
     19   Everything to the left of the first '=' is the keyword,
     20   everything to the right is the value.  No stripping of
     21   white space, so beware.
     22 
     23   The file must exist, otherwise you get the Python exception from open().
     24   """
     25   for line in open(file_name, 'r').readlines():
     26     key, val = line.rstrip('\r\n').split('=', 1)
     27     values_dict[key] = val
     28 
     29 
     30 def fetch_values(file_list):
     31   """
     32   Returns a dictionary of values to be used for substitution, populating
     33   the dictionary with KEYWORD=VALUE settings from the files in 'file_list'.
     34 
     35   Explicitly adds the following value from internal calculations:
     36 
     37     OFFICIAL_BUILD
     38   """
     39   CHROME_BUILD_TYPE = os.environ.get('CHROME_BUILD_TYPE')
     40   if CHROME_BUILD_TYPE == '_official':
     41     official_build = '1'
     42   else:
     43     official_build = '0'
     44 
     45   values = dict(
     46     OFFICIAL_BUILD = official_build,
     47   )
     48 
     49   for file_name in file_list:
     50     fetch_values_from_file(values, file_name)
     51 
     52   return values
     53 
     54 
     55 def subst_template(contents, values):
     56   """
     57   Returns the template with substituted values from the specified dictionary.
     58 
     59   Keywords to be substituted are surrounded by '@':  @KEYWORD@.
     60 
     61   No attempt is made to avoid recursive substitution.  The order
     62   of evaluation is random based on the order of the keywords returned
     63   by the Python dictionary.  So do NOT substitute a value that
     64   contains any @KEYWORD@ strings expecting them to be recursively
     65   substituted, okay?
     66   """
     67   for key, val in values.iteritems():
     68     try:
     69       contents = contents.replace('@' + key + '@', val)
     70     except TypeError:
     71       print repr(key), repr(val)
     72   return contents
     73 
     74 
     75 def subst_file(file_name, values):
     76   """
     77   Returns the contents of the specified file_name with substituted
     78   values from the specified dictionary.
     79 
     80   This is like subst_template, except it operates on a file.
     81   """
     82   template = open(file_name, 'r').read()
     83   return subst_template(template, values);
     84 
     85 
     86 def write_if_changed(file_name, contents):
     87   """
     88   Writes the specified contents to the specified file_name
     89   iff the contents are different than the current contents.
     90   """
     91   try:
     92     old_contents = open(file_name, 'r').read()
     93   except EnvironmentError:
     94     pass
     95   else:
     96     if contents == old_contents:
     97       return
     98     os.unlink(file_name)
     99   open(file_name, 'w').write(contents)
    100 
    101 
    102 def main():
    103   parser = argparse.ArgumentParser()
    104   parser.add_argument('-f', '--file', action='append', default=[],
    105                       help='Read variables from FILE.')
    106   parser.add_argument('-i', '--input', default=None,
    107                       help='Read strings to substitute from FILE.')
    108   parser.add_argument('-o', '--output', default=None,
    109                       help='Write substituted strings to FILE.')
    110   parser.add_argument('-t', '--template', default=None,
    111                       help='Use TEMPLATE as the strings to substitute.')
    112   parser.add_argument('-e', '--eval', action='append', default=[],
    113                       help='Evaluate VAL after reading variables. Can be used '
    114                            'to synthesize variables. e.g. -e \'PATCH_HI=int('
    115                            'PATCH)/256.')
    116   parser.add_argument('args', nargs=argparse.REMAINDER,
    117                       help='For compatibility: INPUT and OUTPUT can be '
    118                            'passed as positional arguments.')
    119   options = parser.parse_args()
    120 
    121   evals = {}
    122   for expression in options.eval:
    123     try:
    124       evals.update(dict([expression.split('=', 1)]))
    125     except ValueError:
    126       parser.error('-e requires VAR=VAL')
    127 
    128   # Compatibility with old versions that considered the first two positional
    129   # arguments shorthands for --input and --output.
    130   while len(options.args) and (options.input is None or \
    131                                options.output is None):
    132     if options.input is None:
    133       options.input = options.args.pop(0)
    134     elif options.output is None:
    135       options.output = options.args.pop(0)
    136   if options.args:
    137     parser.error('Unexpected arguments: %r' % options.args)
    138 
    139   values = fetch_values(options.file)
    140   for key, val in evals.iteritems():
    141     values[key] = str(eval(val, globals(), values))
    142 
    143   if options.template is not None:
    144     contents = subst_template(options.template, values)
    145   elif options.input:
    146     contents = subst_file(options.input, values)
    147   else:
    148     # Generate a default set of version information.
    149     contents = """MAJOR=%(MAJOR)s
    150 MINOR=%(MINOR)s
    151 BUILD=%(BUILD)s
    152 PATCH=%(PATCH)s
    153 LASTCHANGE=%(LASTCHANGE)s
    154 OFFICIAL_BUILD=%(OFFICIAL_BUILD)s
    155 """ % values
    156 
    157   if options.output is not None:
    158     write_if_changed(options.output, contents)
    159   else:
    160     print contents
    161 
    162   return 0
    163 
    164 
    165 if __name__ == '__main__':
    166   sys.exit(main())
    167