Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(186)

Side by Side Diff: scripts/slave/bot_update.py

Issue 274493002: Only apply DEPS patches before gclient sync, apply all other patches afterwards (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@master
Patch Set: Rebase Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
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())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698