Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import argparse | 5 import argparse |
| 6 import datetime | 6 import datetime |
| 7 import difflib | 7 import difflib |
| 8 import random | 8 import random |
| 9 import re | 9 import re |
| 10 import urllib | 10 import urllib |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 111 # Default failure retry. | 111 # Default failure retry. |
| 112 self.rerun_failures_count = 2 | 112 self.rerun_failures_count = 2 |
| 113 | 113 |
| 114 # If tests are run, this value will be set to their total duration. | 114 # If tests are run, this value will be set to their total duration. |
| 115 self.test_duration_sec = 0 | 115 self.test_duration_sec = 0 |
| 116 | 116 |
| 117 # Allow overriding the isolate hashes during bisection with the ones that | 117 # Allow overriding the isolate hashes during bisection with the ones that |
| 118 # correspond to the build of a bisect step. | 118 # correspond to the build of a bisect step. |
| 119 self._isolated_tests_override = None | 119 self._isolated_tests_override = None |
| 120 | 120 |
| 121 # This is inferred from the run_mb step or from the parent bot. If mb is | |
| 122 # run multiple times, it is overwritten. It contains either gyp or gn | |
| 123 # properties. | |
| 124 self.build_environment = self.m.properties.get( | |
| 125 'parent_build_environment', {}) | |
| 126 | |
| 121 @property | 127 @property |
| 122 def isolated_tests(self): | 128 def isolated_tests(self): |
| 123 # During bisection, the isolated hashes will be updated with hashes that | 129 # During bisection, the isolated hashes will be updated with hashes that |
| 124 # correspond to the bisect step. | 130 # correspond to the bisect step. |
| 125 # TODO(machenbach): Remove pragma as soon as rerun is implemented for | 131 # TODO(machenbach): Remove pragma as soon as rerun is implemented for |
| 126 # swarming. | 132 # swarming. |
| 127 if self._isolated_tests_override is not None: # pragma: no cover | 133 if self._isolated_tests_override is not None: # pragma: no cover |
| 128 return self._isolated_tests_override | 134 return self._isolated_tests_override |
| 129 return self.m.isolate.isolated_tests | 135 return self.m.isolate.isolated_tests |
| 130 | 136 |
| (...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 295 build_dir=gn_build_dir, | 301 build_dir=gn_build_dir, |
| 296 ok_ret='any', | 302 ok_ret='any', |
| 297 ) | 303 ) |
| 298 self.m.python( | 304 self.m.python( |
| 299 'compare build flags (fyi)', | 305 'compare build flags (fyi)', |
| 300 self.m.path['checkout'].join('tools', 'gyp_flag_compare.py'), | 306 self.m.path['checkout'].join('tools', 'gyp_flag_compare.py'), |
| 301 [gn_build_dir, gyp_build_dir, 'all', 'all'], | 307 [gn_build_dir, gyp_build_dir, 'all', 'all'], |
| 302 ok_ret='any', | 308 ok_ret='any', |
| 303 ) | 309 ) |
| 304 | 310 |
| 305 @property | |
| 306 def build_environment(self): | |
| 307 if self.m.properties.get('parent_build_environment'): | |
| 308 return self.m.properties['parent_build_environment'] | |
| 309 if self.bot_type == 'tester': | |
| 310 return None | |
| 311 build_environment = dict( | |
| 312 (k, v) for (k, v) in self.m.chromium.c.gyp_env.as_jsonish().iteritems() | |
| 313 if k.startswith('GYP') and v is not None | |
| 314 ) | |
| 315 build_environment.update(self.c.gyp_env.as_jsonish()) | |
| 316 if 'GYP_DEFINES' in build_environment: | |
|
Michael Achenbach
2016/07/05 14:36:48
This part moves to the update_build_environment me
| |
| 317 # Filter out gomadir. | |
| 318 build_environment['GYP_DEFINES'] = ' '.join( | |
| 319 d for d in build_environment['GYP_DEFINES'].split() | |
| 320 if not d.startswith('gomadir') | |
| 321 ) | |
| 322 return build_environment | |
| 323 | |
| 324 def setup_mips_toolchain(self): | 311 def setup_mips_toolchain(self): |
| 325 mips_dir = self.m.path['slave_build'].join(MIPS_DIR, 'bin') | 312 mips_dir = self.m.path['slave_build'].join(MIPS_DIR, 'bin') |
| 326 if not self.m.path.exists(mips_dir): | 313 if not self.m.path.exists(mips_dir): |
| 327 self.m.gsutil.download_url( | 314 self.m.gsutil.download_url( |
| 328 'gs://chromium-v8/%s' % MIPS_TOOLCHAIN, | 315 'gs://chromium-v8/%s' % MIPS_TOOLCHAIN, |
| 329 self.m.path['slave_build'], | 316 self.m.path['slave_build'], |
| 330 name='bootstrapping mips toolchain') | 317 name='bootstrapping mips toolchain') |
| 331 self.m.step('unzipping', | 318 self.m.step('unzipping', |
| 332 ['tar', 'xf', MIPS_TOOLCHAIN], | 319 ['tar', 'xf', MIPS_TOOLCHAIN], |
| 333 cwd=self.m.path['slave_build']) | 320 cwd=self.m.path['slave_build']) |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 398 if tests_to_isolate: | 385 if tests_to_isolate: |
| 399 self.m.isolate.isolate_tests( | 386 self.m.isolate.isolate_tests( |
| 400 self.m.chromium.output_dir, | 387 self.m.chromium.output_dir, |
| 401 targets=sorted(list(set(tests_to_isolate))), | 388 targets=sorted(list(set(tests_to_isolate))), |
| 402 verbose=True, | 389 verbose=True, |
| 403 set_swarm_hashes=False, | 390 set_swarm_hashes=False, |
| 404 ) | 391 ) |
| 405 if self.should_upload_build: | 392 if self.should_upload_build: |
| 406 self.upload_isolated_json() | 393 self.upload_isolated_json() |
| 407 | 394 |
| 408 def _compare_gyp_defines(self, mb_output): | |
| 409 """Compare infra gyp flags with client gyp flags. | |
| 410 | 395 |
| 411 Returns the difference as a list of strings or an empty list if there is | 396 def _gyp_defines_to_dict(self, gyp_defines): |
|
Michael Achenbach
2016/07/05 14:36:48
This is just extracted one level up. No code chang
| |
| 412 none. | 397 # Example input: "foo=1 bar='buz bing'". We assume there's no '=' in the |
| 398 # value part. | |
| 399 result = [] | |
| 400 for x in gyp_defines.split(): | |
| 401 kv = x.split('=', 1) | |
| 402 if len(kv) == 1: | |
| 403 # No '=' in x. It's part of a quoted string containing a space. | |
| 404 # Append it to the last value. | |
| 405 result[-1][1] += (' ' + kv[0]) | |
| 406 else: | |
| 407 result.append(kv) | |
| 408 return dict(tuple(kv) for kv in result) | |
| 409 | |
| 410 def _infer_build_environment(self, mb_output): | |
| 411 """Scans for gyp or gn properties in mb output. | |
| 412 | |
| 413 Returns: dict, where key is e.g. 'GYP_DEFINES' or 'gn_args'. | |
| 413 """ | 414 """ |
| 414 | 415 result = {} |
| 415 def gyp_defines_to_dict(gyp_defines): | |
| 416 # Example input: "foo=1 bar='buz bing'". We assume there's no '=' in the | |
| 417 # value part. | |
| 418 result = [] | |
| 419 for x in gyp_defines.split(): | |
| 420 kv = x.split('=', 1) | |
| 421 if len(kv) == 1: | |
| 422 # No '=' in x. It's part of a quoted string containing a space. | |
| 423 # Append it to the last value. | |
| 424 result[-1][1] += (' ' + kv[0]) | |
| 425 else: | |
| 426 result.append(kv) | |
| 427 return dict(tuple(kv) for kv in result) | |
| 428 | |
| 429 infra_flags = gyp_defines_to_dict( | |
| 430 self.m.chromium.c.gyp_env.as_jsonish()['GYP_DEFINES']) | |
| 431 | |
| 432 # Get the client's gyp flags from MB's output. Group 1 captures with posix, | 416 # Get the client's gyp flags from MB's output. Group 1 captures with posix, |
| 433 # group 2 with windows output semantics. | 417 # group 2 with windows output semantics. |
| 434 # | 418 # |
| 435 # Posix: | 419 # Posix: |
| 436 # GYP_DEFINES='foo=1 path=a/b/c' | 420 # GYP_DEFINES='foo=1 path=a/b/c' |
| 437 # | 421 # |
| 438 # Windows: | 422 # Windows: |
| 439 # set GYP_DEFINES=foo=1 path='a/b/c' | 423 # set GYP_DEFINES=foo=1 path='a/b/c' |
| 440 match = re.search( | 424 # TODO(machenbach): Remove the gyp case after gyp is deprecated. |
| 441 '^(?:set )?GYP_DEFINES=(?:(?:\'(.*)\')|(?:(.*)))$', mb_output, re.M) | 425 for match in re.finditer( |
| 426 '^(?:set )?GYP_([^=]*)=(?:(?:\'(.*)\')|(?:(.*)))$', mb_output, re.M): | |
|
Michael Achenbach
2016/07/05 14:36:48
Regexp is unchanged except for GYP_([^=]*). This a
| |
| 427 # Yield the property name (e.g. GYP_DEFINES) and the value. Either the | |
| 428 # windows or the posix group matches. | |
| 429 result['GYP_' + match.group(1)] = match.group(2) or match.group(3) | |
| 442 | 430 |
| 443 # This won't match in the gn case. | 431 # Check if the output looks like gn. Space-join all gn args, except |
| 432 # goma_dir. | |
| 433 # TODO(machenbach): Instead of scanning the output, we could also read | |
| 434 # the gn.args file that was written. | |
| 435 match = re.search(r'Writing """\\?\s*(.*)""" to ', mb_output, re.S) | |
|
Michael Achenbach
2016/07/05 14:36:48
This matches e.g.:
https://build.chromium.org/p/cl
| |
| 444 if match: | 436 if match: |
| 445 client_flags = gyp_defines_to_dict(match.group(1) or match.group(2)) | 437 result['gn_args'] = ' '.join( |
| 438 l for l in match.group(1).strip().splitlines() | |
| 439 if not l.startswith('goma_dir')) | |
| 440 | |
| 441 return result | |
| 442 | |
| 443 def _compare_gyp_defines(self, mb_output): | |
|
Michael Achenbach
2016/07/05 14:36:48
This method will go away soon.
| |
| 444 """Compare infra gyp flags with client gyp flags. | |
| 445 | |
| 446 Returns the difference as a list of strings or an empty list if there is | |
| 447 none. | |
| 448 """ | |
| 449 infra_flags = self._gyp_defines_to_dict( | |
| 450 self.m.chromium.c.gyp_env.as_jsonish()['GYP_DEFINES']) | |
| 451 | |
| 452 props = self._infer_build_environment(mb_output) | |
| 453 if 'GYP_DEFINES' in props: | |
| 454 client_flags = self._gyp_defines_to_dict(props['GYP_DEFINES']) | |
| 446 | 455 |
| 447 # Tweak both dictionaries for known differences. | 456 # Tweak both dictionaries for known differences. |
| 448 if infra_flags.get('target_arch') == infra_flags.get('v8_target_arch'): | 457 if infra_flags.get('target_arch') == infra_flags.get('v8_target_arch'): |
| 449 # We drop the default case target_arch==v8_target_arch in MB and | 458 # We drop the default case target_arch==v8_target_arch in MB and |
| 450 # only specify target_arch. | 459 # only specify target_arch. |
| 451 infra_flags.pop('v8_target_arch') # pragma: no cover | 460 infra_flags.pop('v8_target_arch') # pragma: no cover |
| 452 | 461 |
| 453 if 'jsfunfuzz' in infra_flags: | 462 if 'jsfunfuzz' in infra_flags: |
| 454 # This is for runhooks only. Not used in MB. | 463 # This is for runhooks only. Not used in MB. |
| 455 infra_flags.pop('jsfunfuzz') # pragma: no cover | 464 infra_flags.pop('jsfunfuzz') # pragma: no cover |
| 456 | 465 |
| 457 if not 'component' in infra_flags and 'component' in client_flags: | 466 if not 'component' in infra_flags and 'component' in client_flags: |
| 458 # We make this explicit with MB but used the default without. Only | 467 # We make this explicit with MB but used the default without. Only |
| 459 # compare if we specified it explicitly in the infrastructure. | 468 # compare if we specified it explicitly in the infrastructure. |
| 460 client_flags.pop('component') # pragma: no cover | 469 client_flags.pop('component') # pragma: no cover |
| 461 | 470 |
| 462 if infra_flags != client_flags: | 471 if infra_flags != client_flags: |
| 463 to_str = lambda x: sorted('%s: %s' % kv for kv in x.iteritems()) | 472 to_str = lambda x: sorted('%s: %s' % kv for kv in x.iteritems()) |
| 464 return list(difflib.ndiff(to_str(infra_flags), to_str(client_flags))) | 473 return list(difflib.ndiff(to_str(infra_flags), to_str(client_flags))) |
| 465 return [] # pragma: no cover | 474 return [] # pragma: no cover |
| 466 | 475 |
| 476 def _update_build_environment(self, mb_output): | |
| 477 build_environment = self._infer_build_environment(mb_output) | |
| 478 if 'GYP_DEFINES' in build_environment: | |
| 479 # Filter out gomadir. | |
| 480 build_environment['GYP_DEFINES'] = ' '.join( | |
| 481 d for d in build_environment['GYP_DEFINES'].split() | |
| 482 if not d.startswith('gomadir') | |
| 483 ) | |
| 484 self.build_environment = build_environment | |
| 485 | |
| 467 def compile(self, **kwargs): | 486 def compile(self, **kwargs): |
| 468 if self.m.chromium.c.project_generator.tool == 'mb': | 487 if self.m.chromium.c.project_generator.tool == 'mb': |
| 469 use_goma = (self.m.chromium.c.compile_py.compiler and | 488 use_goma = (self.m.chromium.c.compile_py.compiler and |
| 470 'goma' in self.m.chromium.c.compile_py.compiler) | 489 'goma' in self.m.chromium.c.compile_py.compiler) |
| 471 def step_test_data(): | 490 def step_test_data(): |
| 472 # Fake gyp flags. In the expectations, the flag comparison will | 491 # Fake gyp flags. In the expectations, the flag comparison will |
| 473 # complain a lot because the fake data is different. | 492 # complain a lot because the fake data is different. |
| 474 return self.m.raw_io.test_api.stream_output( | 493 return self.m.raw_io.test_api.stream_output( |
| 475 'some line\n' | 494 'some line\n' |
| 476 'GYP_DEFINES=\'target_arch=x64 cool_flag=a=1\'\n' | 495 'GYP_DEFINES=\'target_arch=x64 cool_flag=a=1\'\n' |
| 477 'moar\n' | 496 'moar\n' |
| 478 ) | 497 ) |
| 479 self.m.chromium.run_mb( | 498 self.m.chromium.run_mb( |
| 480 self.m.properties['mastername'], | 499 self.m.properties['mastername'], |
| 481 self.m.properties['buildername'], | 500 self.m.properties['buildername'], |
| 482 use_goma=use_goma, | 501 use_goma=use_goma, |
| 483 mb_config_path=self.m.path['checkout'].join( | 502 mb_config_path=self.m.path['checkout'].join( |
| 484 'infra', 'mb', 'mb_config.pyl'), | 503 'infra', 'mb', 'mb_config.pyl'), |
| 485 gyp_script=self.m.path.join('gypfiles', 'gyp_v8'), | 504 gyp_script=self.m.path.join('gypfiles', 'gyp_v8'), |
| 486 # TODO(machenbach): Remove the comparison after the mb switch and | 505 # TODO(machenbach): Remove the comparison after the mb switch and |
| 487 # once all gyp flags have been verified. | 506 # once all gyp flags have been verified. |
| 488 stdout=self.m.raw_io.output(), | 507 stdout=self.m.raw_io.output(), |
| 489 step_test_data=step_test_data, | 508 step_test_data=step_test_data, |
| 490 ) | 509 ) |
| 491 # Log captured output. | 510 # Log captured output. |
| 492 self.m.step.active_result.presentation.logs['stdout'] = ( | 511 self.m.step.active_result.presentation.logs['stdout'] = ( |
| 493 self.m.step.active_result.stdout.splitlines()) | 512 self.m.step.active_result.stdout.splitlines()) |
| 494 | 513 |
| 514 # Update the build environment dictionary, which is printed to the | |
| 515 # user on test failures for easier build reproduction. | |
| 516 self._update_build_environment(self.m.step.active_result.stdout) | |
| 517 | |
| 495 # Compare infra gyp flags with client gyp flags. | 518 # Compare infra gyp flags with client gyp flags. |
| 496 diff = self._compare_gyp_defines(self.m.step.active_result.stdout) | 519 diff = self._compare_gyp_defines(self.m.step.active_result.stdout) |
| 497 | 520 |
| 498 if diff: | 521 if diff: |
| 499 self.m.step.active_result.presentation.logs['diff'] = diff | 522 self.m.step.active_result.presentation.logs['diff'] = diff |
| 500 self.m.step.active_result.presentation.status = self.m.step.WARNING | 523 self.m.step.active_result.presentation.status = self.m.step.WARNING |
| 501 | 524 |
| 502 self.peek_gn() | 525 self.peek_gn() |
| 503 self.m.chromium.compile(**kwargs) | 526 self.m.chromium.compile(**kwargs) |
| 504 self.isolate_tests() | 527 self.isolate_tests() |
| (...skipping 755 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1260 def report_culprits(self, culprit_range): | 1283 def report_culprits(self, culprit_range): |
| 1261 assert culprit_range | 1284 assert culprit_range |
| 1262 if len(culprit_range) > 1: | 1285 if len(culprit_range) > 1: |
| 1263 text = 'Suspecting multiple commits' | 1286 text = 'Suspecting multiple commits' |
| 1264 else: | 1287 else: |
| 1265 text = 'Suspecting %s' % culprit_range[0][:8] | 1288 text = 'Suspecting %s' % culprit_range[0][:8] |
| 1266 | 1289 |
| 1267 step_result = self.m.step(text, cmd=None) | 1290 step_result = self.m.step(text, cmd=None) |
| 1268 for culprit in culprit_range: | 1291 for culprit in culprit_range: |
| 1269 step_result.presentation.links[culprit[:8]] = COMMIT_TEMPLATE % culprit | 1292 step_result.presentation.links[culprit[:8]] = COMMIT_TEMPLATE % culprit |
| OLD | NEW |