Home | History | Annotate | Download | only in auto_bisect
      1 # Copyright 2014 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 """Classes and functions for building Chrome.
      6 
      7 This includes functions for running commands to build, as well as
      8 specific rules about which targets to build.
      9 """
     10 
     11 import os
     12 import subprocess
     13 import sys
     14 
     15 import bisect_utils
     16 
     17 ORIGINAL_ENV = {}
     18 
     19 
     20 class Builder(object):
     21   """Subclasses of the Builder class are used by the bisect script to build
     22   relevant targets.
     23   """
     24   def __init__(self, opts):
     25     """Performs setup for building with target build system.
     26 
     27     Args:
     28       opts: Options parsed from command line.
     29 
     30     Raises:
     31       RuntimeError: Some condition necessary for building was not met.
     32     """
     33     if bisect_utils.IsWindowsHost():
     34       if not opts.build_preference:
     35         opts.build_preference = 'msvs'
     36 
     37       if opts.build_preference == 'msvs':
     38         if not os.getenv('VS100COMNTOOLS'):
     39           raise RuntimeError(
     40               'Path to visual studio could not be determined.')
     41       else:
     42         # Need to re-escape goma dir, see crbug.com/394990.
     43         if opts.goma_dir:
     44           opts.goma_dir = opts.goma_dir.encode('string_escape')
     45         SetBuildSystemDefault(opts.build_preference, opts.use_goma,
     46                               opts.goma_dir)
     47     else:
     48       if not opts.build_preference:
     49         if 'ninja' in os.getenv('GYP_GENERATORS', default=''):
     50           opts.build_preference = 'ninja'
     51         else:
     52           opts.build_preference = 'make'
     53 
     54       SetBuildSystemDefault(opts.build_preference, opts.use_goma, opts.goma_dir)
     55 
     56     if not SetupPlatformBuildEnvironment(opts):
     57       raise RuntimeError('Failed to set platform environment.')
     58 
     59   @staticmethod
     60   def FromOpts(opts):
     61     """Constructs and returns a Builder object.
     62 
     63     Args:
     64       opts: Options parsed from the command-line.
     65     """
     66     builder = None
     67     if opts.target_platform == 'cros':
     68       builder = CrosBuilder(opts)
     69     elif opts.target_platform == 'android':
     70       builder = AndroidBuilder(opts)
     71     elif opts.target_platform == 'android-chrome':
     72       builder = AndroidChromeBuilder(opts)
     73     else:
     74       builder = DesktopBuilder(opts)
     75     return builder
     76 
     77   def Build(self, depot, opts):
     78     """Runs a command to build Chrome."""
     79     raise NotImplementedError()
     80 
     81 
     82 def GetBuildOutputDirectory(opts, src_dir=None):
     83   """Returns the path to the build directory, relative to the checkout root.
     84 
     85   Assumes that the current working directory is the checkout root.
     86 
     87   Args:
     88     opts: Command-line options.
     89     src_dir: Path to chromium/src directory.
     90 
     91   Returns:
     92     A path to the directory to use as build output directory.
     93 
     94   Raises:
     95     NotImplementedError: The platform according to sys.platform is unexpected.
     96   """
     97   src_dir = src_dir or 'src'
     98   if opts.build_preference == 'ninja' or bisect_utils.IsLinuxHost():
     99     return os.path.join(src_dir, 'out')
    100   if bisect_utils.IsMacHost():
    101     return os.path.join(src_dir, 'xcodebuild')
    102   if bisect_utils.IsWindowsHost():
    103     return os.path.join(src_dir, 'build')
    104   raise NotImplementedError('Unexpected platform %s' % sys.platform)
    105 
    106 
    107 class DesktopBuilder(Builder):
    108   """DesktopBuilder is used to build Chromium on Linux, Mac, or Windows."""
    109 
    110   def __init__(self, opts):
    111     super(DesktopBuilder, self).__init__(opts)
    112 
    113   def Build(self, depot, opts):
    114     """Builds chromium_builder_perf target using options passed into the script.
    115 
    116     Args:
    117       depot: Name of current depot being bisected.
    118       opts: The options parsed from the command line.
    119 
    120     Returns:
    121       True if build was successful.
    122     """
    123     targets = ['chromium_builder_perf']
    124 
    125     threads = None
    126     if opts.use_goma:
    127       threads = 64
    128 
    129     build_success = False
    130     if opts.build_preference == 'make':
    131       build_success = BuildWithMake(threads, targets, opts.target_build_type)
    132     elif opts.build_preference == 'ninja':
    133       build_success = BuildWithNinja(threads, targets, opts.target_build_type)
    134     elif opts.build_preference == 'msvs':
    135       assert bisect_utils.IsWindowsHost(), 'msvs is only supported on Windows.'
    136       build_success = BuildWithVisualStudio(targets, opts.target_build_type)
    137     else:
    138       assert False, 'No build system defined.'
    139     return build_success
    140 
    141 
    142 class AndroidBuilder(Builder):
    143   """AndroidBuilder is used to build on android."""
    144 
    145   def __init__(self, opts):
    146     super(AndroidBuilder, self).__init__(opts)
    147 
    148   # TODO(qyearsley): Make this a class method and verify that it works with
    149   # a unit test.
    150   # pylint: disable=R0201
    151   def _GetTargets(self):
    152     """Returns a list of build targets."""
    153     return ['chrome_shell_apk', 'cc_perftests_apk', 'android_tools']
    154 
    155   def Build(self, depot, opts):
    156     """Builds the android content shell and other necessary tools.
    157 
    158     Args:
    159         depot: Current depot being bisected.
    160         opts: The options parsed from the command line.
    161 
    162     Returns:
    163         True if build was successful.
    164     """
    165     threads = None
    166     if opts.use_goma:
    167       threads = 64
    168 
    169     build_success = False
    170     if opts.build_preference == 'ninja':
    171       build_success = BuildWithNinja(
    172           threads, self._GetTargets(), opts.target_build_type)
    173     else:
    174       assert False, 'No build system defined.'
    175 
    176     return build_success
    177 
    178 
    179 class AndroidChromeBuilder(AndroidBuilder):
    180   """AndroidChromeBuilder is used to build "android-chrome".
    181 
    182   This is slightly different from AndroidBuilder.
    183   """
    184 
    185   def __init__(self, opts):
    186     super(AndroidChromeBuilder, self).__init__(opts)
    187 
    188   # TODO(qyearsley): Make this a class method and verify that it works with
    189   # a unit test.
    190   # pylint: disable=R0201
    191   def _GetTargets(self):
    192     """Returns a list of build targets."""
    193     return AndroidBuilder._GetTargets(self) + ['chrome_apk']
    194 
    195 
    196 class CrosBuilder(Builder):
    197   """CrosBuilder is used to build and image ChromeOS/Chromium.
    198 
    199   WARNING(qyearsley, 2014-08-15): This hasn't been tested recently.
    200   """
    201 
    202   def __init__(self, opts):
    203     super(CrosBuilder, self).__init__(opts)
    204 
    205   @staticmethod
    206   def ImageToTarget(opts):
    207     """Installs latest image to target specified by opts.cros_remote_ip.
    208 
    209     Args:
    210       opts: Program options containing cros_board and cros_remote_ip.
    211 
    212     Returns:
    213       True if successful.
    214     """
    215     try:
    216       # Keys will most likely be set to 0640 after wiping the chroot.
    217       os.chmod(bisect_utils.CROS_SCRIPT_KEY_PATH, 0600)
    218       os.chmod(bisect_utils.CROS_TEST_KEY_PATH, 0600)
    219       cmd = [bisect_utils.CROS_SDK_PATH, '--', './bin/cros_image_to_target.py',
    220              '--remote=%s' % opts.cros_remote_ip,
    221              '--board=%s' % opts.cros_board, '--test', '--verbose']
    222 
    223       return_code = bisect_utils.RunProcess(cmd)
    224       return not return_code
    225     except OSError:
    226       return False
    227 
    228   @staticmethod
    229   def BuildPackages(opts, depot):
    230     """Builds packages for cros.
    231 
    232     Args:
    233       opts: Program options containing cros_board.
    234       depot: The depot being bisected.
    235 
    236     Returns:
    237       True if successful.
    238     """
    239     cmd = [bisect_utils.CROS_SDK_PATH]
    240 
    241     if depot != 'cros':
    242       path_to_chrome = os.path.join(os.getcwd(), '..')
    243       cmd += ['--chrome_root=%s' % path_to_chrome]
    244 
    245     cmd += ['--']
    246 
    247     if depot != 'cros':
    248       cmd += ['CHROME_ORIGIN=LOCAL_SOURCE']
    249 
    250     cmd += ['BUILDTYPE=%s' % opts.target_build_type, './build_packages',
    251         '--board=%s' % opts.cros_board]
    252     return_code = bisect_utils.RunProcess(cmd)
    253 
    254     return not return_code
    255 
    256   @staticmethod
    257   def BuildImage(opts, depot):
    258     """Builds test image for cros.
    259 
    260     Args:
    261       opts: Program options containing cros_board.
    262       depot: The depot being bisected.
    263 
    264     Returns:
    265       True if successful.
    266     """
    267     cmd = [bisect_utils.CROS_SDK_PATH]
    268 
    269     if depot != 'cros':
    270       path_to_chrome = os.path.join(os.getcwd(), '..')
    271       cmd += ['--chrome_root=%s' % path_to_chrome]
    272 
    273     cmd += ['--']
    274 
    275     if depot != 'cros':
    276       cmd += ['CHROME_ORIGIN=LOCAL_SOURCE']
    277 
    278     cmd += ['BUILDTYPE=%s' % opts.target_build_type, '--', './build_image',
    279         '--board=%s' % opts.cros_board, 'test']
    280 
    281     return_code = bisect_utils.RunProcess(cmd)
    282 
    283     return not return_code
    284 
    285   def Build(self, depot, opts):
    286     """Builds targets using options passed into the script.
    287 
    288     Args:
    289         depot: Current depot being bisected.
    290         opts: The options parsed from the command line.
    291 
    292     Returns:
    293         True if build was successful.
    294     """
    295     if self.BuildPackages(opts, depot):
    296       if self.BuildImage(opts, depot):
    297         return self.ImageToTarget(opts)
    298     return False
    299 
    300 
    301 def SetBuildSystemDefault(build_system, use_goma, goma_dir):
    302   """Sets up any environment variables needed to build with the specified build
    303   system.
    304 
    305   Args:
    306     build_system: A string specifying build system. Currently only 'ninja' or
    307         'make' are supported.
    308   """
    309   if build_system == 'ninja':
    310     gyp_var = os.getenv('GYP_GENERATORS', default='')
    311 
    312     if not gyp_var or not 'ninja' in gyp_var:
    313       if gyp_var:
    314         os.environ['GYP_GENERATORS'] = gyp_var + ',ninja'
    315       else:
    316         os.environ['GYP_GENERATORS'] = 'ninja'
    317 
    318       if bisect_utils.IsWindowsHost():
    319         os.environ['GYP_DEFINES'] = 'component=shared_library '\
    320             'incremental_chrome_dll=1 disable_nacl=1 fastbuild=1 '\
    321             'chromium_win_pch=0'
    322 
    323   elif build_system == 'make':
    324     os.environ['GYP_GENERATORS'] = 'make'
    325   else:
    326     raise RuntimeError('%s build not supported.' % build_system)
    327 
    328   if use_goma:
    329     os.environ['GYP_DEFINES'] = '%s %s' % (os.getenv('GYP_DEFINES', default=''),
    330                                            'use_goma=1')
    331     if goma_dir:
    332       os.environ['GYP_DEFINES'] += ' gomadir=%s' % goma_dir
    333 
    334 
    335 def SetupPlatformBuildEnvironment(opts):
    336   """Performs any platform-specific setup.
    337 
    338   Args:
    339     opts: The options parsed from the command line through parse_args().
    340 
    341   Returns:
    342     True if successful.
    343   """
    344   if 'android' in opts.target_platform:
    345     CopyAndSaveOriginalEnvironmentVars()
    346     return SetupAndroidBuildEnvironment(opts)
    347   elif opts.target_platform == 'cros':
    348     return bisect_utils.SetupCrosRepo()
    349   return True
    350 
    351 
    352 def BuildWithMake(threads, targets, build_type='Release'):
    353   """Runs a make command with the given targets.
    354 
    355   Args:
    356     threads: The number of threads to use. None means unspecified/unlimited.
    357     targets: List of make targets.
    358     build_type: Release or Debug.
    359 
    360   Returns:
    361     True if the command had a 0 exit code, False otherwise.
    362   """
    363   cmd = ['make', 'BUILDTYPE=%s' % build_type]
    364   if threads:
    365     cmd.append('-j%d' % threads)
    366   cmd += targets
    367   return_code = bisect_utils.RunProcess(cmd)
    368   return not return_code
    369 
    370 
    371 def BuildWithNinja(threads, targets, build_type='Release'):
    372   """Runs a ninja command with the given targets."""
    373   cmd = ['ninja', '-C', os.path.join('out', build_type)]
    374   if threads:
    375     cmd.append('-j%d' % threads)
    376   cmd += targets
    377   return_code = bisect_utils.RunProcess(cmd)
    378   return not return_code
    379 
    380 
    381 def BuildWithVisualStudio(targets, build_type='Release'):
    382   """Runs a command to build the given targets with Visual Studio."""
    383   path_to_devenv = os.path.abspath(
    384       os.path.join(os.environ['VS100COMNTOOLS'], '..', 'IDE', 'devenv.com'))
    385   path_to_sln = os.path.join(os.getcwd(), 'chrome', 'chrome.sln')
    386   cmd = [path_to_devenv, '/build', build_type, path_to_sln]
    387   for t in targets:
    388     cmd.extend(['/Project', t])
    389   return_code = bisect_utils.RunProcess(cmd)
    390   return not return_code
    391 
    392 
    393 def CopyAndSaveOriginalEnvironmentVars():
    394   """Makes a copy of the current environment variables.
    395 
    396   Before making a copy of the environment variables and setting a global
    397   variable, this function unsets a certain set of environment variables.
    398   """
    399   # TODO: Waiting on crbug.com/255689, will remove this after.
    400   vars_to_remove = [
    401       'CHROME_SRC',
    402       'CHROMIUM_GYP_FILE',
    403       'GYP_CROSSCOMPILE',
    404       'GYP_DEFINES',
    405       'GYP_GENERATORS',
    406       'GYP_GENERATOR_FLAGS',
    407       'OBJCOPY',
    408   ]
    409   for key in os.environ:
    410     if 'ANDROID' in key:
    411       vars_to_remove.append(key)
    412   for key in vars_to_remove:
    413     if os.environ.has_key(key):
    414       del os.environ[key]
    415 
    416   global ORIGINAL_ENV
    417   ORIGINAL_ENV = os.environ.copy()
    418 
    419 
    420 def SetupAndroidBuildEnvironment(opts, path_to_src=None):
    421   """Sets up the android build environment.
    422 
    423   Args:
    424     opts: The options parsed from the command line through parse_args().
    425     path_to_src: Path to the src checkout.
    426 
    427   Returns:
    428     True if successful.
    429   """
    430   # Revert the environment variables back to default before setting them up
    431   # with envsetup.sh.
    432   env_vars = os.environ.copy()
    433   for k, _ in env_vars.iteritems():
    434     del os.environ[k]
    435   for k, v in ORIGINAL_ENV.iteritems():
    436     os.environ[k] = v
    437 
    438   envsetup_path = os.path.join('build', 'android', 'envsetup.sh')
    439   proc = subprocess.Popen(['bash', '-c', 'source %s && env' % envsetup_path],
    440                           stdout=subprocess.PIPE,
    441                           stderr=subprocess.PIPE,
    442                           cwd=path_to_src)
    443   out, _ = proc.communicate()
    444 
    445   for line in out.splitlines():
    446     k, _, v = line.partition('=')
    447     os.environ[k] = v
    448 
    449   # envsetup.sh no longer sets OS=android in GYP_DEFINES environment variable.
    450   # (See http://crrev.com/170273005). So, we set this variable explicitly here
    451   # in order to build Chrome on Android.
    452   if 'GYP_DEFINES' not in os.environ:
    453     os.environ['GYP_DEFINES'] = 'OS=android'
    454   else:
    455     os.environ['GYP_DEFINES'] += ' OS=android'
    456 
    457   if opts.use_goma:
    458     os.environ['GYP_DEFINES'] += ' use_goma=1'
    459   return not proc.returncode
    460