blob: 4c89c464472d6e224184051fefe8be2c53877be5 [file] [log] [blame]
# 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.')