Home | History | Annotate | Download | only in bionicbb
      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