# # Copyright (C) 2015 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the 'License'); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an 'AS IS' BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # from __future__ import absolute_import import json import logging import os.path import re import requests import jenkinsapi import gerrit import config def is_untrusted_committer(change_id, patch_set): # TODO(danalbert): Needs to be based on the account that made the comment. commit = gerrit.get_commit(change_id, patch_set) committer = commit['committer']['email'] return not committer.endswith('@google.com') def contains_cleanspec(change_id, patch_set): files = gerrit.get_files_for_revision(change_id, patch_set) return 'CleanSpec.mk' in [os.path.basename(f) for f in files] def contains_bionicbb(change_id, patch_set): files = gerrit.get_files_for_revision(change_id, patch_set) return any('tools/bionicbb' in f for f in files) def should_skip_build(info): if info['MessageType'] not in ('newchange', 'newpatchset', 'comment'): raise ValueError('should_skip_build() is only valid for new ' 'changes, patch sets, and commits.') change_id = info['Change-Id'] patch_set = info['PatchSet'] checks = [ is_untrusted_committer, contains_cleanspec, contains_bionicbb, ] for check in checks: if check(change_id, patch_set): return True return False def clean_project(dry_run): username = config.jenkins_credentials['username'] password = config.jenkins_credentials['password'] jenkins_url = config.jenkins_url jenkins = jenkinsapi.api.Jenkins(jenkins_url, username, password) build = 'clean-bionic-presubmit' if build in jenkins: if not dry_run: job = jenkins[build].invoke() url = job.get_build().baseurl else: url = 'DRY_RUN_URL' logging.info('Cleaning: %s %s', build, url) else: logging.error('Failed to clean: could not find project %s', build) return True def build_project(gerrit_info, dry_run, lunch_target=None): project_to_jenkins_map = { 'platform/bionic': 'bionic-presubmit', 'platform/build': 'bionic-presubmit', 'platform/external/jemalloc': 'bionic-presubmit', 'platform/external/libcxx': 'bionic-presubmit', 'platform/external/libcxxabi': 'bionic-presubmit', 'platform/external/compiler-rt': 'bionic-presubmit', } username = config.jenkins_credentials['username'] password = config.jenkins_credentials['password'] jenkins_url = config.jenkins_url jenkins = jenkinsapi.api.Jenkins(jenkins_url, username, password) project = gerrit_info['Project'] change_id = gerrit_info['Change-Id'] if project in project_to_jenkins_map: build = project_to_jenkins_map[project] else: build = 'bionic-presubmit' if build in jenkins: project_path = '/'.join(project.split('/')[1:]) if not project_path: raise RuntimeError('bogus project: {}'.format(project)) if project_path.startswith('platform/'): raise RuntimeError('Bad project mapping: {} => {}'.format( project, project_path)) ref = gerrit.ref_for_change(change_id) params = { 'REF': ref, 'CHANGE_ID': change_id, 'PROJECT': project_path } if lunch_target is not None: params['LUNCH_TARGET'] = lunch_target if not dry_run: _ = jenkins[build].invoke(build_params=params) # https://issues.jenkins-ci.org/browse/JENKINS-27256 # url = job.get_build().baseurl url = 'URL UNAVAILABLE' else: url = 'DRY_RUN_URL' logging.info('Building: %s => %s %s %s', project, build, url, change_id) else: logging.error('Unknown build: %s => %s %s', project, build, change_id) return True def handle_change(gerrit_info, _, dry_run): if should_skip_build(gerrit_info): return True return build_project(gerrit_info, dry_run) def drop_rejection(gerrit_info, dry_run): request_data = { 'changeid': gerrit_info['Change-Id'], 'patchset': gerrit_info['PatchSet'] } url = '{}/{}'.format(config.build_listener_url, 'drop-rejection') headers = {'Content-Type': 'application/json;charset=UTF-8'} if not dry_run: try: requests.post(url, headers=headers, data=json.dumps(request_data)) except requests.exceptions.ConnectionError as ex: logging.error('Failed to drop rejection: %s', ex) return False logging.info('Dropped rejection: %s', gerrit_info['Change-Id']) return True def handle_comment(gerrit_info, body, dry_run): if 'Verified+1' in body: drop_rejection(gerrit_info, dry_run) if should_skip_build(gerrit_info): return True command_map = { 'clean': lambda: clean_project(dry_run), 'retry': lambda: build_project(gerrit_info, dry_run), 'arm': lambda: build_project(gerrit_info, dry_run, lunch_target='aosp_arm-eng'), 'aarch64': lambda: build_project(gerrit_info, dry_run, lunch_target='aosp_arm64-eng'), 'mips': lambda: build_project(gerrit_info, dry_run, lunch_target='aosp_mips-eng'), 'mips64': lambda: build_project(gerrit_info, dry_run, lunch_target='aosp_mips64-eng'), 'x86': lambda: build_project(gerrit_info, dry_run, lunch_target='aosp_x86-eng'), 'x86_64': lambda: build_project(gerrit_info, dry_run, lunch_target='aosp_x86_64-eng'), } def handle_unknown_command(): pass # TODO(danalbert): should complain to the commenter. commands = [match.group(1).strip() for match in re.finditer(r'^bionicbb:\s*(.+)$', body, flags=re.MULTILINE)] for command in commands: if command in command_map: command_map[command]() else: handle_unknown_command() return True def skip_handler(gerrit_info, _, __): logging.info('Skipping %s: %s', gerrit_info['MessageType'], gerrit_info['Change-Id']) return True