204 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			204 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#
 | 
						|
# 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
 |