blob: d1096ab9854d156c1e3cbd73badcd2f6bad4d277 [file] [log] [blame]
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Wrapper for building the Chromium OS platform.
Takes care of running gyp/ninja/etc... with all the right values.
from __future__ import print_function
import argparse
import glob
import os
import shutil
import subprocess
import sys
from chromite.lib import osutils
from chromite.lib.cros_build_lib import ShellUnquote
class Platform2(object):
"""Main builder logic for platform2"""
def __init__(self, use_flags=None, board=None, host=False, libdir=None,
incremental=True, verbose=False, enable_tests=False,
cache_dir=None, jobs=None):
self.board = board = host
self.incremental = incremental = jobs
self.verbose = verbose
if use_flags is not None:
self.use_flags = use_flags
self.use_flags = self.get_platform2_use_flags()
if enable_tests:
self.sysroot = '/'
self.pkgconfig = 'pkg-config'
board_vars = self.get_portageq_envvars(['SYSROOT', 'PKG_CONFIG'],
self.sysroot = board_vars['SYSROOT']
self.pkgconfig = board_vars['PKG_CONFIG']
if libdir:
self.libdir = libdir
self.libdir = '/usr/lib'
if cache_dir:
self.cache_dir = cache_dir
self.cache_dir = os.path.join(self.sysroot,
def get_src_dir(self):
"""Return the path to build tools and common GYP files"""
return os.path.realpath(os.path.dirname(__file__))
def get_platform2_root(self):
"""Return the path to src/platform2"""
return os.path.dirname(self.get_src_dir())
def get_buildroot(self):
"""Return the path to the folder where build artifacts are located."""
if not self.incremental:
workdir = os.environ.get('WORKDIR')
if workdir:
# Matches $(cros-workon_get_build_dir) behavior.
return os.path.join(workdir, 'build')
return os.getcwd()
return self.cache_dir
def get_products_path(self):
"""Return the path to the folder where build product are located."""
return os.path.join(self.get_buildroot(), 'out/Default')
def get_portageq_envvars(self, varnames, board=None):
"""Returns the values of a given set of variables using portageq."""
if isinstance(varnames, basestring):
varnames = [varnames]
# See if the env already has these settings. If so, grab them directly.
# This avoids the need to specify --board at all most of the time.
board_vars = {}
for varname in varnames:
board_vars[varname] = os.environ[varname]
return board_vars
except KeyError:
if board is None and not
board = self.board
# Portage will set this to an incomplete list which breaks portageq
# walking all of the repos. Clear it and let the value be repopulated.
os.environ.pop('PORTDIR_OVERLAY', None)
portageq_bin = 'portageq' if not board else 'portageq-%s' % board
cmd = [portageq_bin, 'envvar', '-v']
cmd += varnames
output = subprocess.check_output(cmd)
except (UnboundLocalError, OSError):
raise AssertionError('Error running %r' % cmd)
output_lines = [x for x in output.splitlines() if x]
output_items = [x.split('=', 1) for x in output_lines]
board_vars = dict(dict([(k, ShellUnquote(v)) for k, v in output_items]))
return board_vars if len(board_vars) > 1 else board_vars.values()[0]
def get_platform2_use_flags(self):
"""Returns the set of USE flags set for the Platform2 package."""
equery_bin = 'equery' if not self.board else 'equery-%s' % self.board
cmd = [equery_bin, 'u', 'platform2']
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, errors = p.communicate()
except UnboundLocalError:
raise AssertionError('Error running %s' % equery_bin)
except OSError:
raise AssertionError('Error running equery: %s' % errors)
return set([x for x in output.splitlines() if x])
def get_build_environment(self):
"""Returns a dict containing environment variables we will use to run GYP.
We do this to set the various CC/CXX/AR names for the target board.
varnames = ['CHOST', 'AR', 'CC', 'CXX']
if not and not self.board:
for v in varnames:
os.environ.setdefault(v, '')
board_env = self.get_portageq_envvars(varnames)
tool_names = {
'AR': 'ar',
'CC': 'gcc',
'CXX': 'g++',
env = os.environ.copy()
for var, tool in tool_names.items():
env['%s_target' % var] = (board_env[var] if board_env[var] else \
'%s-%s' % (board_env['CHOST'], tool))
return env
def get_components_glob(self):
"""Return a glob of marker files for components/projects that were built.
Each project spits out a file whilst building: we return a glob of them
so we can install/test those projects or reset between compiles to ensure
components that are no longer part of the build don't get installed.
return glob.glob(os.path.join(self.get_products_path(),
def use(self, flag):
"""Returns a boolean describing whether or not a given USE flag is set."""
return flag in self.use_flags
def configure(self, args):
"""Runs the configure step of the Platform2 build.
Creates the build root if it doesn't already exists. Generates flags to
run GYP with, and then runs GYP.
if not os.path.isdir(self.get_buildroot()):
if not self.incremental and os.path.isdir(self.get_products_path()):
targets = [os.path.join(self.get_src_dir(), 'platform.gyp')]
if args:
targets = args
common_gyp = os.path.join(self.get_src_dir(), 'common.gypi')
libbase_ver = os.environ.get('BASE_VER', '')
if not libbase_ver:
# If BASE_VER variable not set, read the content of common_mk/BASE_VER
# file which contains the default libchrome revision number.
base_ver_file = os.path.join(self.get_src_dir(), 'BASE_VER')
libbase_ver = osutils.ReadFile(base_ver_file).strip()
assert libbase_ver
# The common root folder of platform2/.
# Used as (DEPTH) variable in specific project .gyp files.
src_root = os.path.normpath(os.path.join(self.get_src_dir(), '..'))
# Do NOT pass the board name into GYP. If you think you need to so, you're
# probably doing it wrong.
gyp_args = ['gyp'] + targets + [
'--include=%s' % common_gyp,
'--depth=%s' % src_root,
'--toplevel-dir=%s' % self.get_platform2_root(),
'--generator-output=%s' % self.get_buildroot(),
'-Dpkg-config=%s' % self.pkgconfig,
'-Dsysroot=%s' % self.sysroot,
'-Dlibdir=%s' % self.libdir,
'-Dbuild_root=%s' % self.get_buildroot(),
'-Dplatform2_root=%s' % self.get_platform2_root(),
'-Dlibbase_ver=%s' % libbase_ver,
'-Dclang_syntax=%s' % os.environ.get('CROS_WORKON_CLANG', ''),
'-Denable_exceptions=%s' % os.environ.get('CXXEXCEPTIONS', '0'),
'-Dexternal_cflags=%s' % os.environ.get('CFLAGS', ''),
'-Dexternal_cxxflags=%s' % os.environ.get('CXXFLAGS', ''),
'-Dexternal_cppflags=%s' % os.environ.get('CPPFLAGS', ''),
'-Dexternal_ldflags=%s' % os.environ.get('LDFLAGS', ''),
# USE flags allow some chars that gyp does not, so normalize them.
gyp_args += ['-DUSE_%s=1' % (use_flag.replace('-', '_'),)
for use_flag in self.use_flags]
subprocess.check_call(gyp_args, env=self.get_build_environment(),
except subprocess.CalledProcessError:
raise AssertionError('Error running: %s'
% ' '.join(map(repr, gyp_args)))
except OSError:
raise AssertionError('Error running %s' % (gyp_args[0]))
def compile(self, args):
"""Runs the compile step of the Platform2 build.
Removes any existing component markers that may exist (so we don't run
tests/install for projects that have been disabled since the last
build). Builds arguments for running Ninja and then runs Ninja.
for component in self.get_components_glob():
if not args:
args = ['all']
ninja_args = ['ninja', '-C', self.get_products_path()]
ninja_args += ['-j', str(]
ninja_args += args
if self.verbose:
if os.environ.get('NINJA_ARGS'):
except subprocess.CalledProcessError:
raise AssertionError('Error running: %s'
% ' '.join(map(repr, ninja_args)))
except OSError:
raise AssertionError('Error running %s' % (ninja_args[0]))
def deviterate(self, args):
"""Runs the configure and compile steps of the Platform2 build.
This is the default action, to allow easy iterative testing of changes
as a developer.
class _ParseStringSetAction(argparse.Action):
"""Helper for turning a string argument into a list"""
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, set(values.split()))
def main(argv):
actions = ['configure', 'compile', 'deviterate']
parser = argparse.ArgumentParser()
parser.add_argument('--action', default='deviterate',
choices=actions, help='action to run')
help='board to build for')
help='directory to use as cache for incremental build')
parser.add_argument('--disable_incremental', action='store_false',
dest='incremental', help='disable incremental build')
parser.add_argument('--enable_tests', action='store_true',
help='build and run tests')
parser.add_argument('--host', action='store_true',
help='specify that we\'re building for the host')
help='the libdir for the specific board, eg /usr/lib64')
action=_ParseStringSetAction, help='USE flags to enable')
parser.add_argument('-j', '--jobs', type=int, default=None,
help='number of jobs to run in parallel')
parser.add_argument('--verbose', action='store_true', default=None,
help='enable verbose log output')
parser.add_argument('args', nargs='*')
options = parser.parse_args(argv)
if and options.board:
raise AssertionError('You must provide only one of --board or --host')
if options.verbose is None:
# Should convert to cros_build_lib.BooleanShellValue.
options.verbose = (os.environ.get('VERBOSE', '0') == '1')
p2 = Platform2(options.use_flags, options.board,,
options.libdir, options.incremental, options.verbose,
options.enable_tests, options.cache_dir,
getattr(p2, options.action)(options.args)
if __name__ == '__main__':