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