| # Copyright 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Client configuration management. |
| |
| This module holds the code for detecting and configuring the current client and |
| it's output directories. |
| It is responsible for writing out the client specific plugins that tell the |
| rest of the cr tool what the client is capable of. |
| """ |
| |
| import os |
| import pprint |
| import sys |
| |
| import cr |
| import cr.auto.build |
| import cr.auto.client |
| |
| # The config version currently supported. |
| VERSION = 0.5 |
| # The default directory name to store configs inside. |
| CONFIG_PATH = '.cr' |
| # The filename of the config file inside a config directory. |
| CONFIG_FILE = 'config.py' |
| # The directory inside the config directory which contains the client config. |
| CLIENT_CONFIG_DIR = 'client' |
| # The directory inside the config directory which contains build configs. |
| BUILD_CONFIG_DIR = 'builds' |
| # The format string for the header of a config file. |
| CONFIG_FILE_PREFIX = """ |
| # This is an autogenerated file |
| # it *will* be overwritten, and changes may lost |
| # The system will autoload any other python file in the same folder. |
| |
| import cr |
| |
| OVERRIDES = cr.Config.From(""" |
| # The format string for each value in a config file. |
| CONFIG_VAR_LINE = '\n {0} = {1!r},' |
| # The format string for the tail of a config file. |
| CONFIG_FILE_SUFFIX = '\n)\n' |
| # The name of the gclient config file |
| GCLIENT_FILENAME = '.gclient' |
| |
| # The default config values installed by this module. |
| DEFAULT = cr.Config.From( |
| CR_ROOT_PATH=os.path.join('{GOOGLE_CODE}'), |
| CR_CLIENT_NAME='chromium', |
| CR_CLIENT_PATH=os.path.join('{CR_ROOT_PATH}', '{CR_CLIENT_NAME}'), |
| CR_SRC=os.path.join('{CR_CLIENT_PATH}', 'src'), |
| CR_BUILD_DIR=os.path.join('{CR_SRC}', '{CR_OUT_FULL}'), |
| ) |
| |
| |
| def DetectClient(): |
| # Attempt to detect the current client from the cwd |
| # See if we can detect the source tree root |
| client_path = os.getcwd() |
| while (client_path and |
| not os.path.exists(os.path.join(client_path, GCLIENT_FILENAME))): |
| old = client_path |
| client_path = os.path.dirname(client_path) |
| if client_path == old: |
| client_path = None |
| if client_path is not None: |
| dirname, basename = os.path.split(client_path) |
| if basename == 'src': |
| # we have the src path, base is one level up |
| client_path = dirname |
| if client_path is not None: |
| cr.context.derived['CR_CLIENT_PATH'] = client_path |
| # now get the value from it may be different |
| client_path = cr.context.Get('CR_CLIENT_PATH') |
| if client_path is not None: |
| cr.context.derived['CR_CLIENT_NAME'] = os.path.basename(client_path) |
| |
| |
| def _GetConfigDir(use_build_dir): |
| base_path = os.path.join(cr.context.Get('CR_CLIENT_PATH'), CONFIG_PATH) |
| if use_build_dir: |
| path_suffix = os.path.join(BUILD_CONFIG_DIR, cr.context.Get('CR_OUT_FULL')) |
| else: |
| path_suffix = CLIENT_CONFIG_DIR |
| return os.path.realpath(os.path.join(base_path, path_suffix)) |
| |
| |
| def _GetDeprecatedConfigDir(use_build_dir): |
| if use_build_dir: |
| path = cr.context.Get('CR_BUILD_DIR') |
| else: |
| path = cr.context.Get('CR_CLIENT_PATH') |
| return os.path.realpath(os.path.join(path, CONFIG_PATH)) |
| |
| |
| def _GetConfigFile(config_dir): |
| return os.path.join(config_dir, CONFIG_FILE) |
| |
| |
| def _MigrateAndGetConfigDir(use_build_dir): |
| new_config_dir = _GetConfigDir(use_build_dir) |
| new_config_file = _GetConfigFile(new_config_dir) |
| new_config_exists = os.path.exists(new_config_file) |
| |
| old_config_dir = _GetDeprecatedConfigDir(use_build_dir) |
| old_config_file = _GetConfigFile(old_config_dir) |
| old_config_exists = os.path.exists(old_config_file) |
| |
| if old_config_exists: |
| if new_config_exists: |
| print 'Warning: Old config file %s superseded by new config file %s' % ( |
| old_config_file, new_config_file) |
| else: |
| print 'Migrating config file from %s to %s...' % ( |
| old_config_file, new_config_file) |
| if not cr.context.dry_run: |
| # Make the new config directory (if necessary). |
| try: |
| os.makedirs(new_config_dir) |
| except OSError: |
| if not os.path.isdir(new_config_dir): |
| raise |
| # Move the config file. |
| os.rename(old_config_file, new_config_file) |
| # Delete the old config directory (only applies to the build config). |
| if use_build_dir: |
| try: |
| os.removedirs(old_config_dir) |
| except OSError: |
| print 'Warning: Old config directory %s could not be removed' % ( |
| old_config_dir) |
| |
| return new_config_dir |
| |
| |
| def _WriteConfig(writer, data): |
| writer.write(CONFIG_FILE_PREFIX) |
| for key, value in data.items(): |
| writer.write(CONFIG_VAR_LINE.format(key, value)) |
| writer.write(CONFIG_FILE_SUFFIX) |
| |
| |
| def AddArguments(parser): |
| parser.add_argument( |
| '-o', '--out', dest='_out', metavar='name', |
| default=None, |
| help='The name of the out directory to use. Overrides CR_OUT.' |
| ) |
| |
| |
| def GetOutArgument(): |
| return getattr(cr.context.args, '_out', None) |
| |
| |
| def ApplyOutArgument(): |
| # TODO(iancottrell): be flexible, allow out to do approximate match... |
| out = GetOutArgument() |
| if out: |
| cr.context.derived.Set(CR_OUT_FULL=out) |
| |
| |
| def ReadGClient(): |
| """Loads the .gclient configuration for the current client. |
| |
| This will load from CR_CLIENT_PATH. |
| |
| Returns: |
| The dict of values set in the .gclient file. |
| |
| """ |
| # Now attempt to load and parse the .gclient file |
| result = {} |
| try: |
| gclient_file = cr.context.Substitute( |
| os.path.join('{CR_CLIENT_PATH}', GCLIENT_FILENAME)) |
| with open(gclient_file, 'r') as spec_file: |
| # matching the behaviour of gclient, so pylint: disable=exec-used |
| exec(spec_file.read(), {}, result) |
| except IOError: |
| # no .gclient file, skip it |
| pass |
| return result |
| |
| |
| def WriteGClient(): |
| """Writes the .gclient configuration for the current client. |
| |
| This will write to CR_CLIENT_PATH. |
| |
| """ |
| gclient_file = cr.context.Substitute( |
| os.path.join('{CR_CLIENT_PATH}', GCLIENT_FILENAME)) |
| spec = '\n'.join('%s = %s' % (key, pprint.pformat(value)) |
| for key,value in cr.context.gclient.items()) |
| if cr.context.dry_run: |
| print 'Write the following spec to', gclient_file |
| print spec |
| else: |
| with open(gclient_file, 'w') as spec_file: |
| spec_file.write(spec) |
| |
| def LoadConfig(): |
| """Loads the client configuration for the given context. |
| |
| This will load configuration if present from CR_CLIENT_PATH and then |
| CR_BUILD_DIR. |
| |
| Returns: |
| True if configuration was fully loaded. |
| |
| """ |
| # Load the root config, will help set default build dir |
| client_config_dir = _MigrateAndGetConfigDir(use_build_dir=False) |
| cr.auto.client.__path__.append(client_config_dir) |
| cr.loader.Scan() |
| # Now load build dir config |
| build_config_dir = _MigrateAndGetConfigDir(use_build_dir=True) |
| cr.auto.build.__path__.append(build_config_dir) |
| cr.loader.Scan() |
| |
| if not hasattr(cr.auto.build, 'config'): |
| return False |
| |
| cr.context.derived.Set(CR_BUILD_CONFIG_PATH=_GetConfigFile(build_config_dir)) |
| return True |
| |
| |
| def WriteConfig(use_build_dir, data): |
| """Writes a configuration out to a file. |
| |
| This writes all the key value pairs in data out to a config file. |
| |
| Args: |
| use_build_dir: True if the config file should be written to the build |
| directory. Otherwise it will be written to the root config directory. |
| data: The key value pairs to write. |
| """ |
| config_dir = _GetConfigDir(use_build_dir) |
| filename = _GetConfigFile(config_dir) |
| if cr.context.dry_run: |
| print 'makedirs', config_dir |
| print 'Write config to', filename |
| _WriteConfig(sys.stdout, data) |
| else: |
| try: |
| os.makedirs(config_dir) |
| except OSError: |
| if not os.path.isdir(config_dir): |
| raise |
| with open(filename, 'w') as writer: |
| _WriteConfig(writer, data) |
| |
| |
| def PrintInfo(): |
| print 'Selected output directory is', cr.context.Find('CR_BUILD_DIR') |
| print 'Build config file is', _GetConfigFile(_GetConfigDir( |
| use_build_dir=True)) |
| try: |
| for name in cr.auto.build.config.OVERRIDES.exported.keys(): |
| print ' ', name, '=', cr.context.Get(name) |
| except AttributeError: |
| pass |
| |
| |
| class InitHook(cr.Plugin, cr.Plugin.Type): |
| """Base class for output directory initialization hooks. |
| |
| Implementations used to fix from old version to new ones live in the |
| cr.fixups package. |
| """ |
| |
| def Run(self, old_version, config): |
| """Run the initialization hook. |
| |
| This is invoked once per init invocation. |
| Args: |
| old_version: The old version, |
| 0.0 if the old version was bad or missing, |
| None if building a new output direcory. |
| config: The mutable config that will be written. |
| """ |
| raise NotImplementedError('Must be overridden.') |
| |