diff --git a/infra/ci/build.py b/infra/ci/build.py index 9bdbb9699..9fde53fe0 100755 --- a/infra/ci/build.py +++ b/infra/ci/build.py @@ -18,12 +18,15 @@ from __future__ import print_function +import enum import os import re import sys import subprocess import yaml +CANARY_PROJECT = 'skcms' + DEFAULT_ARCHITECTURES = ['x86_64'] DEFAULT_ENGINES = ['afl', 'honggfuzz', 'libfuzzer'] DEFAULT_SANITIZERS = ['address', 'undefined'] @@ -32,13 +35,18 @@ DEFAULT_SANITIZERS = ['address', 'undefined'] LANGUAGES_WITH_COVERAGE_SUPPORT = ['c', 'c++'] +def get_changed_files(): + """Returns the output of a git command that discovers changed files.""" + return subprocess.check_output(['git', 'diff', '--name-only', + 'FETCH_HEAD']).decode() + + def get_modified_buildable_projects(): """Returns a list of all the projects modified in this commit that have a build.sh file.""" - output = subprocess.check_output(['git', 'diff', '--name-only', - 'FETCH_HEAD']).decode() + git_output = get_changed_files() projects_regex = '.*projects/(?P.*)/.*\n' - modified_projects = set(re.findall(projects_regex, output)) + modified_projects = set(re.findall(projects_regex, git_output)) projects_dir = os.path.join(get_oss_fuzz_root(), 'projects') # Filter out projects without Dockerfile files since new projects and reverted # projects frequently don't have them. In these cases we don't want Travis's @@ -156,9 +164,22 @@ def build_project(project): check_build(project, engine, sanitizer, architecture) -def main(): - """Build modified projects.""" +class BuildModifiedProjectsResult(enum.Enum): + """Enum containing the return values of build_modified_projects().""" + NONE_BUILT = 0 + BUILD_SUCCESS = 1 + BUILD_FAIL = 2 + + +def build_modified_projects(): + """Build modified projects. Returns BuildModifiedProjectsResult.NONE_BUILT if + no builds were attempted. Returns BuildModifiedProjectsResult.BUILD_SUCCESS if + all attempts succeed, otherwise returns + BuildModifiedProjectsResult.BUILD_FAIL.""" projects = get_modified_buildable_projects() + if not projects: + return BuildModifiedProjectsResult.NONE_BUILT + failed_projects = [] for project in projects: try: @@ -168,6 +189,42 @@ def main(): if failed_projects: print('Failed projects:', ' '.join(failed_projects)) + return BuildModifiedProjectsResult.BUILD_FAIL + + return BuildModifiedProjectsResult.BUILD_SUCCESS + + +def should_build_canary_project(): + """Returns True if we should build the canary project.""" + git_output = get_changed_files() + infra_code_regex = '.*infra/.*\n' + return re.search(infra_code_regex, git_output) is not None + + +def build_canary_project(): + """Builds a specific project when infra/ is changed to verify that infra/ + changes don't break things. Returns False if build was attempted but + failed.""" + if not should_build_canary_project(): + return True + + try: + build_project('skcms') + except subprocess.CalledProcessError: + return False + + return True + + +def main(): + """Build modified projects or canary project.""" + result = build_modified_projects() + if result == BuildModifiedProjectsResult.BUILD_FAIL: + return 1 + + # It's unnecessary to build the canary if we've built any projects already. + no_projects_built = result == BuildModifiedProjectsResult.NONE_BUILT + if no_projects_built and not build_canary_project(): return 1 return 0