tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

version.py (8556B)


      1 #!/usr/bin/env python3
      2 # Copyright 2014 The Chromium Authors
      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 
     11 import argparse
     12 import os
     13 import stat
     14 import sys
     15 
     16 import android_chrome_version
     17 
     18 
     19 def FetchValuesFromFile(values_dict, file_name):
     20  """
     21  Fetches KEYWORD=VALUE settings from the specified file.
     22 
     23  Everything to the left of the first '=' is the keyword,
     24  everything to the right is the value.  No stripping of
     25  white space, so beware.
     26 
     27  The file must exist, otherwise you get the Python exception from open().
     28  """
     29  with open(file_name, 'r') as f:
     30    for line in f.readlines():
     31      key, val = line.rstrip('\r\n').split('=', 1)
     32      values_dict[key] = val
     33 
     34 
     35 def FetchValues(file_list, is_official_build=None):
     36  """
     37  Returns a dictionary of values to be used for substitution.
     38 
     39  Populates the dictionary with KEYWORD=VALUE settings from the files in
     40  'file_list'.
     41 
     42  Explicitly adds the following value from internal calculations:
     43 
     44    OFFICIAL_BUILD
     45  """
     46  CHROME_BUILD_TYPE = os.environ.get('CHROME_BUILD_TYPE')
     47  if CHROME_BUILD_TYPE == '_official' or is_official_build:
     48    official_build = '1'
     49  else:
     50    official_build = '0'
     51 
     52  values = dict(
     53    OFFICIAL_BUILD = official_build,
     54  )
     55 
     56  for file_name in file_list:
     57    FetchValuesFromFile(values, file_name)
     58 
     59  script_dirname = os.path.dirname(os.path.realpath(__file__))
     60  if official_build == '1':
     61    lastchange_filename = os.path.join(script_dirname, "LASTCHANGE")
     62  else:
     63    lastchange_filename = os.path.join(script_dirname, "LASTCHANGE.dummy")
     64  lastchange_values = {}
     65  FetchValuesFromFile(lastchange_values, lastchange_filename)
     66 
     67  for placeholder_key, placeholder_value in values.items():
     68    values[placeholder_key] = SubstTemplate(placeholder_value,
     69                                            lastchange_values)
     70 
     71  return values
     72 
     73 
     74 def SubstTemplate(contents, values):
     75  """
     76  Returns the template with substituted values from the specified dictionary.
     77 
     78  Keywords to be substituted are surrounded by '@':  @KEYWORD@.
     79 
     80  No attempt is made to avoid recursive substitution.  The order
     81  of evaluation is random based on the order of the keywords returned
     82  by the Python dictionary.  So do NOT substitute a value that
     83  contains any @KEYWORD@ strings expecting them to be recursively
     84  substituted, okay?
     85  """
     86  for key, val in values.items():
     87    try:
     88      contents = contents.replace('@' + key + '@', val)
     89    except TypeError:
     90      print(repr(key), repr(val))
     91  return contents
     92 
     93 
     94 def SubstFile(file_name, values):
     95  """
     96  Returns the contents of the specified file_name with substituted values.
     97 
     98  Substituted values come from the specified dictionary.
     99 
    100  This is like SubstTemplate, except it operates on a file.
    101  """
    102  with open(file_name, 'r') as f:
    103    template = f.read()
    104  return SubstTemplate(template, values)
    105 
    106 
    107 def WriteIfChanged(file_name, contents, mode):
    108  """
    109  Writes the specified contents to the specified file_name.
    110 
    111  Does nothing if the contents aren't different than the current contents.
    112  """
    113  try:
    114    with open(file_name, 'r') as f:
    115      old_contents = f.read()
    116  except EnvironmentError:
    117    pass
    118  else:
    119    if contents == old_contents and mode == stat.S_IMODE(
    120        os.lstat(file_name).st_mode):
    121      return
    122    os.unlink(file_name)
    123  with open(file_name, 'w') as f:
    124    f.write(contents)
    125  os.chmod(file_name, mode)
    126 
    127 
    128 def BuildParser():
    129  """Build argparse parser, with added arguments."""
    130  parser = argparse.ArgumentParser()
    131  parser.add_argument('-f', '--file', action='append', default=[],
    132                      help='Read variables from FILE.')
    133  parser.add_argument('-i', '--input', default=None,
    134                      help='Read strings to substitute from FILE.')
    135  parser.add_argument('-o', '--output', default=None,
    136                      help='Write substituted strings to FILE.')
    137  parser.add_argument('-t', '--template', default=None,
    138                      help='Use TEMPLATE as the strings to substitute.')
    139  parser.add_argument('-x',
    140                      '--executable',
    141                      default=False,
    142                      action='store_true',
    143                      help='Set the executable bit on the output (on POSIX).')
    144  parser.add_argument(
    145      '-e',
    146      '--eval',
    147      action='append',
    148      default=[],
    149      help='Evaluate VAL after reading variables. Can be used '
    150      'to synthesize variables. e.g. -e \'PATCH_HI=int('
    151      'PATCH)//256.')
    152  parser.add_argument(
    153      '-a',
    154      '--arch',
    155      default=None,
    156      choices=android_chrome_version.ARCH_CHOICES,
    157      help='Set which cpu architecture the build is for.')
    158  parser.add_argument('--os', default=None, help='Set the target os.')
    159  parser.add_argument('--official', action='store_true',
    160                      help='Whether the current build should be an official '
    161                           'build, used in addition to the environment '
    162                           'variable.')
    163  parser.add_argument('args', nargs=argparse.REMAINDER,
    164                      help='For compatibility: INPUT and OUTPUT can be '
    165                           'passed as positional arguments.')
    166  return parser
    167 
    168 
    169 def BuildEvals(options, parser):
    170  """Construct a dict of passed '-e' arguments for evaluating."""
    171  evals = {}
    172  for expression in options.eval:
    173    try:
    174      evals.update(dict([expression.split('=', 1)]))
    175    except ValueError:
    176      parser.error('-e requires VAR=VAL')
    177  return evals
    178 
    179 
    180 def ModifyOptionsCompat(options, parser):
    181  """Support compatibility with old versions.
    182 
    183  Specifically, for old versions that considered the first two
    184  positional arguments shorthands for --input and --output.
    185  """
    186  while len(options.args) and (options.input is None or options.output is None):
    187    if options.input is None:
    188      options.input = options.args.pop(0)
    189    elif options.output is None:
    190      options.output = options.args.pop(0)
    191  if options.args:
    192    parser.error('Unexpected arguments: %r' % options.args)
    193 
    194 
    195 def GenerateValues(options, evals):
    196  """Construct a dict of raw values used to generate output.
    197 
    198  e.g. this could return a dict like
    199  {
    200    'BUILD': 74,
    201  }
    202 
    203  which would be used to resolve a template like
    204  'build = "@BUILD@"' into 'build = "74"'
    205 
    206  """
    207  values = FetchValues(options.file, options.official)
    208 
    209  for key, val in evals.items():
    210    values[key] = str(eval(val, globals(), values))
    211 
    212  if options.os == 'android':
    213    android_chrome_version_codes = android_chrome_version.GenerateVersionCodes(
    214        int(values['BUILD']), int(values['PATCH']), options.arch)
    215    values.update(android_chrome_version_codes)
    216 
    217  return values
    218 
    219 
    220 def GenerateOutputContents(options, values):
    221  """Construct output string (e.g. from template).
    222 
    223  Arguments:
    224  options -- argparse parsed arguments
    225  values -- dict with raw values used to resolve the keywords in a template
    226    string
    227  """
    228 
    229  if options.template is not None:
    230    return SubstTemplate(options.template, values)
    231  elif options.input:
    232    return SubstFile(options.input, values)
    233  else:
    234    # Generate a default set of version information.
    235    return """MAJOR=%(MAJOR)s
    236 MINOR=%(MINOR)s
    237 BUILD=%(BUILD)s
    238 PATCH=%(PATCH)s
    239 LASTCHANGE=%(LASTCHANGE)s
    240 OFFICIAL_BUILD=%(OFFICIAL_BUILD)s
    241 """ % values
    242 
    243 
    244 def GenerateOutputMode(options):
    245  """Construct output mode (e.g. from template).
    246 
    247  Arguments:
    248  options -- argparse parsed arguments
    249  """
    250  if options.executable:
    251    return 0o755
    252  else:
    253    return 0o644
    254 
    255 
    256 def BuildOutput(args):
    257  """Gets all input and output values needed for writing output."""
    258  # Build argparse parser with arguments
    259  parser = BuildParser()
    260  options = parser.parse_args(args)
    261 
    262  # Get dict of passed '-e' arguments for evaluating
    263  evals = BuildEvals(options, parser)
    264  # For compatibility with interface that considered first two positional
    265  # arguments shorthands for --input and --output.
    266  ModifyOptionsCompat(options, parser)
    267 
    268  # Get the raw values that will be used the generate the output
    269  values = GenerateValues(options, evals)
    270  # Get the output string and mode
    271  contents = GenerateOutputContents(options, values)
    272  mode = GenerateOutputMode(options)
    273 
    274  return {'options': options, 'contents': contents, 'mode': mode}
    275 
    276 
    277 def main(args):
    278  output = BuildOutput(args)
    279 
    280  if output['options'].output is not None:
    281    WriteIfChanged(output['options'].output, output['contents'], output['mode'])
    282  else:
    283    print(output['contents'])
    284 
    285  return 0
    286 
    287 
    288 if __name__ == '__main__':
    289  sys.exit(main(sys.argv[1:]))