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""" 7version.py -- Chromium version string substitution utility. 8""" 9 10import argparse 11import os 12import sys 13 14 15def 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 30def fetch_values(file_list, is_official_build=None): 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' or is_official_build: 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 55def 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 75def 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 86def 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 102def 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('--official', action='store_true', 117 help='Whether the current build should be an official ' 118 'build, used in addition to the environment ' 119 'variable.') 120 parser.add_argument('args', nargs=argparse.REMAINDER, 121 help='For compatibility: INPUT and OUTPUT can be ' 122 'passed as positional arguments.') 123 options = parser.parse_args() 124 125 evals = {} 126 for expression in options.eval: 127 try: 128 evals.update(dict([expression.split('=', 1)])) 129 except ValueError: 130 parser.error('-e requires VAR=VAL') 131 132 # Compatibility with old versions that considered the first two positional 133 # arguments shorthands for --input and --output. 134 while len(options.args) and (options.input is None or \ 135 options.output is None): 136 if options.input is None: 137 options.input = options.args.pop(0) 138 elif options.output is None: 139 options.output = options.args.pop(0) 140 if options.args: 141 parser.error('Unexpected arguments: %r' % options.args) 142 143 values = fetch_values(options.file, options.official) 144 for key, val in evals.iteritems(): 145 values[key] = str(eval(val, globals(), values)) 146 147 if options.template is not None: 148 contents = subst_template(options.template, values) 149 elif options.input: 150 contents = subst_file(options.input, values) 151 else: 152 # Generate a default set of version information. 153 contents = """MAJOR=%(MAJOR)s 154MINOR=%(MINOR)s 155BUILD=%(BUILD)s 156PATCH=%(PATCH)s 157LASTCHANGE=%(LASTCHANGE)s 158OFFICIAL_BUILD=%(OFFICIAL_BUILD)s 159""" % values 160 161 if options.output is not None: 162 write_if_changed(options.output, contents) 163 else: 164 print contents 165 166 return 0 167 168 169if __name__ == '__main__': 170 sys.exit(main()) 171