diff --git a/infra/gcb/badge_images/building.png b/infra/gcb/badge_images/building.png
new file mode 100644
index 000000000..9e7e73447
Binary files /dev/null and b/infra/gcb/badge_images/building.png differ
diff --git a/infra/gcb/badge_images/building.svg b/infra/gcb/badge_images/building.svg
new file mode 100644
index 000000000..19f28d7fb
--- /dev/null
+++ b/infra/gcb/badge_images/building.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/infra/gcb/badge_images/coverage_failing.png b/infra/gcb/badge_images/coverage_failing.png
new file mode 100644
index 000000000..85abe2352
Binary files /dev/null and b/infra/gcb/badge_images/coverage_failing.png differ
diff --git a/infra/gcb/badge_images/coverage_failing.svg b/infra/gcb/badge_images/coverage_failing.svg
new file mode 100644
index 000000000..dc7b72e39
--- /dev/null
+++ b/infra/gcb/badge_images/coverage_failing.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/infra/gcb/badge_images/failing.png b/infra/gcb/badge_images/failing.png
new file mode 100644
index 000000000..0d2bb4701
Binary files /dev/null and b/infra/gcb/badge_images/failing.png differ
diff --git a/infra/gcb/badge_images/failing.svg b/infra/gcb/badge_images/failing.svg
new file mode 100644
index 000000000..ed0f8621e
--- /dev/null
+++ b/infra/gcb/badge_images/failing.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/infra/gcb/builds_status.py b/infra/gcb/builds_status.py
index 0b2fe15ef..eb98ac166 100755
--- a/infra/gcb/builds_status.py
+++ b/infra/gcb/builds_status.py
@@ -20,6 +20,7 @@ import build_project
STATUS_BUCKET = 'oss-fuzz-build-logs'
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+BADGE_DIR = 'badges/'
RETRY_COUNT = 3
RETRY_WAIT = 5
MAX_BUILD_RESULTS = 2000
@@ -173,6 +174,45 @@ def update_build_status(
upload_status(successes, failures, status_filename)
+def update_build_badges(builds, projects, build_tag, coverage_tag):
+ for project in projects:
+ last_build = find_last_build(builds, project, build_tag)
+ last_coverage_build = find_last_build(builds, project, coverage_tag)
+ if not last_build or not last_coverage_build:
+ continue
+
+ badge = 'building'
+ if not is_build_successful(last_coverage_build):
+ badge = 'coverage_failing'
+ if not is_build_successful(last_build):
+ badge = 'failing'
+
+ print("[badge] {}: {}".format(project, badge))
+
+ storage_client = storage.Client()
+ status_bucket = storage_client.get_bucket(STATUS_BUCKET)
+
+ # Supported image types for badges
+ image_types = {
+ 'svg': 'image/svg+xml',
+ 'png': 'image/png'
+ }
+ for extension, mime_type in image_types.items():
+ badge_name = '{badge}.{extension}'.format(
+ badge=badge, extension=extension)
+ # Retrieve the image relative to this script's location
+ badge_file = os.path.join(
+ SCRIPT_DIR, 'badge_images', image_directory, badge_name)
+
+ # The uploaded blob name should look like `badges/project.png`
+ blob_name = '{badge_dir}{project_name}.{extension}'.format(
+ badge_dir=BADGE_DIR, project_name=project,
+ extension=extension)
+
+ badge_blob = status_bucket.blob(blob_name)
+ badge_blob.upload_from_filename(badge_file, content_type=mime_type)
+
+
def main():
if len(sys.argv) != 2:
usage()
@@ -190,6 +230,10 @@ def main():
build_and_run_coverage.COVERAGE_BUILD_TAG,
status_filename='status-coverage.json')
+ update_build_badges(builds, projects,
+ build_tag=build_project.FUZZING_BUILD_TAG,
+ coverage_tag=build_and_run_coverage.COVERAGE_BUILD_TAG)
+
if __name__ == '__main__':
main()