| Dan Albert | d3fe4f1 | 2015-04-17 13:01:29 -0700 | [diff] [blame] | 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: | 
| Dan Albert | ded34ce | 2015-05-07 16:33:42 -0700 | [diff] [blame] | 76 | _ = jenkins[build].invoke() | 
|  | 77 | # https://issues.jenkins-ci.org/browse/JENKINS-27256 | 
|  | 78 | # url = job.get_build().baseurl | 
|  | 79 | url = 'URL UNAVAILABLE' | 
| Dan Albert | d3fe4f1 | 2015-04-17 13:01:29 -0700 | [diff] [blame] | 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 |