| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 # TODO(hinoka): Use logging. | 6 # TODO(hinoka): Use logging. |
| 7 | 7 |
| 8 import codecs | 8 import codecs |
| 9 import copy | 9 import copy |
| 10 import cStringIO | 10 import cStringIO |
| (...skipping 246 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 257 | 257 |
| 258 | 258 |
| 259 class GclientSyncFailed(SubprocessFailed): | 259 class GclientSyncFailed(SubprocessFailed): |
| 260 pass | 260 pass |
| 261 | 261 |
| 262 | 262 |
| 263 class SVNRevisionNotFound(Exception): | 263 class SVNRevisionNotFound(Exception): |
| 264 pass | 264 pass |
| 265 | 265 |
| 266 | 266 |
| 267 class InvalidDiff(Exception): |
| 268 pass |
| 269 |
| 270 |
| 267 def call(*args, **kwargs): | 271 def call(*args, **kwargs): |
| 268 """Interactive subprocess call.""" | 272 """Interactive subprocess call.""" |
| 269 kwargs['stdout'] = subprocess.PIPE | 273 kwargs['stdout'] = subprocess.PIPE |
| 270 kwargs['stderr'] = subprocess.STDOUT | 274 kwargs['stderr'] = subprocess.STDOUT |
| 271 stdin_data = kwargs.pop('stdin_data', None) | 275 stdin_data = kwargs.pop('stdin_data', None) |
| 276 tries = kwargs.pop('tries', RETRIES) |
| 272 if stdin_data: | 277 if stdin_data: |
| 273 kwargs['stdin'] = subprocess.PIPE | 278 kwargs['stdin'] = subprocess.PIPE |
| 274 out = cStringIO.StringIO() | 279 out = cStringIO.StringIO() |
| 275 new_env = kwargs.get('env', {}) | 280 new_env = kwargs.get('env', {}) |
| 276 env = copy.copy(os.environ) | 281 env = copy.copy(os.environ) |
| 277 env.update(new_env) | 282 env.update(new_env) |
| 278 kwargs['env'] = env | 283 kwargs['env'] = env |
| 279 for attempt in xrange(RETRIES): | 284 for attempt in range(tries): |
| 280 attempt_msg = ' (retry #%d)' % attempt if attempt else '' | 285 attempt_msg = ' (retry #%d)' % attempt if attempt else '' |
| 281 if new_env: | 286 if new_env: |
| 282 print '===Injecting Environment Variables===' | 287 print '===Injecting Environment Variables===' |
| 283 for k, v in sorted(new_env.items()): | 288 for k, v in sorted(new_env.items()): |
| 284 print '%s: %s' % (k, v) | 289 print '%s: %s' % (k, v) |
| 285 print '===Running %s%s===' % (' '.join(args), attempt_msg) | 290 print '===Running %s%s===' % (' '.join(args), attempt_msg) |
| 286 start_time = time.time() | 291 start_time = time.time() |
| 287 proc = subprocess.Popen(args, **kwargs) | 292 proc = subprocess.Popen(args, **kwargs) |
| 288 if stdin_data: | 293 if stdin_data: |
| 289 proc.stdin.write(stdin_data) | 294 proc.stdin.write(stdin_data) |
| (...skipping 17 matching lines...) Expand all Loading... |
| 307 code = proc.wait() | 312 code = proc.wait() |
| 308 elapsed_time = ((time.time() - start_time) / 60.0) | 313 elapsed_time = ((time.time() - start_time) / 60.0) |
| 309 if not code: | 314 if not code: |
| 310 print '===Succeeded in %.1f mins===' % elapsed_time | 315 print '===Succeeded in %.1f mins===' % elapsed_time |
| 311 print | 316 print |
| 312 return out.getvalue() | 317 return out.getvalue() |
| 313 print '===Failed in %.1f mins===' % elapsed_time | 318 print '===Failed in %.1f mins===' % elapsed_time |
| 314 print | 319 print |
| 315 | 320 |
| 316 raise SubprocessFailed('%s failed with code %d in %s after %d attempts.' % | 321 raise SubprocessFailed('%s failed with code %d in %s after %d attempts.' % |
| 317 (' '.join(args), code, os.getcwd(), RETRIES), code) | 322 (' '.join(args), code, os.getcwd(), tries), code) |
| 318 | 323 |
| 319 | 324 |
| 320 def git(*args, **kwargs): | 325 def git(*args, **kwargs): |
| 321 """Wrapper around call specifically for Git commands.""" | 326 """Wrapper around call specifically for Git commands.""" |
| 322 if args and args[0] == 'cache': | 327 if args and args[0] == 'cache': |
| 323 # Rewrite "git cache" calls into "python git_cache.py". | 328 # Rewrite "git cache" calls into "python git_cache.py". |
| 324 cmd = (sys.executable, '-u', GIT_CACHE_PATH) + args[1:] | 329 cmd = (sys.executable, '-u', GIT_CACHE_PATH) + args[1:] |
| 325 else: | 330 else: |
| 326 git_executable = 'git' | 331 git_executable = 'git' |
| 327 # On windows, subprocess doesn't fuzzy-match 'git' to 'git.bat', so we | 332 # On windows, subprocess doesn't fuzzy-match 'git' to 'git.bat', so we |
| (...skipping 215 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 543 | 548 |
| 544 | 549 |
| 545 def _last_commit_for_file(filename, repo_base): | 550 def _last_commit_for_file(filename, repo_base): |
| 546 cmd = ['log', '--format=%H', '--max-count=1', '--', filename] | 551 cmd = ['log', '--format=%H', '--max-count=1', '--', filename] |
| 547 return git(*cmd, cwd=repo_base).strip() | 552 return git(*cmd, cwd=repo_base).strip() |
| 548 | 553 |
| 549 | 554 |
| 550 def need_to_run_deps2git(repo_base, deps_file, deps_git_file): | 555 def need_to_run_deps2git(repo_base, deps_file, deps_git_file): |
| 551 """Checks to see if we need to run deps2git. | 556 """Checks to see if we need to run deps2git. |
| 552 | 557 |
| 553 Returns True if there was a DEPS change after the last .DEPS.git update. | 558 Returns True if there was a DEPS change after the last .DEPS.git update |
| 559 or if DEPS has local modifications. |
| 554 """ | 560 """ |
| 555 print 'Checking if %s exists' % deps_git_file | 561 print 'Checking if %s exists' % deps_git_file |
| 556 if not path.isfile(deps_git_file): | 562 if not path.isfile(deps_git_file): |
| 557 # .DEPS.git doesn't exist but DEPS does? We probably want to generate one. | 563 # .DEPS.git doesn't exist but DEPS does? We probably want to generate one. |
| 558 print 'it exists!' | 564 print 'it doesn\'t exist!' |
| 565 return True |
| 566 |
| 567 if git('diff', deps_file, cwd=repo_base).strip(): |
| 559 return True | 568 return True |
| 560 | 569 |
| 561 last_known_deps_ref = _last_commit_for_file(deps_file, repo_base) | 570 last_known_deps_ref = _last_commit_for_file(deps_file, repo_base) |
| 562 last_known_deps_git_ref = _last_commit_for_file(deps_git_file, repo_base) | 571 last_known_deps_git_ref = _last_commit_for_file(deps_git_file, repo_base) |
| 563 merge_base_ref = git('merge-base', last_known_deps_ref, | 572 merge_base_ref = git('merge-base', last_known_deps_ref, |
| 564 last_known_deps_git_ref, cwd=repo_base).strip() | 573 last_known_deps_git_ref, cwd=repo_base).strip() |
| 565 | 574 |
| 566 # If the merge base of the last DEPS and last .DEPS.git file is not | 575 # If the merge base of the last DEPS and last .DEPS.git file is not |
| 567 # equivilent to the hash of the last DEPS file, that means the DEPS file | 576 # equivilent to the hash of the last DEPS file, that means the DEPS file |
| 568 # was committed after the last .DEPS.git file. | 577 # was committed after the last .DEPS.git file. |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 616 version = m.group(1) | 625 version = m.group(1) |
| 617 git_buildspec = get_git_buildspec(version) | 626 git_buildspec = get_git_buildspec(version) |
| 618 with open(deps_git_file, 'wb') as f: | 627 with open(deps_git_file, 'wb') as f: |
| 619 f.write(git_buildspec) | 628 f.write(git_buildspec) |
| 620 | 629 |
| 621 | 630 |
| 622 def ensure_deps2git(sln_dir, shallow): | 631 def ensure_deps2git(sln_dir, shallow): |
| 623 repo_base = path.join(os.getcwd(), sln_dir) | 632 repo_base = path.join(os.getcwd(), sln_dir) |
| 624 deps_file = path.join(repo_base, 'DEPS') | 633 deps_file = path.join(repo_base, 'DEPS') |
| 625 deps_git_file = path.join(repo_base, '.DEPS.git') | 634 deps_git_file = path.join(repo_base, '.DEPS.git') |
| 626 print 'Checking if %s is newer than %s' % (deps_file, deps_git_file) | |
| 627 if not path.isfile(deps_file): | 635 if not path.isfile(deps_file): |
| 628 return | 636 return |
| 629 | 637 |
| 638 print 'Checking if %s is newer than %s' % (deps_file, deps_git_file) |
| 630 if not need_to_run_deps2git(repo_base, deps_file, deps_git_file): | 639 if not need_to_run_deps2git(repo_base, deps_file, deps_git_file): |
| 631 return | 640 return |
| 632 | 641 |
| 633 print '===DEPS file modified, need to run deps2git===' | 642 print '===DEPS file modified, need to run deps2git===' |
| 634 # Magic to get deps2git to work with internal DEPS. | 643 # Magic to get deps2git to work with internal DEPS. |
| 635 shutil.copyfile(S2G_INTERNAL_FROM_PATH, S2G_INTERNAL_DEST_PATH) | 644 shutil.copyfile(S2G_INTERNAL_FROM_PATH, S2G_INTERNAL_DEST_PATH) |
| 636 | 645 |
| 637 # TODO(hinoka): This might need to be smarter if we need to deal with | 646 # TODO(hinoka): This might need to be smarter if we need to deal with |
| 638 # DEPS changes that are in an internal repository. | 647 # DEPS changes that are in an internal repository. |
| 639 repo_type = 'public' | 648 repo_type = 'public' |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 779 def _download(url): | 788 def _download(url): |
| 780 """Fetch url and return content, with retries for flake.""" | 789 """Fetch url and return content, with retries for flake.""" |
| 781 for attempt in xrange(RETRIES): | 790 for attempt in xrange(RETRIES): |
| 782 try: | 791 try: |
| 783 return urllib2.urlopen(url).read() | 792 return urllib2.urlopen(url).read() |
| 784 except Exception: | 793 except Exception: |
| 785 if attempt == RETRIES - 1: | 794 if attempt == RETRIES - 1: |
| 786 raise | 795 raise |
| 787 | 796 |
| 788 | 797 |
| 789 def apply_issue_svn(root, patch_url): | 798 def parse_diff(diff): |
| 790 patch_data = call('svn', 'cat', patch_url) | 799 """Takes a unified diff and returns a list of diffed files and their diffs. |
| 791 call(PATCH_TOOL, '-p0', '--remove-empty-files', '--force', '--forward', | 800 |
| 792 stdin_data=patch_data, cwd=root) | 801 The return format is a list of pairs of: |
| 802 (<filename>, <diff contents>) |
| 803 <diff contents> is inclusive of the diff line. |
| 804 """ |
| 805 result = [] |
| 806 current_diff = '' |
| 807 current_header = None |
| 808 for line in diff.splitlines(): |
| 809 # "diff" is for git style patches, and "Index: " is for SVN style patches. |
| 810 if line.startswith('diff') or line.startswith('Index: '): |
| 811 if current_header: |
| 812 # If we are in a diff portion, then save the diff. |
| 813 result.append((current_header, '%s\n' % current_diff)) |
| 814 git_header_match = re.match(r'diff (?:--git )?(\S+) (\S+)', line) |
| 815 svn_header_match = re.match(r'Index: (.*)', line) |
| 816 |
| 817 if git_header_match: |
| 818 # First, see if its a git style header. |
| 819 from_file = git_header_match.group(1) |
| 820 to_file = git_header_match.group(2) |
| 821 if from_file != to_file and from_file.startswith('a/'): |
| 822 # Sometimes git prepends 'a/' and 'b/' in front of file paths. |
| 823 from_file = from_file[2:] |
| 824 current_header = from_file |
| 825 |
| 826 elif svn_header_match: |
| 827 # Otherwise, check if its an SVN style header. |
| 828 current_header = svn_header_match.group(1) |
| 829 |
| 830 else: |
| 831 # Otherwise... I'm not really sure what to do with this. |
| 832 raise InvalidDiff('Can\'t process header: %s\nFull diff:\n%s' % |
| 833 (line, diff)) |
| 834 |
| 835 current_diff = '' |
| 836 current_diff += '%s\n' % line |
| 837 if current_header: |
| 838 # We hit EOF, gotta save the last diff. |
| 839 result.append((current_header, current_diff)) |
| 840 return result |
| 793 | 841 |
| 794 | 842 |
| 795 def apply_issue_rietveld(issue, patchset, root, server, rev_map, revision): | 843 def get_svn_patch(patch_url): |
| 844 """Fetch patch from patch_url, return list of (filename, diff)""" |
| 845 patch_data = call('svn', 'cat', patch_url) |
| 846 return parse_diff(patch_data) |
| 847 |
| 848 |
| 849 def apply_svn_patch(patch_root, patches, whitelist=None, blacklist=None): |
| 850 """Expects a list of (filename, diff), applies it on top of patch_root.""" |
| 851 if whitelist: |
| 852 patches = [(name, diff) for name, diff in patches if name in whitelist] |
| 853 elif blacklist: |
| 854 patches = [(name, diff) for name, diff in patches if name not in blacklist] |
| 855 diffs = [diff for _, diff in patches] |
| 856 patch = ''.join(diffs) |
| 857 |
| 858 if patch: |
| 859 print '===Patching files===' |
| 860 for filename, _ in patches: |
| 861 print 'Patching %s' % filename |
| 862 call(PATCH_TOOL, '-p0', '--remove-empty-files', '--force', '--forward', |
| 863 stdin_data=patch, cwd=patch_root, tries=1) |
| 864 |
| 865 |
| 866 def apply_rietveld_issue(issue, patchset, root, server, rev_map, revision, |
| 867 whitelist=None, blacklist=None): |
| 796 apply_issue_bin = ('apply_issue.bat' if sys.platform.startswith('win') | 868 apply_issue_bin = ('apply_issue.bat' if sys.platform.startswith('win') |
| 797 else 'apply_issue') | 869 else 'apply_issue') |
| 798 call(apply_issue_bin, | 870 cmd = [apply_issue_bin, |
| 799 '--root_dir', root, | 871 # The patch will be applied on top of this directory. |
| 800 '--issue', issue, | 872 '--root_dir', root, |
| 801 '--patchset', patchset, | 873 # Tell apply_issue how to fetch the patch. |
| 802 '--no-auth', | 874 '--issue', issue, |
| 803 '--server', server, | 875 '--patchset', patchset, |
| 804 '--base_ref', revision, | 876 '--no-auth', |
| 805 '--force', | 877 '--server', server, |
| 806 '--ignore_deps') | 878 # Always run apply_issue.py, otherwise it would see update.flag |
| 879 # and then bail out. |
| 880 '--force', |
| 881 # Don't run gclient sync when it sees a DEPS change. |
| 882 '--ignore_deps', |
| 883 # Don't commit the patch or add it to the index. |
| 884 '--no_commit', |
| 885 ] |
| 886 if whitelist: |
| 887 for item in whitelist: |
| 888 cmd.extend(['--whitelist', item]) |
| 889 elif blacklist: |
| 890 for item in blacklist: |
| 891 cmd.extend(['--blacklist', item]) |
| 892 |
| 893 # Only try once, since subsequent failures hide the real failure. |
| 894 call(*cmd, tries=1) |
| 807 | 895 |
| 808 | 896 |
| 809 def check_flag(flag_file): | 897 def check_flag(flag_file): |
| 810 """Returns True if the flag file is present.""" | 898 """Returns True if the flag file is present.""" |
| 811 return os.path.isfile(flag_file) | 899 return os.path.isfile(flag_file) |
| 812 | 900 |
| 813 | 901 |
| 814 def delete_flag(flag_file): | 902 def delete_flag(flag_file): |
| 815 """Remove bot update flag.""" | 903 """Remove bot update flag.""" |
| 816 if os.path.isfile(flag_file): | 904 if os.path.isfile(flag_file): |
| 817 os.remove(flag_file) | 905 os.remove(flag_file) |
| 818 | 906 |
| 819 | 907 |
| 820 def emit_flag(flag_file): | 908 def emit_flag(flag_file): |
| 821 """Deposit a bot update flag on the system to tell gclient not to run.""" | 909 """Deposit a bot update flag on the system to tell gclient not to run.""" |
| 822 print 'Emitting flag file at %s' % flag_file | 910 print 'Emitting flag file at %s' % flag_file |
| 823 with open(flag_file, 'wb') as f: | 911 with open(flag_file, 'wb') as f: |
| 824 f.write('Success!') | 912 f.write('Success!') |
| 825 | 913 |
| 826 | 914 |
| 827 def get_real_git_hash(git_hash, dir_name): | |
| 828 """Return git_hash or the parent of git_hash if its a patched hash.""" | |
| 829 log = git('log', '-1', '--format=%B', cwd=dir_name).strip() | |
| 830 if 'committed patch' in log.lower(): | |
| 831 return git('log', '-1', '--format=%H', 'HEAD^', cwd=dir_name).strip() | |
| 832 return git_hash | |
| 833 | |
| 834 | |
| 835 def parse_got_revision(gclient_output, got_revision_mapping, use_svn_revs): | 915 def parse_got_revision(gclient_output, got_revision_mapping, use_svn_revs): |
| 836 """Translate git gclient revision mapping to build properties. | 916 """Translate git gclient revision mapping to build properties. |
| 837 | 917 |
| 838 If use_svn_revs is True, then translate git hashes in the revision mapping | 918 If use_svn_revs is True, then translate git hashes in the revision mapping |
| 839 to svn revision numbers. | 919 to svn revision numbers. |
| 840 """ | 920 """ |
| 841 properties = {} | 921 properties = {} |
| 842 solutions_output = gclient_output['solutions'] | 922 solutions_output = gclient_output['solutions'] |
| 843 for dir_name, property_name in got_revision_mapping.iteritems(): | 923 for dir_name, property_name in got_revision_mapping.iteritems(): |
| 844 if dir_name not in solutions_output: | 924 if dir_name not in solutions_output: |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 890 | 970 |
| 891 def ensure_checkout(solutions, revisions, first_sln, target_os, target_os_only, | 971 def ensure_checkout(solutions, revisions, first_sln, target_os, target_os_only, |
| 892 root, issue, patchset, patch_url, rietveld_server, | 972 root, issue, patchset, patch_url, rietveld_server, |
| 893 revision_mapping, buildspec_name, gyp_env, shallow): | 973 revision_mapping, buildspec_name, gyp_env, shallow): |
| 894 # Get a checkout of each solution, without DEPS or hooks. | 974 # Get a checkout of each solution, without DEPS or hooks. |
| 895 # Calling git directly because there is no way to run Gclient without | 975 # Calling git directly because there is no way to run Gclient without |
| 896 # invoking DEPS. | 976 # invoking DEPS. |
| 897 print 'Fetching Git checkout' | 977 print 'Fetching Git checkout' |
| 898 git_ref = git_checkout(solutions, revisions, shallow) | 978 git_ref = git_checkout(solutions, revisions, shallow) |
| 899 | 979 |
| 900 # If either patch_url or issue is passed in, then we need to apply a patch. | 980 patches = None |
| 901 if patch_url: | 981 if patch_url: |
| 902 # patch_url takes precidence since its only passed in on gcl try/git try. | 982 patches = get_svn_patch(patch_url) |
| 903 apply_issue_svn(root, patch_url) | 983 |
| 904 elif issue: | 984 if root == first_sln: |
| 905 apply_issue_rietveld(issue, patchset, root, rietveld_server, | 985 # Only top level DEPS patching is supported right now. |
| 906 revision_mapping, git_ref) | 986 if patches: |
| 987 apply_svn_patch(root, patches, whitelist=['DEPS']) |
| 988 elif issue: |
| 989 apply_rietveld_issue(issue, patchset, root, rietveld_server, |
| 990 revision_mapping, git_ref, whitelist=['DEPS']) |
| 991 |
| 907 | 992 |
| 908 if buildspec_name: | 993 if buildspec_name: |
| 909 buildspecs2git(root, buildspec_name) | 994 buildspecs2git(root, buildspec_name) |
| 910 elif first_sln == root: | 995 else: |
| 911 # Run deps2git if there is a DEPS commit after the last .DEPS.git commit. | 996 # Run deps2git if there is a DEPS change after the last .DEPS.git commit. |
| 912 # We only need to ensure deps2git if the root is not overridden, since | |
| 913 # if the root is overridden, it means we are working with a sub repository | |
| 914 # patch, which means its impossible for it to touch DEPS. | |
| 915 ensure_deps2git(root, shallow) | 997 ensure_deps2git(root, shallow) |
| 916 | 998 |
| 917 # Ensure our build/ directory is set up with the correct .gclient file. | 999 # Ensure our build/ directory is set up with the correct .gclient file. |
| 918 gclient_configure(solutions, target_os, target_os_only) | 1000 gclient_configure(solutions, target_os, target_os_only) |
| 919 | 1001 |
| 920 # Let gclient do the DEPS syncing. | 1002 # Let gclient do the DEPS syncing. |
| 921 gclient_output = gclient_sync(buildspec_name) | 1003 gclient_output = gclient_sync(buildspec_name) |
| 922 if buildspec_name: | 1004 if buildspec_name: |
| 923 # Run gclient runhooks if we're on an official builder. | 1005 # Run gclient runhooks if we're on an official builder. |
| 924 # TODO(hinoka): Remove this when the official builders run their own | 1006 # TODO(hinoka): Remove this when the official builders run their own |
| 925 # runhooks step. | 1007 # runhooks step. |
| 926 gclient_runhooks(gyp_env) | 1008 gclient_runhooks(gyp_env) |
| 927 | 1009 |
| 928 # Finally, ensure that all DEPS are pinned to the correct revision. | 1010 # Finally, ensure that all DEPS are pinned to the correct revision. |
| 929 dir_names = [sln['name'] for sln in solutions] | 1011 dir_names = [sln['name'] for sln in solutions] |
| 930 ensure_deps_revisions(gclient_output.get('solutions', {}), | 1012 ensure_deps_revisions(gclient_output.get('solutions', {}), |
| 931 dir_names, revisions) | 1013 dir_names, revisions) |
| 1014 # Apply the rest of the patch here (sans DEPS) |
| 1015 if patches: |
| 1016 apply_svn_patch(root, patches, blacklist=['DEPS']) |
| 1017 elif issue: |
| 1018 apply_rietveld_issue(issue, patchset, root, rietveld_server, |
| 1019 revision_mapping, git_ref, blacklist=['DEPS']) |
| 1020 |
| 932 return gclient_output | 1021 return gclient_output |
| 933 | 1022 |
| 934 | 1023 |
| 935 class UploadTelemetryThread(threading.Thread): | 1024 class UploadTelemetryThread(threading.Thread): |
| 936 def __init__(self, prefix, master, builder, slave, kwargs): | 1025 def __init__(self, prefix, master, builder, slave, kwargs): |
| 937 super(UploadTelemetryThread, self).__init__() | 1026 super(UploadTelemetryThread, self).__init__() |
| 938 self.master = master | 1027 self.master = master |
| 939 self.builder = builder | 1028 self.builder = builder |
| 940 self.slave = slave | 1029 self.slave = slave |
| 941 self.prefix = prefix | 1030 self.prefix = prefix |
| (...skipping 282 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1224 specs=options.specs) | 1313 specs=options.specs) |
| 1225 all_threads.append(thr) | 1314 all_threads.append(thr) |
| 1226 | 1315 |
| 1227 # Sort of wait for all telemetry threads to finish. | 1316 # Sort of wait for all telemetry threads to finish. |
| 1228 for thr in all_threads: | 1317 for thr in all_threads: |
| 1229 thr.join(5) | 1318 thr.join(5) |
| 1230 | 1319 |
| 1231 | 1320 |
| 1232 if __name__ == '__main__': | 1321 if __name__ == '__main__': |
| 1233 sys.exit(main()) | 1322 sys.exit(main()) |
| OLD | NEW |