1# Copyright 2013 The Chromium 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 5"""Client configuration management. 6 7This module holds the code for detecting and configuring the current client and 8it's output directories. 9It is responsible for writing out the client specific plugins that tell the 10rest of the cr tool what the client is capable of. 11""" 12 13import os 14import pprint 15import sys 16 17import cr 18import cr.auto.build 19import cr.auto.client 20 21# The config version currently supported. 22VERSION = 0.5 23# The default directory name to store config inside 24CLIENT_CONFIG_PATH = '.cr' 25# The partial filename to add to a directory to get it's config file. 26CLIENT_CONFIG_FILE = os.path.join(CLIENT_CONFIG_PATH, 'config.py') 27# The format string for the header of a config file. 28CONFIG_FILE_PREFIX = """ 29# This is an autogenerated file 30# it *will* be overwritten, and changes may lost 31# The system will autoload any other python file in the same folder. 32 33import cr 34 35OVERRIDES = cr.Config.From(""" 36# The format string for each value in a config file. 37CONFIG_VAR_LINE = '\n {0} = {1!r},' 38# The format string for the tail of a config file. 39CONFIG_FILE_SUFFIX = '\n)\n' 40# The name of the gclient config file 41GCLIENT_FILENAME = '.gclient' 42 43# The default config values installed by this module. 44DEFAULT = cr.Config.From( 45 CR_ROOT_PATH=os.path.join('{GOOGLE_CODE}'), 46 CR_CLIENT_NAME='chromium', 47 CR_CLIENT_PATH=os.path.join('{CR_ROOT_PATH}', '{CR_CLIENT_NAME}'), 48 CR_SRC=os.path.join('{CR_CLIENT_PATH}', 'src'), 49 CR_BUILD_DIR=os.path.join('{CR_SRC}', '{CR_OUT_FULL}'), 50) 51 52 53def DetectClient(): 54 # Attempt to detect the current client from the cwd 55 # See if we can detect the source tree root 56 client_path = os.getcwd() 57 while (client_path and 58 not os.path.exists(os.path.join(client_path, GCLIENT_FILENAME))): 59 old = client_path 60 client_path = os.path.dirname(client_path) 61 if client_path == old: 62 client_path = None 63 if client_path is not None: 64 dirname, basename = os.path.split(client_path) 65 if basename == 'src': 66 # we have the src path, base is one level up 67 client_path = dirname 68 if client_path is not None: 69 cr.context.derived['CR_CLIENT_PATH'] = client_path 70 # now get the value from it may be different 71 client_path = cr.context.Get('CR_CLIENT_PATH') 72 if client_path is not None: 73 cr.context.derived['CR_CLIENT_NAME'] = os.path.basename(client_path) 74 75 76def _GetConfigFilename(path): 77 return os.path.realpath(os.path.join(path, CLIENT_CONFIG_FILE)) 78 79 80def _IsOutputDir(path): 81 return os.path.isfile(_GetConfigFilename(path)) 82 83 84def _WriteConfig(writer, data): 85 writer.write(CONFIG_FILE_PREFIX) 86 for key, value in data.items(): 87 writer.write(CONFIG_VAR_LINE.format(key, value)) 88 writer.write(CONFIG_FILE_SUFFIX) 89 90 91def AddArguments(parser): 92 parser.add_argument( 93 '-o', '--out', dest='_out', metavar='name', 94 default=None, 95 help='The name of the out directory to use. Overrides CR_OUT.' 96 ) 97 98 99def GetOutArgument(): 100 return getattr(cr.context.args, '_out', None) 101 102 103def ApplyOutArgument(): 104 # TODO(iancottrell): be flexible, allow out to do approximate match... 105 out = GetOutArgument() 106 if out: 107 cr.context.derived.Set(CR_OUT_FULL=out) 108 109 110def ReadGClient(): 111 """Loads the .gclient configuration for the current client. 112 113 This will load from CR_CLIENT_PATH. 114 115 Returns: 116 The dict of values set in the .gclient file. 117 118 """ 119 # Now attempt to load and parse the .gclient file 120 result = {} 121 try: 122 gclient_file = cr.context.Substitute( 123 os.path.join('{CR_CLIENT_PATH}', GCLIENT_FILENAME)) 124 with open(gclient_file, 'r') as spec_file: 125 # matching the behaviour of gclient, so pylint: disable=exec-used 126 exec(spec_file.read(), {}, result) 127 except IOError: 128 # no .gclient file, skip it 129 pass 130 return result 131 132 133def WriteGClient(): 134 """Writes the .gclient configuration for the current client. 135 136 This will write to CR_CLIENT_PATH. 137 138 """ 139 gclient_file = cr.context.Substitute( 140 os.path.join('{CR_CLIENT_PATH}', GCLIENT_FILENAME)) 141 spec = '\n'.join('%s = %s' % (key, pprint.pformat(value)) 142 for key,value in cr.context.gclient.items()) 143 if cr.context.dry_run: 144 print 'Write the following spec to', gclient_file 145 print spec 146 else: 147 with open(gclient_file, 'w') as spec_file: 148 spec_file.write(spec) 149 150def LoadConfig(): 151 """Loads the client configuration for the given context. 152 153 This will load configuration if present from CR_CLIENT_PATH and then 154 CR_BUILD_DIR. 155 156 Returns: 157 True if configuration was fully loaded. 158 159 """ 160 # Load the root config, will help set default build dir 161 client_path = cr.context.Find('CR_CLIENT_PATH') 162 if not client_path: 163 return False 164 cr.auto.client.__path__.append(os.path.join(client_path, CLIENT_CONFIG_PATH)) 165 cr.loader.Scan() 166 # Now load build dir config 167 build_dir = cr.context.Find('CR_BUILD_DIR') 168 if not build_dir: 169 return False 170 cr.auto.build.__path__.append(os.path.join(build_dir, CLIENT_CONFIG_PATH)) 171 cr.loader.Scan() 172 return hasattr(cr.auto.build, 'config') 173 174 175def WriteConfig(path, data): 176 """Writes a configuration out to a file. 177 178 This writes all the key value pairs in data out to a config file below path. 179 180 Args: 181 path: The base path to write the config plugin into. 182 data: The key value pairs to write. 183 """ 184 filename = _GetConfigFilename(path) 185 config_dir = os.path.dirname(filename) 186 if cr.context.dry_run: 187 print 'makedirs', config_dir 188 print 'Write config to', filename 189 _WriteConfig(sys.stdout, data) 190 else: 191 try: 192 os.makedirs(config_dir) 193 except OSError: 194 if not os.path.isdir(config_dir): 195 raise 196 with open(filename, 'w') as writer: 197 _WriteConfig(writer, data) 198 199 200def PrintInfo(): 201 print 'Selected output directory is', cr.context.Find('CR_BUILD_DIR') 202 try: 203 for name in cr.auto.build.config.OVERRIDES.exported.keys(): 204 print ' ', name, '=', cr.context.Get(name) 205 except AttributeError: 206 pass 207 208 209class InitHook(cr.Plugin, cr.Plugin.Type): 210 """Base class for output directory initialization hooks. 211 212 Implementations used to fix from old version to new ones live in the 213 cr.fixups package. 214 """ 215 216 def Run(self, old_version, config): 217 """Run the initialization hook. 218 219 This is invoked once per init invocation. 220 Args: 221 old_version: The old version, 222 0.0 if the old version was bad or missing, 223 None if building a new output direcory. 224 config: The mutable config that will be written. 225 """ 226 raise NotImplementedError('Must be overridden.') 227 228