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# 16from __future__ import absolute_import 17 18import json 19import logging 20import os.path 21import re 22import requests 23 24import jenkinsapi 25 26import gerrit 27 28import config 29 30 31def 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 38def 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 43def 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 48def 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 67def 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 88def 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 139def 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 145def 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 162def 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 202def skip_handler(gerrit_info, _, __): 203 logging.info('Skipping %s: %s', gerrit_info['MessageType'], 204 gerrit_info['Change-Id']) 205 return True 206