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

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: Adds filename printing 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):
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.
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_issue_rietveld(issue, patchset, root, server, rev_map, revision,
agable 2014/05/07 21:39:00 apply_rietveld_issue (to match apply_svn_patch)
agable 2014/05/07 21:39:00 Also, take patch first (to match apply_svn_patch)
Ryan Tseng 2014/05/09 20:59:41 Patchset here is actually an integer, where as "pa
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
agable 2014/05/07 21:39:00 I wouldn't put empty lines in the middle of this.
Ryan Tseng 2014/05/09 20:59:41 Done.
818 '--no-auth', 885 # Tell apply_issue how to fetch the patch.
819 '--server', server, 886 '--issue', issue,
820 '--base_ref', revision, 887 '--patchset', patchset,
821 '--force', 888 '--no-auth',
822 '--ignore_deps') 889 '--server', server,
890
891 # DEPRECATED?
agable 2014/05/07 21:39:00 If it is, remove it. If it is still necessary, say
Ryan Tseng 2014/05/09 20:59:41 Done.
892 '--base_ref', revision,
893
894 # Always run apply_issue.py, otherwise it would see update.flag
895 # and then bail out.
896 '--force',
897
898 # Don't run gclient sync when it sees a DEPS change.
899 '--ignore_deps',
900
901 # Don't commit the patch or add it to the index.
902 '--no_commit',
903 ]
904 if whitelist:
905 for item in whitelist:
906 cmd.extend(['--whitelist', item])
907 elif blacklist:
908 for item in blacklist:
909 cmd.extend(['--blacklist', item])
910
911 # Only try once, since subsequent failures hide the real failure.
912 call(*cmd, tries=1)
823 913
824 914
825 def check_flag(flag_file): 915 def check_flag(flag_file):
826 """Returns True if the flag file is present.""" 916 """Returns True if the flag file is present."""
827 return os.path.isfile(flag_file) 917 return os.path.isfile(flag_file)
828 918
829 919
830 def delete_flag(flag_file): 920 def delete_flag(flag_file):
831 """Remove bot update flag.""" 921 """Remove bot update flag."""
832 if os.path.isfile(flag_file): 922 if os.path.isfile(flag_file):
833 os.remove(flag_file) 923 os.remove(flag_file)
834 924
835 925
836 def emit_flag(flag_file): 926 def emit_flag(flag_file):
837 """Deposit a bot update flag on the system to tell gclient not to run.""" 927 """Deposit a bot update flag on the system to tell gclient not to run."""
838 print 'Emitting flag file at %s' % flag_file 928 print 'Emitting flag file at %s' % flag_file
839 with open(flag_file, 'wb') as f: 929 with open(flag_file, 'wb') as f:
840 f.write('Success!') 930 f.write('Success!')
841 931
842 932
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): 933 def parse_got_revision(gclient_output, got_revision_mapping, use_svn_revs):
852 """Translate git gclient revision mapping to build properties. 934 """Translate git gclient revision mapping to build properties.
853 935
854 If use_svn_revs is True, then translate git hashes in the revision mapping 936 If use_svn_revs is True, then translate git hashes in the revision mapping
855 to svn revision numbers. 937 to svn revision numbers.
856 """ 938 """
857 properties = {} 939 properties = {}
858 solutions_output = gclient_output['solutions'] 940 solutions_output = gclient_output['solutions']
859 for dir_name, property_name in got_revision_mapping.iteritems(): 941 for dir_name, property_name in got_revision_mapping.iteritems():
860 if dir_name not in solutions_output: 942 if dir_name not in solutions_output:
861 continue 943 continue
862 solution_output = solutions_output[dir_name] 944 solution_output = solutions_output[dir_name]
863 if solution_output.get('scm') is None: 945 if solution_output.get('scm') is None:
864 # This is an ignored DEPS, so the output got_revision should be 'None'. 946 # This is an ignored DEPS, so the output got_revision should be 'None'.
865 git_revision = revision = None 947 git_revision = revision = None
866 else: 948 else:
867 # Since we are using .DEPS.git, everything had better be git. 949 # Since we are using .DEPS.git, everything had better be git.
868 assert solution_output.get('scm') == 'git' 950 assert solution_output.get('scm') == 'git'
869 git_revision = get_real_git_hash(solution_output['revision'], dir_name) 951 git_revision = solution_output['revision']
870 if use_svn_revs: 952 if use_svn_revs:
871 revision = get_svn_rev(git_revision, dir_name) 953 revision = get_svn_rev(git_revision, dir_name)
872 if not revision: 954 if not revision:
873 revision = git_revision 955 revision = git_revision
874 else: 956 else:
875 revision = git_revision 957 revision = git_revision
876 958
877 properties[property_name] = revision 959 properties[property_name] = revision
878 if revision != git_revision: 960 if revision != git_revision:
879 properties['%s_git' % property_name] = git_revision 961 properties['%s_git' % property_name] = git_revision
(...skipping 13 matching lines...) Expand all
893 975
894 def ensure_checkout(solutions, revision, first_sln, target_os, target_os_only, 976 def ensure_checkout(solutions, revision, first_sln, target_os, target_os_only,
895 root, issue, patchset, patch_url, rietveld_server, 977 root, issue, patchset, patch_url, rietveld_server,
896 revision_mapping, buildspec_name, gyp_env, shallow): 978 revision_mapping, buildspec_name, gyp_env, shallow):
897 # Get a checkout of each solution, without DEPS or hooks. 979 # Get a checkout of each solution, without DEPS or hooks.
898 # Calling git directly because there is no way to run Gclient without 980 # Calling git directly because there is no way to run Gclient without
899 # invoking DEPS. 981 # invoking DEPS.
900 print 'Fetching Git checkout' 982 print 'Fetching Git checkout'
901 git_ref = git_checkout(solutions, revision, shallow) 983 git_ref = git_checkout(solutions, revision, shallow)
902 984
903 # If either patch_url or issue is passed in, then we need to apply a patch. 985 patches = None
904 if patch_url: 986 if patch_url:
905 # patch_url takes precidence since its only passed in on gcl try/git try. 987 patches = get_svn_patch(patch_url)
906 apply_issue_svn(root, patch_url) 988
907 elif issue: 989 if root == first_sln:
908 apply_issue_rietveld(issue, patchset, root, rietveld_server, 990 # Only top level DEPS patching is supported right now.
909 revision_mapping, git_ref) 991 if patches:
992 apply_svn_patch(root, patches, whitelist=['DEPS'])
993 elif issue:
994 apply_issue_rietveld(issue, patchset, root, rietveld_server,
995 revision_mapping, git_ref, whitelist=['DEPS'])
996
910 997
911 if buildspec_name: 998 if buildspec_name:
912 buildspecs2git(root, buildspec_name) 999 buildspecs2git(root, buildspec_name)
913 elif first_sln == root: 1000 else:
914 # Run deps2git if there is a DEPS commit after the last .DEPS.git commit. 1001 # 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) 1002 ensure_deps2git(root, shallow)
919 1003
920 # Ensure our build/ directory is set up with the correct .gclient file. 1004 # Ensure our build/ directory is set up with the correct .gclient file.
921 gclient_configure(solutions, target_os, target_os_only) 1005 gclient_configure(solutions, target_os, target_os_only)
922 1006
923 # Let gclient do the DEPS syncing. 1007 # Let gclient do the DEPS syncing.
924 gclient_output = gclient_sync(buildspec_name) 1008 gclient_output = gclient_sync(buildspec_name)
925 if buildspec_name: 1009 if buildspec_name:
926 # Run gclient runhooks if we're on an official builder. 1010 # Run gclient runhooks if we're on an official builder.
927 # TODO(hinoka): Remove this when the official builders run their own 1011 # TODO(hinoka): Remove this when the official builders run their own
928 # runhooks step. 1012 # runhooks step.
929 gclient_runhooks(gyp_env) 1013 gclient_runhooks(gyp_env)
1014
1015 # Apply the rest of the patch here (sans DEPS)
1016 if patches:
1017 apply_svn_patch(root, patches, blacklist=['DEPS'])
1018 elif issue:
1019 apply_issue_rietveld(issue, patchset, root, rietveld_server,
1020 revision_mapping, git_ref, blacklist=['DEPS'])
1021
930 return gclient_output 1022 return gclient_output
931 1023
932 1024
933 def parse_args(): 1025 def parse_args():
934 parse = optparse.OptionParser() 1026 parse = optparse.OptionParser()
935 1027
936 parse.add_option('--issue', help='Issue number to patch from.') 1028 parse.add_option('--issue', help='Issue number to patch from.')
937 parse.add_option('--patchset', 1029 parse.add_option('--patchset',
938 help='Patchset from issue to patch from, if applicable.') 1030 help='Patchset from issue to patch from, if applicable.')
939 parse.add_option('--patch_url', help='Optional URL to SVN patch.') 1031 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, 1180 patch_root=options.root,
1089 step_text=step_text, 1181 step_text=step_text,
1090 properties=got_revisions) 1182 properties=got_revisions)
1091 else: 1183 else:
1092 # If we're not on recipes, tell annotator about our got_revisions. 1184 # If we're not on recipes, tell annotator about our got_revisions.
1093 emit_properties(got_revisions) 1185 emit_properties(got_revisions)
1094 1186
1095 1187
1096 if __name__ == '__main__': 1188 if __name__ == '__main__':
1097 sys.exit(main()) 1189 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