1 # 2 # Copyright (C) 2015 The Android Open Source Project 3 # 4 # Licensed under the Apache License, Version 2.0 (the 'License'); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an 'AS IS' BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 # 16 from __future__ import absolute_import 17 18 import json 19 import logging 20 import os.path 21 import re 22 import requests 23 24 import jenkinsapi 25 26 import gerrit 27 28 import config 29 30 31 def is_untrusted_committer(change_id, patch_set): 32 # TODO(danalbert): Needs to be based on the account that made the comment. 33 commit = gerrit.get_commit(change_id, patch_set) 34 committer = commit['committer']['email'] 35 return not committer.endswith('@google.com') 36 37 38 def contains_cleanspec(change_id, patch_set): 39 files = gerrit.get_files_for_revision(change_id, patch_set) 40 return 'CleanSpec.mk' in [os.path.basename(f) for f in files] 41 42 43 def contains_bionicbb(change_id, patch_set): 44 files = gerrit.get_files_for_revision(change_id, patch_set) 45 return any('tools/bionicbb' in f for f in files) 46 47 48 def should_skip_build(info): 49 if info['MessageType'] not in ('newchange', 'newpatchset', 'comment'): 50 raise ValueError('should_skip_build() is only valid for new ' 51 'changes, patch sets, and commits.') 52 53 change_id = info['Change-Id'] 54 patch_set = info['PatchSet'] 55 56 checks = [ 57 is_untrusted_committer, 58 contains_cleanspec, 59 contains_bionicbb, 60 ] 61 for check in checks: 62 if check(change_id, patch_set): 63 return True 64 return False 65 66 67 def clean_project(dry_run): 68 username = config.jenkins_credentials['username'] 69 password = config.jenkins_credentials['password'] 70 jenkins_url = config.jenkins_url 71 jenkins = jenkinsapi.api.Jenkins(jenkins_url, username, password) 72 73 build = 'clean-bionic-presubmit' 74 if build in jenkins: 75 if not dry_run: 76 _ = jenkins[build].invoke() 77 # https://issues.jenkins-ci.org/browse/JENKINS-27256 78 # url = job.get_build().baseurl 79 url = 'URL UNAVAILABLE' 80 else: 81 url = 'DRY_RUN_URL' 82 logging.info('Cleaning: %s %s', build, url) 83 else: 84 logging.error('Failed to clean: could not find project %s', build) 85 return True 86 87 88 def build_project(gerrit_info, dry_run, lunch_target=None): 89 project_to_jenkins_map = { 90 'platform/bionic': 'bionic-presubmit', 91 'platform/build': 'bionic-presubmit', 92 'platform/external/jemalloc': 'bionic-presubmit', 93 'platform/external/libcxx': 'bionic-presubmit', 94 'platform/external/libcxxabi': 'bionic-presubmit', 95 'platform/external/compiler-rt': 'bionic-presubmit', 96 } 97 98 username = config.jenkins_credentials['username'] 99 password = config.jenkins_credentials['password'] 100 jenkins_url = config.jenkins_url 101 jenkins = jenkinsapi.api.Jenkins(jenkins_url, username, password) 102 103 project = gerrit_info['Project'] 104 change_id = gerrit_info['Change-Id'] 105 if project in project_to_jenkins_map: 106 build = project_to_jenkins_map[project] 107 else: 108 build = 'bionic-presubmit' 109 110 if build in jenkins: 111 project_path = '/'.join(project.split('/')[1:]) 112 if not project_path: 113 raise RuntimeError('bogus project: {}'.format(project)) 114 if project_path.startswith('platform/'): 115 raise RuntimeError('Bad project mapping: {} => {}'.format( 116 project, project_path)) 117 ref = gerrit.ref_for_change(change_id) 118 params = { 119 'REF': ref, 120 'CHANGE_ID': change_id, 121 'PROJECT': project_path 122 } 123 if lunch_target is not None: 124 params['LUNCH_TARGET'] = lunch_target 125 if not dry_run: 126 _ = jenkins[build].invoke(build_params=params) 127 # https://issues.jenkins-ci.org/browse/JENKINS-27256 128 # url = job.get_build().baseurl 129 url = 'URL UNAVAILABLE' 130 else: 131 url = 'DRY_RUN_URL' 132 logging.info('Building: %s => %s %s %s', project, build, url, 133 change_id) 134 else: 135 logging.error('Unknown build: %s => %s %s', project, build, change_id) 136 return True 137 138 139 def handle_change(gerrit_info, _, dry_run): 140 if should_skip_build(gerrit_info): 141 return True 142 return build_project(gerrit_info, dry_run) 143 144 145 def drop_rejection(gerrit_info, dry_run): 146 request_data = { 147 'changeid': gerrit_info['Change-Id'], 148 'patchset': gerrit_info['PatchSet'] 149 } 150 url = '{}/{}'.format(config.build_listener_url, 'drop-rejection') 151 headers = {'Content-Type': 'application/json;charset=UTF-8'} 152 if not dry_run: 153 try: 154 requests.post(url, headers=headers, data=json.dumps(request_data)) 155 except requests.exceptions.ConnectionError as ex: 156 logging.error('Failed to drop rejection: %s', ex) 157 return False 158 logging.info('Dropped rejection: %s', gerrit_info['Change-Id']) 159 return True 160 161 162 def handle_comment(gerrit_info, body, dry_run): 163 if 'Verified+1' in body: 164 drop_rejection(gerrit_info, dry_run) 165 166 if should_skip_build(gerrit_info): 167 return True 168 169 command_map = { 170 'clean': lambda: clean_project(dry_run), 171 'retry': lambda: build_project(gerrit_info, dry_run), 172 173 'arm': lambda: build_project(gerrit_info, dry_run, 174 lunch_target='aosp_arm-eng'), 175 'aarch64': lambda: build_project(gerrit_info, dry_run, 176 lunch_target='aosp_arm64-eng'), 177 'mips': lambda: build_project(gerrit_info, dry_run, 178 lunch_target='aosp_mips-eng'), 179 'mips64': lambda: build_project(gerrit_info, dry_run, 180 lunch_target='aosp_mips64-eng'), 181 'x86': lambda: build_project(gerrit_info, dry_run, 182 lunch_target='aosp_x86-eng'), 183 'x86_64': lambda: build_project(gerrit_info, dry_run, 184 lunch_target='aosp_x86_64-eng'), 185 } 186 187 def handle_unknown_command(): 188 pass # TODO(danalbert): should complain to the commenter. 189 190 commands = [match.group(1).strip() for match in 191 re.finditer(r'^bionicbb:\s*(.+)$', body, flags=re.MULTILINE)] 192 193 for command in commands: 194 if command in command_map: 195 command_map[command]() 196 else: 197 handle_unknown_command() 198 199 return True 200 201 202 def skip_handler(gerrit_info, _, __): 203 logging.info('Skipping %s: %s', gerrit_info['MessageType'], 204 gerrit_info['Change-Id']) 205 return True 206