| 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 374 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 385 if tests_to_isolate: | 385 if tests_to_isolate: |
| 386 self.m.isolate.isolate_tests( | 386 self.m.isolate.isolate_tests( |
| 387 self.m.chromium.output_dir, | 387 self.m.chromium.output_dir, |
| 388 targets=sorted(list(set(tests_to_isolate))), | 388 targets=sorted(list(set(tests_to_isolate))), |
| 389 verbose=True, | 389 verbose=True, |
| 390 set_swarm_hashes=False, | 390 set_swarm_hashes=False, |
| 391 ) | 391 ) |
| 392 if self.should_upload_build: | 392 if self.should_upload_build: |
| 393 self.upload_isolated_json() | 393 self.upload_isolated_json() |
| 394 | 394 |
| 395 | 395 def _update_build_environment(self, mb_output): |
| 396 def _gyp_defines_to_dict(self, gyp_defines): | 396 """Sets the build_environment property based on gyp or gn properties in mb |
| 397 # Example input: "foo=1 bar='buz bing'". We assume there's no '=' in the | 397 output. |
| 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'. | |
| 414 """ | 398 """ |
| 415 result = {} | 399 self.build_environment = {} |
| 416 # Get the client's gyp flags from MB's output. Group 1 captures with posix, | 400 # Get the client's gyp flags from MB's output. Group 1 captures with posix, |
| 417 # group 2 with windows output semantics. | 401 # group 2 with windows output semantics. |
| 418 # | 402 # |
| 419 # Posix: | 403 # Posix: |
| 420 # GYP_DEFINES='foo=1 path=a/b/c' | 404 # GYP_DEFINES='foo=1 path=a/b/c' |
| 421 # | 405 # |
| 422 # Windows: | 406 # Windows: |
| 423 # set GYP_DEFINES=foo=1 path='a/b/c' | 407 # set GYP_DEFINES=foo=1 path='a/b/c' |
| 424 # TODO(machenbach): Remove the gyp case after gyp is deprecated. | 408 # TODO(machenbach): Remove the gyp case after gyp is deprecated. |
| 425 for match in re.finditer( | 409 for match in re.finditer( |
| 426 '^(?:set )?GYP_([^=]*)=(?:(?:\'(.*)\')|(?:(.*)))$', mb_output, re.M): | 410 '^(?:set )?GYP_([^=]*)=(?:(?:\'(.*)\')|(?:(.*)))$', mb_output, re.M): |
| 427 # Yield the property name (e.g. GYP_DEFINES) and the value. Either the | 411 # Yield the property name (e.g. GYP_DEFINES) and the value. Either the |
| 428 # windows or the posix group matches. | 412 # windows or the posix group matches. |
| 429 result['GYP_' + match.group(1)] = match.group(2) or match.group(3) | 413 self.build_environment['GYP_' + match.group(1)] = ( |
| 414 match.group(2) or match.group(3)) |
| 415 |
| 416 if 'GYP_DEFINES' in self.build_environment: |
| 417 # Filter out gomadir. |
| 418 self.build_environment['GYP_DEFINES'] = ' '.join( |
| 419 d for d in self.build_environment['GYP_DEFINES'].split() |
| 420 if not d.startswith('gomadir') |
| 421 ) |
| 430 | 422 |
| 431 # Check if the output looks like gn. Space-join all gn args, except | 423 # Check if the output looks like gn. Space-join all gn args, except |
| 432 # goma_dir. | 424 # goma_dir. |
| 433 # TODO(machenbach): Instead of scanning the output, we could also read | 425 # TODO(machenbach): Instead of scanning the output, we could also read |
| 434 # the gn.args file that was written. | 426 # the gn.args file that was written. |
| 435 match = re.search(r'Writing """\\?\s*(.*)""" to ', mb_output, re.S) | 427 match = re.search(r'Writing """\\?\s*(.*)""" to ', mb_output, re.S) |
| 436 if match: | 428 if match: |
| 437 result['gn_args'] = ' '.join( | 429 self.build_environment['gn_args'] = ' '.join( |
| 438 l for l in match.group(1).strip().splitlines() | 430 l for l in match.group(1).strip().splitlines() |
| 439 if not l.startswith('goma_dir')) | 431 if not l.startswith('goma_dir')) |
| 440 | 432 |
| 441 return result | |
| 442 | |
| 443 def _compare_gyp_defines(self, mb_output): | |
| 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']) | |
| 455 | |
| 456 # Tweak both dictionaries for known differences. | |
| 457 if infra_flags.get('target_arch') == infra_flags.get('v8_target_arch'): | |
| 458 # We drop the default case target_arch==v8_target_arch in MB and | |
| 459 # only specify target_arch. | |
| 460 infra_flags.pop('v8_target_arch') # pragma: no cover | |
| 461 | |
| 462 if 'jsfunfuzz' in infra_flags: | |
| 463 # This is for runhooks only. Not used in MB. | |
| 464 infra_flags.pop('jsfunfuzz') # pragma: no cover | |
| 465 | |
| 466 if not 'component' in infra_flags and 'component' in client_flags: | |
| 467 # We make this explicit with MB but used the default without. Only | |
| 468 # compare if we specified it explicitly in the infrastructure. | |
| 469 client_flags.pop('component') # pragma: no cover | |
| 470 | |
| 471 if infra_flags != client_flags: | |
| 472 to_str = lambda x: sorted('%s: %s' % kv for kv in x.iteritems()) | |
| 473 return list(difflib.ndiff(to_str(infra_flags), to_str(client_flags))) | |
| 474 return [] # pragma: no cover | |
| 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 | |
| 486 def compile(self, **kwargs): | 433 def compile(self, **kwargs): |
| 487 if self.m.chromium.c.project_generator.tool == 'mb': | 434 if self.m.chromium.c.project_generator.tool == 'mb': |
| 488 use_goma = (self.m.chromium.c.compile_py.compiler and | 435 use_goma = (self.m.chromium.c.compile_py.compiler and |
| 489 'goma' in self.m.chromium.c.compile_py.compiler) | 436 'goma' in self.m.chromium.c.compile_py.compiler) |
| 490 def step_test_data(): | 437 def step_test_data(): |
| 491 # Fake gyp flags. In the expectations, the flag comparison will | 438 # Fake gyp flags. |
| 492 # complain a lot because the fake data is different. | 439 # TODO(machenbach): Replace with gn args after the gn migration. |
| 493 return self.m.raw_io.test_api.stream_output( | 440 return self.m.raw_io.test_api.stream_output( |
| 494 'some line\n' | 441 'some line\n' |
| 495 'GYP_DEFINES=\'target_arch=x64 cool_flag=a=1\'\n' | 442 'GYP_DEFINES=\'target_arch=x64 cool_flag=a=1\'\n' |
| 496 'moar\n' | 443 'moar\n' |
| 497 ) | 444 ) |
| 498 self.m.chromium.run_mb( | 445 self.m.chromium.run_mb( |
| 499 self.m.properties['mastername'], | 446 self.m.properties['mastername'], |
| 500 self.m.properties['buildername'], | 447 self.m.properties['buildername'], |
| 501 use_goma=use_goma, | 448 use_goma=use_goma, |
| 502 mb_config_path=self.m.path['checkout'].join( | 449 mb_config_path=self.m.path['checkout'].join( |
| 503 'infra', 'mb', 'mb_config.pyl'), | 450 'infra', 'mb', 'mb_config.pyl'), |
| 504 gyp_script=self.m.path.join('gypfiles', 'gyp_v8'), | 451 gyp_script=self.m.path.join('gypfiles', 'gyp_v8'), |
| 505 # TODO(machenbach): Remove the comparison after the mb switch and | |
| 506 # once all gyp flags have been verified. | |
| 507 stdout=self.m.raw_io.output(), | 452 stdout=self.m.raw_io.output(), |
| 508 step_test_data=step_test_data, | 453 step_test_data=step_test_data, |
| 509 ) | 454 ) |
| 510 # Log captured output. | 455 # Log captured output. |
| 511 self.m.step.active_result.presentation.logs['stdout'] = ( | 456 self.m.step.active_result.presentation.logs['stdout'] = ( |
| 512 self.m.step.active_result.stdout.splitlines()) | 457 self.m.step.active_result.stdout.splitlines()) |
| 513 | 458 |
| 514 # Update the build environment dictionary, which is printed to the | 459 # Update the build environment dictionary, which is printed to the |
| 515 # user on test failures for easier build reproduction. | 460 # user on test failures for easier build reproduction. |
| 516 self._update_build_environment(self.m.step.active_result.stdout) | 461 self._update_build_environment(self.m.step.active_result.stdout) |
| 517 | 462 |
| 518 # Compare infra gyp flags with client gyp flags. | |
| 519 diff = self._compare_gyp_defines(self.m.step.active_result.stdout) | |
| 520 | |
| 521 if diff: | |
| 522 self.m.step.active_result.presentation.logs['diff'] = diff | |
| 523 self.m.step.active_result.presentation.status = self.m.step.WARNING | |
| 524 | |
| 525 self.peek_gn() | 463 self.peek_gn() |
| 526 self.m.chromium.compile(**kwargs) | 464 self.m.chromium.compile(**kwargs) |
| 527 self.isolate_tests() | 465 self.isolate_tests() |
| 528 | 466 |
| 529 # TODO(machenbach): This should move to a dynamorio module as soon as one | 467 # TODO(machenbach): This should move to a dynamorio module as soon as one |
| 530 # exists. | 468 # exists. |
| 531 def dr_compile(self): | 469 def dr_compile(self): |
| 532 self.m.file.makedirs( | 470 self.m.file.makedirs( |
| 533 'Create Build Dir', | 471 'Create Build Dir', |
| 534 self.m.path['slave_build'].join('dynamorio', 'build')) | 472 self.m.path['slave_build'].join('dynamorio', 'build')) |
| (...skipping 748 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1283 def report_culprits(self, culprit_range): | 1221 def report_culprits(self, culprit_range): |
| 1284 assert culprit_range | 1222 assert culprit_range |
| 1285 if len(culprit_range) > 1: | 1223 if len(culprit_range) > 1: |
| 1286 text = 'Suspecting multiple commits' | 1224 text = 'Suspecting multiple commits' |
| 1287 else: | 1225 else: |
| 1288 text = 'Suspecting %s' % culprit_range[0][:8] | 1226 text = 'Suspecting %s' % culprit_range[0][:8] |
| 1289 | 1227 |
| 1290 step_result = self.m.step(text, cmd=None) | 1228 step_result = self.m.step(text, cmd=None) |
| 1291 for culprit in culprit_range: | 1229 for culprit in culprit_range: |
| 1292 step_result.presentation.links[culprit[:8]] = COMMIT_TEMPLATE % culprit | 1230 step_result.presentation.links[culprit[:8]] = COMMIT_TEMPLATE % culprit |
| OLD | NEW |