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

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: Review fixes 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 import codecs 6 import codecs
7 import copy 7 import copy
8 import cStringIO 8 import cStringIO
9 import ctypes 9 import ctypes
10 import json 10 import json
(...skipping 206 matching lines...) Expand 10 before | Expand all | Expand 10 after
217 217
218 class SVNRevisionNotFound(Exception): 218 class SVNRevisionNotFound(Exception):
219 pass 219 pass
220 220
221 221
222 def call(*args, **kwargs): 222 def call(*args, **kwargs):
223 """Interactive subprocess call.""" 223 """Interactive subprocess call."""
224 kwargs['stdout'] = subprocess.PIPE 224 kwargs['stdout'] = subprocess.PIPE
225 kwargs['stderr'] = subprocess.STDOUT 225 kwargs['stderr'] = subprocess.STDOUT
226 stdin_data = kwargs.pop('stdin_data', None) 226 stdin_data = kwargs.pop('stdin_data', None)
227 tries = kwargs.pop('tries', RETRIES)
227 if stdin_data: 228 if stdin_data:
228 kwargs['stdin'] = subprocess.PIPE 229 kwargs['stdin'] = subprocess.PIPE
229 out = cStringIO.StringIO() 230 out = cStringIO.StringIO()
230 new_env = kwargs.get('env', {}) 231 new_env = kwargs.get('env', {})
231 env = copy.copy(os.environ) 232 env = copy.copy(os.environ)
232 env.update(new_env) 233 env.update(new_env)
233 kwargs['env'] = env 234 kwargs['env'] = env
234 for attempt in xrange(RETRIES): 235 for attempt in range(tries):
iannucci 2014/05/08 22:17:01 hm... why not xrange?
Ryan Tseng 2014/05/09 20:59:42 "range" sounds cleaner (xrange isn't even a word!)
235 attempt_msg = ' (retry #%d)' % attempt if attempt else '' 236 attempt_msg = ' (retry #%d)' % attempt if attempt else ''
236 if new_env: 237 if new_env:
237 print '===Injecting Environment Variables===' 238 print '===Injecting Environment Variables==='
238 for k, v in sorted(new_env.items()): 239 for k, v in sorted(new_env.items()):
239 print '%s: %s' % (k, v) 240 print '%s: %s' % (k, v)
240 print '===Running %s%s===' % (' '.join(args), attempt_msg) 241 print '===Running %s%s===' % (' '.join(args), attempt_msg)
241 start_time = time.time() 242 start_time = time.time()
242 proc = subprocess.Popen(args, **kwargs) 243 proc = subprocess.Popen(args, **kwargs)
243 if stdin_data: 244 if stdin_data:
244 proc.stdin.write(stdin_data) 245 proc.stdin.write(stdin_data)
(...skipping 17 matching lines...) Expand all
262 code = proc.wait() 263 code = proc.wait()
263 elapsed_time = ((time.time() - start_time) / 60.0) 264 elapsed_time = ((time.time() - start_time) / 60.0)
264 if not code: 265 if not code:
265 print '===Succeeded in %.1f mins===' % elapsed_time 266 print '===Succeeded in %.1f mins===' % elapsed_time
266 print 267 print
267 return out.getvalue() 268 return out.getvalue()
268 print '===Failed in %.1f mins===' % elapsed_time 269 print '===Failed in %.1f mins===' % elapsed_time
269 print 270 print
270 271
271 raise SubprocessFailed('%s failed with code %d in %s after %d attempts.' % 272 raise SubprocessFailed('%s failed with code %d in %s after %d attempts.' %
272 (' '.join(args), code, os.getcwd(), RETRIES), code) 273 (' '.join(args), code, os.getcwd(), tries), code)
273 274
274 275
275 def git(*args, **kwargs): 276 def git(*args, **kwargs):
276 """Wrapper around call specifically for Git commands.""" 277 """Wrapper around call specifically for Git commands."""
277 if args and args[0] == 'cache': 278 if args and args[0] == 'cache':
278 # Rewrite "git cache" calls into "python git_cache.py". 279 # Rewrite "git cache" calls into "python git_cache.py".
279 cmd = (sys.executable, '-u', GIT_CACHE_PATH) + args[1:] 280 cmd = (sys.executable, '-u', GIT_CACHE_PATH) + args[1:]
280 else: 281 else:
281 git_executable = 'git' 282 git_executable = 'git'
282 # On windows, subprocess doesn't fuzzy-match 'git' to 'git.bat', so we 283 # On windows, subprocess doesn't fuzzy-match 'git' to 'git.bat', so we
(...skipping 288 matching lines...) Expand 10 before | Expand all | Expand 10 after
571 572
572 573
573 def _last_commit_for_file(filename, repo_base): 574 def _last_commit_for_file(filename, repo_base):
574 cmd = ['log', '--format=%H', '--max-count=1', '--', filename] 575 cmd = ['log', '--format=%H', '--max-count=1', '--', filename]
575 return git(*cmd, cwd=repo_base).strip() 576 return git(*cmd, cwd=repo_base).strip()
576 577
577 578
578 def need_to_run_deps2git(repo_base, deps_file, deps_git_file): 579 def need_to_run_deps2git(repo_base, deps_file, deps_git_file):
579 """Checks to see if we need to run deps2git. 580 """Checks to see if we need to run deps2git.
580 581
581 Returns True if there was a DEPS change after the last .DEPS.git update. 582 Returns True if there was a DEPS change after the last .DEPS.git update
583 or if DEPS has local modifications.
582 """ 584 """
583 print 'Checking if %s exists' % deps_git_file 585 print 'Checking if %s exists' % deps_git_file
584 if not path.isfile(deps_git_file): 586 if not path.isfile(deps_git_file):
585 # .DEPS.git doesn't exist but DEPS does? We probably want to generate one. 587 # .DEPS.git doesn't exist but DEPS does? We probably want to generate one.
586 print 'it exists!' 588 print 'it doesn\'t exist!'
589 return True
590
591 if git('diff', deps_file, cwd=repo_base).strip():
587 return True 592 return True
588 593
589 last_known_deps_ref = _last_commit_for_file(deps_file, repo_base) 594 last_known_deps_ref = _last_commit_for_file(deps_file, repo_base)
590 last_known_deps_git_ref = _last_commit_for_file(deps_git_file, repo_base) 595 last_known_deps_git_ref = _last_commit_for_file(deps_git_file, repo_base)
591 merge_base_ref = git('merge-base', last_known_deps_ref, 596 merge_base_ref = git('merge-base', last_known_deps_ref,
592 last_known_deps_git_ref, cwd=repo_base).strip() 597 last_known_deps_git_ref, cwd=repo_base).strip()
593 598
594 # If the merge base of the last DEPS and last .DEPS.git file is not 599 # If the merge base of the last DEPS and last .DEPS.git file is not
595 # equivilent to the hash of the last DEPS file, that means the DEPS file 600 # equivilent to the hash of the last DEPS file, that means the DEPS file
596 # was committed after the last .DEPS.git file. 601 # was committed after the last .DEPS.git file.
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
644 version = m.group(1) 649 version = m.group(1)
645 git_buildspec = get_git_buildspec(version) 650 git_buildspec = get_git_buildspec(version)
646 with open(deps_git_file, 'wb') as f: 651 with open(deps_git_file, 'wb') as f:
647 f.write(git_buildspec) 652 f.write(git_buildspec)
648 653
649 654
650 def ensure_deps2git(sln_dir, shallow): 655 def ensure_deps2git(sln_dir, shallow):
651 repo_base = path.join(os.getcwd(), sln_dir) 656 repo_base = path.join(os.getcwd(), sln_dir)
652 deps_file = path.join(repo_base, 'DEPS') 657 deps_file = path.join(repo_base, 'DEPS')
653 deps_git_file = path.join(repo_base, '.DEPS.git') 658 deps_git_file = path.join(repo_base, '.DEPS.git')
654 print 'Checking if %s is newer than %s' % (deps_file, deps_git_file)
655 if not path.isfile(deps_file): 659 if not path.isfile(deps_file):
656 return 660 return
657 661
662 print 'Checking if %s is newer than %s' % (deps_file, deps_git_file)
658 if not need_to_run_deps2git(repo_base, deps_file, deps_git_file): 663 if not need_to_run_deps2git(repo_base, deps_file, deps_git_file):
659 return 664 return
660 665
661 print '===DEPS file modified, need to run deps2git===' 666 print '===DEPS file modified, need to run deps2git==='
662 # Magic to get deps2git to work with internal DEPS. 667 # Magic to get deps2git to work with internal DEPS.
663 shutil.copyfile(S2G_INTERNAL_FROM_PATH, S2G_INTERNAL_DEST_PATH) 668 shutil.copyfile(S2G_INTERNAL_FROM_PATH, S2G_INTERNAL_DEST_PATH)
664 669
665 # TODO(hinoka): This might need to be smarter if we need to deal with 670 # TODO(hinoka): This might need to be smarter if we need to deal with
666 # DEPS changes that are in an internal repository. 671 # DEPS changes that are in an internal repository.
667 repo_type = 'public' 672 repo_type = 'public'
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after
795 def _download(url): 800 def _download(url):
796 """Fetch url and return content, with retries for flake.""" 801 """Fetch url and return content, with retries for flake."""
797 for attempt in xrange(RETRIES): 802 for attempt in xrange(RETRIES):
798 try: 803 try:
799 return urllib2.urlopen(url).read() 804 return urllib2.urlopen(url).read()
800 except Exception: 805 except Exception:
801 if attempt == RETRIES - 1: 806 if attempt == RETRIES - 1:
802 raise 807 raise
803 808
804 809
805 def apply_issue_svn(root, patch_url): 810 def parse_diff(diff):
806 patch_data = call('svn', 'cat', patch_url) 811 """Takes a unified diff and returns a list of diffed files and their diffs.
807 call(PATCH_TOOL, '-p0', '--remove-empty-files', '--force', '--forward', 812
808 stdin_data=patch_data, cwd=root) 813 The return format is a list of pairs of:
814 (<filename>, <diff contents>)
815 <diff contents> is inclusive of the diff line.
816 """
817 result = []
818 current_diff = ''
819 current_header = None
820 for line in diff.splitlines():
821 # "diff" is for git style patches, and "Index: " is for SVN style patches.
822 if line.startswith('diff') or line.startswith('Index: '):
823 if current_header:
824 # If we are in a diff portion, then save the diff.
825 result.append((current_header, '%s\n' % current_diff))
826 git_header_match = re.match(r'diff (?:--git )?(\S+) (\S+)', line)
827 svn_header_match = re.match(r'Index: (.*)', line)
828
829 if git_header_match:
830 # First, see if its a git style header.
831 from_file = git_header_match.group(1)
832 to_file = git_header_match.group(2)
833 if from_file != to_file and from_file.startswith('a/'):
834 # Sometimes git prepends 'a/' and 'b/' in front of file paths.
835 from_file = from_file[2:]
836 current_header = from_file
837
838 elif svn_header_match:
839 # Otherwise, check if its an SVN style header.
840 current_header = svn_header_match.group(1)
841
842 else:
843 # Otherwise... I'm not really sure what to do with this.
iannucci 2014/05/08 22:17:01 probably should abort loudly
Ryan Tseng 2014/05/09 20:59:42 Done.
844 current_header = line
845
846 current_diff = ''
847 current_diff += '%s\n' % line
848 if current_header:
849 # We hit EOF, gotta save the last diff.
850 result.append((current_header, current_diff))
851 return result
809 852
810 853
811 def apply_issue_rietveld(issue, patchset, root, server, rev_map, revision): 854 def get_svn_patch(patch_url):
855 """Fetch patch from patch_url, return list of (filename, diff)"""
856 patch_data = call('svn', 'cat', patch_url)
857 return parse_diff(patch_data)
858
859
860 def apply_svn_patch(patch_root, patches, whitelist=None, blacklist=None):
861 """Expects a list of (filename, diff), applies it on top of patch_root."""
862 if whitelist:
863 patches = [(name, diff) for name, diff in patches if name in whitelist]
864 elif blacklist:
865 patches = [(name, diff) for name, diff in patches if name not in blacklist]
866 diffs = [diff for _, diff in patches]
867 patch = ''.join(diffs)
868
869 if patch:
870 print '===Patching files==='
871 for filename, _ in patches:
872 print 'Patching %s' % filename
873 call(PATCH_TOOL, '-p0', '--remove-empty-files', '--force', '--forward',
874 stdin_data=patch, cwd=patch_root, tries=1)
875
876
877 def apply_rietveld_issue(issue, patchset, root, server, rev_map, revision,
878 whitelist=None, blacklist=None):
812 apply_issue_bin = ('apply_issue.bat' if sys.platform.startswith('win') 879 apply_issue_bin = ('apply_issue.bat' if sys.platform.startswith('win')
813 else 'apply_issue') 880 else 'apply_issue')
814 call(apply_issue_bin, 881 cmd = [apply_issue_bin,
815 '--root_dir', root, 882 # The patch will be applied on top of this directory.
816 '--issue', issue, 883 '--root_dir', root,
817 '--patchset', patchset, 884 # Tell apply_issue how to fetch the patch.
818 '--no-auth', 885 '--issue', issue,
819 '--server', server, 886 '--patchset', patchset,
820 '--base_ref', revision, 887 '--no-auth',
821 '--force', 888 '--server', server,
822 '--ignore_deps') 889 # Always run apply_issue.py, otherwise it would see update.flag
890 # and then bail out.
891 '--force',
892 # Don't run gclient sync when it sees a DEPS change.
893 '--ignore_deps',
894 # Don't commit the patch or add it to the index.
895 '--no_commit',
896 ]
897 if whitelist:
898 for item in whitelist:
899 cmd.extend(['--whitelist', item])
900 elif blacklist:
901 for item in blacklist:
902 cmd.extend(['--blacklist', item])
903
904 # Only try once, since subsequent failures hide the real failure.
905 call(*cmd, tries=1)
823 906
824 907
825 def check_flag(flag_file): 908 def check_flag(flag_file):
826 """Returns True if the flag file is present.""" 909 """Returns True if the flag file is present."""
827 return os.path.isfile(flag_file) 910 return os.path.isfile(flag_file)
828 911
829 912
830 def delete_flag(flag_file): 913 def delete_flag(flag_file):
831 """Remove bot update flag.""" 914 """Remove bot update flag."""
832 if os.path.isfile(flag_file): 915 if os.path.isfile(flag_file):
833 os.remove(flag_file) 916 os.remove(flag_file)
834 917
835 918
836 def emit_flag(flag_file): 919 def emit_flag(flag_file):
837 """Deposit a bot update flag on the system to tell gclient not to run.""" 920 """Deposit a bot update flag on the system to tell gclient not to run."""
838 print 'Emitting flag file at %s' % flag_file 921 print 'Emitting flag file at %s' % flag_file
839 with open(flag_file, 'wb') as f: 922 with open(flag_file, 'wb') as f:
840 f.write('Success!') 923 f.write('Success!')
841 924
842 925
843 def get_real_git_hash(git_hash, dir_name):
844 """Return git_hash or the parent of git_hash if its a patched hash."""
845 log = git('log', '-1', '--format=%B', cwd=dir_name).strip()
846 if 'committed patch' in log.lower():
847 return git('log', '-1', '--format=%H', 'HEAD^', cwd=dir_name).strip()
848 return git_hash
849
850
851 def parse_got_revision(gclient_output, got_revision_mapping, use_svn_revs): 926 def parse_got_revision(gclient_output, got_revision_mapping, use_svn_revs):
852 """Translate git gclient revision mapping to build properties. 927 """Translate git gclient revision mapping to build properties.
853 928
854 If use_svn_revs is True, then translate git hashes in the revision mapping 929 If use_svn_revs is True, then translate git hashes in the revision mapping
855 to svn revision numbers. 930 to svn revision numbers.
856 """ 931 """
857 properties = {} 932 properties = {}
858 solutions_output = gclient_output['solutions'] 933 solutions_output = gclient_output['solutions']
859 for dir_name, property_name in got_revision_mapping.iteritems(): 934 for dir_name, property_name in got_revision_mapping.iteritems():
860 if dir_name not in solutions_output: 935 if dir_name not in solutions_output:
861 continue 936 continue
862 solution_output = solutions_output[dir_name] 937 solution_output = solutions_output[dir_name]
863 if solution_output.get('scm') is None: 938 if solution_output.get('scm') is None:
864 # This is an ignored DEPS, so the output got_revision should be 'None'. 939 # This is an ignored DEPS, so the output got_revision should be 'None'.
865 git_revision = revision = None 940 git_revision = revision = None
866 else: 941 else:
867 # Since we are using .DEPS.git, everything had better be git. 942 # Since we are using .DEPS.git, everything had better be git.
868 assert solution_output.get('scm') == 'git' 943 assert solution_output.get('scm') == 'git'
869 git_revision = get_real_git_hash(solution_output['revision'], dir_name) 944 git_revision = solution_output['revision']
870 if use_svn_revs: 945 if use_svn_revs:
871 revision = get_svn_rev(git_revision, dir_name) 946 revision = get_svn_rev(git_revision, dir_name)
872 if not revision: 947 if not revision:
873 revision = git_revision 948 revision = git_revision
874 else: 949 else:
875 revision = git_revision 950 revision = git_revision
876 951
877 properties[property_name] = revision 952 properties[property_name] = revision
878 if revision != git_revision: 953 if revision != git_revision:
879 properties['%s_git' % property_name] = git_revision 954 properties['%s_git' % property_name] = git_revision
(...skipping 13 matching lines...) Expand all
893 968
894 def ensure_checkout(solutions, revision, first_sln, target_os, target_os_only, 969 def ensure_checkout(solutions, revision, first_sln, target_os, target_os_only,
895 root, issue, patchset, patch_url, rietveld_server, 970 root, issue, patchset, patch_url, rietveld_server,
896 revision_mapping, buildspec_name, gyp_env, shallow): 971 revision_mapping, buildspec_name, gyp_env, shallow):
897 # Get a checkout of each solution, without DEPS or hooks. 972 # Get a checkout of each solution, without DEPS or hooks.
898 # Calling git directly because there is no way to run Gclient without 973 # Calling git directly because there is no way to run Gclient without
899 # invoking DEPS. 974 # invoking DEPS.
900 print 'Fetching Git checkout' 975 print 'Fetching Git checkout'
901 git_ref = git_checkout(solutions, revision, shallow) 976 git_ref = git_checkout(solutions, revision, shallow)
902 977
903 # If either patch_url or issue is passed in, then we need to apply a patch. 978 patches = None
904 if patch_url: 979 if patch_url:
905 # patch_url takes precidence since its only passed in on gcl try/git try. 980 patches = get_svn_patch(patch_url)
906 apply_issue_svn(root, patch_url) 981
907 elif issue: 982 if root == first_sln:
908 apply_issue_rietveld(issue, patchset, root, rietveld_server, 983 # Only top level DEPS patching is supported right now.
909 revision_mapping, git_ref) 984 if patches:
985 apply_svn_patch(root, patches, whitelist=['DEPS'])
986 elif issue:
987 apply_rietveld_issue(issue, patchset, root, rietveld_server,
988 revision_mapping, git_ref, whitelist=['DEPS'])
989
910 990
911 if buildspec_name: 991 if buildspec_name:
912 buildspecs2git(root, buildspec_name) 992 buildspecs2git(root, buildspec_name)
913 elif first_sln == root: 993 else:
914 # Run deps2git if there is a DEPS commit after the last .DEPS.git commit. 994 # Run deps2git if there is a DEPS change after the last .DEPS.git commit.
915 # We only need to ensure deps2git if the root is not overridden, since
916 # if the root is overridden, it means we are working with a sub repository
917 # patch, which means its impossible for it to touch DEPS.
918 ensure_deps2git(root, shallow) 995 ensure_deps2git(root, shallow)
919 996
920 # Ensure our build/ directory is set up with the correct .gclient file. 997 # Ensure our build/ directory is set up with the correct .gclient file.
921 gclient_configure(solutions, target_os, target_os_only) 998 gclient_configure(solutions, target_os, target_os_only)
922 999
923 # Let gclient do the DEPS syncing. 1000 # Let gclient do the DEPS syncing.
924 gclient_output = gclient_sync(buildspec_name) 1001 gclient_output = gclient_sync(buildspec_name)
925 if buildspec_name: 1002 if buildspec_name:
926 # Run gclient runhooks if we're on an official builder. 1003 # Run gclient runhooks if we're on an official builder.
927 # TODO(hinoka): Remove this when the official builders run their own 1004 # TODO(hinoka): Remove this when the official builders run their own
928 # runhooks step. 1005 # runhooks step.
929 gclient_runhooks(gyp_env) 1006 gclient_runhooks(gyp_env)
1007
1008 # Apply the rest of the patch here (sans DEPS)
1009 if patches:
1010 apply_svn_patch(root, patches, blacklist=['DEPS'])
1011 elif issue:
1012 apply_rietveld_issue(issue, patchset, root, rietveld_server,
1013 revision_mapping, git_ref, blacklist=['DEPS'])
1014
930 return gclient_output 1015 return gclient_output
931 1016
932 1017
933 def parse_args(): 1018 def parse_args():
934 parse = optparse.OptionParser() 1019 parse = optparse.OptionParser()
935 1020
936 parse.add_option('--issue', help='Issue number to patch from.') 1021 parse.add_option('--issue', help='Issue number to patch from.')
937 parse.add_option('--patchset', 1022 parse.add_option('--patchset',
938 help='Patchset from issue to patch from, if applicable.') 1023 help='Patchset from issue to patch from, if applicable.')
939 parse.add_option('--patch_url', help='Optional URL to SVN patch.') 1024 parse.add_option('--patch_url', help='Optional URL to SVN patch.')
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after
1088 patch_root=options.root, 1173 patch_root=options.root,
1089 step_text=step_text, 1174 step_text=step_text,
1090 properties=got_revisions) 1175 properties=got_revisions)
1091 else: 1176 else:
1092 # If we're not on recipes, tell annotator about our got_revisions. 1177 # If we're not on recipes, tell annotator about our got_revisions.
1093 emit_properties(got_revisions) 1178 emit_properties(got_revisions)
1094 1179
1095 1180
1096 if __name__ == '__main__': 1181 if __name__ == '__main__':
1097 sys.exit(main()) 1182 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