Merge the two bionicbb services into one.

Change-Id: I6490da1ec96b2e24b330296950be84424e11bd35
diff --git a/tools/bionicbb/presubmit.py b/tools/bionicbb/presubmit.py
new file mode 100644
index 0000000..cc6f3cc
--- /dev/null
+++ b/tools/bionicbb/presubmit.py
@@ -0,0 +1,203 @@
+#
+# 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