aboutsummaryrefslogtreecommitdiff
path: root/infra/cifuzz/continuous_integration.py
diff options
context:
space:
mode:
Diffstat (limited to 'infra/cifuzz/continuous_integration.py')
-rw-r--r--infra/cifuzz/continuous_integration.py169
1 files changed, 136 insertions, 33 deletions
diff --git a/infra/cifuzz/continuous_integration.py b/infra/cifuzz/continuous_integration.py
index b2e8af28e..47c4a7cbf 100644
--- a/infra/cifuzz/continuous_integration.py
+++ b/infra/cifuzz/continuous_integration.py
@@ -21,16 +21,21 @@ import logging
# pylint: disable=wrong-import-position,import-error
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import build_specified_commit
+import docker
import helper
import repo_manager
import retry
import utils
+import workspace_utils
# pylint: disable=too-few-public-methods
BuildPreparationResult = collections.namedtuple(
'BuildPreparationResult', ['success', 'image_repo_path', 'repo_manager'])
+_IMAGE_BUILD_TRIES = 3
+_IMAGE_BUILD_BACKOFF = 2
+
def fix_git_repo_for_diff(repo_manager_obj):
"""Fixes git repos cloned by the "checkout" action so that diffing works on
@@ -47,16 +52,34 @@ class BaseCi:
def __init__(self, config):
self.config = config
+ self.workspace = workspace_utils.Workspace(config)
+
+ def repo_dir(self):
+ """Returns the source repo path, if it has been checked out. None is
+ returned otherwise."""
+ if not os.path.exists(self.workspace.repo_storage):
+ return None
+
+ # Note: this assumes there is only one repo checked out here.
+ listing = os.listdir(self.workspace.repo_storage)
+ if len(listing) != 1:
+ raise RuntimeError('Invalid repo storage.')
+
+ repo_path = os.path.join(self.workspace.repo_storage, listing[0])
+ if not os.path.isdir(repo_path):
+ raise RuntimeError('Repo is not a directory.')
+
+ return repo_path
def prepare_for_fuzzer_build(self):
"""Builds the fuzzer builder image and gets the source code we need to
fuzz."""
- raise NotImplementedError('Children must implement this method.')
+ raise NotImplementedError('Child class must implement method.')
def get_diff_base(self):
"""Returns the base to diff against with git to get the change under
test."""
- raise NotImplementedError('Children must implement this method.')
+ raise NotImplementedError('Child class must implement method.')
def get_changed_code_under_test(self, repo_manager_obj):
"""Returns the changed files that need to be tested."""
@@ -65,10 +88,38 @@ class BaseCi:
logging.info('Diffing against %s.', base)
return repo_manager_obj.get_git_diff(base)
+ def get_build_command(self, host_repo_path, image_repo_path):
+ """Returns the command for building the project that is run inside the
+ project builder container."""
+ raise NotImplementedError('Child class must implement method.')
+
+
+def get_build_command():
+ """Returns the command to build the project inside the project builder
+ container."""
+ return 'compile'
+
+
+def get_replace_repo_and_build_command(host_repo_path, image_repo_path):
+ """Returns the command to replace the repo located at |image_repo_path| with
+ |host_repo_path| and build the project inside the project builder
+ container."""
+ rm_path = os.path.join(image_repo_path, '*')
+ image_src_path = os.path.dirname(image_repo_path)
+ build_command = get_build_command()
+ command = (f'cd / && rm -rf {rm_path} && cp -r {host_repo_path} '
+ f'{image_src_path} && cd - && {build_command}')
+ return command
+
def get_ci(config):
"""Determines what kind of CI is being used and returns the object
representing that system."""
+
+ if config.platform == config.Platform.EXTERNAL_GENERIC_CI:
+ # Non-OSS-Fuzz projects must bring their own source and their own build
+ # integration (which is relative to that source).
+ return ExternalGeneric(config)
if config.platform == config.Platform.EXTERNAL_GITHUB:
# Non-OSS-Fuzz projects must bring their own source and their own build
# integration (which is relative to that source).
@@ -127,27 +178,35 @@ class InternalGithub(GithubCiMixin, BaseCi):
assert self.config.pr_ref or self.config.commit_sha
# detect_main_repo builds the image as a side effect.
inferred_url, image_repo_path = (build_specified_commit.detect_main_repo(
- self.config.project_name, repo_name=self.config.project_repo_name))
+ self.config.oss_fuzz_project_name,
+ repo_name=self.config.project_repo_name))
if not inferred_url or not image_repo_path:
- logging.error('Could not detect repo from project %s.',
- self.config.project_name)
- return BuildPreparationResult(False, None, None)
+ logging.error('Could not detect repo.')
+ return BuildPreparationResult(success=False,
+ image_repo_path=None,
+ repo_manager=None)
- git_workspace = os.path.join(self.config.workspace, 'storage')
- os.makedirs(git_workspace, exist_ok=True)
+ os.makedirs(self.workspace.repo_storage, exist_ok=True)
# Use the same name used in the docker image so we can overwrite it.
image_repo_name = os.path.basename(image_repo_path)
# Checkout project's repo in the shared volume.
- manager = repo_manager.clone_repo_and_get_manager(inferred_url,
- git_workspace,
- repo_name=image_repo_name)
+ manager = repo_manager.clone_repo_and_get_manager(
+ inferred_url, self.workspace.repo_storage, repo_name=image_repo_name)
checkout_specified_commit(manager, self.config.pr_ref,
self.config.commit_sha)
- return BuildPreparationResult(True, image_repo_path, manager)
+ return BuildPreparationResult(success=True,
+ image_repo_path=image_repo_path,
+ repo_manager=manager)
+
+ def get_build_command(self, host_repo_path, image_repo_path): # pylint: disable=no-self-use
+ """Returns the command for building the project that is run inside the
+ project builder container. Command also replaces |image_repo_path| with
+ |host_repo_path|."""
+ return get_replace_repo_and_build_command(host_repo_path, image_repo_path)
class InternalGeneric(BaseCi):
@@ -162,35 +221,71 @@ class InternalGeneric(BaseCi):
logging.info('Building OSS-Fuzz project.')
# detect_main_repo builds the image as a side effect.
_, image_repo_path = (build_specified_commit.detect_main_repo(
- self.config.project_name, repo_name=self.config.project_repo_name))
+ self.config.oss_fuzz_project_name,
+ repo_name=self.config.project_repo_name))
if not image_repo_path:
- logging.error('Could not detect repo from project %s.',
- self.config.project_name)
- return BuildPreparationResult(False, None, None)
+ logging.error('Could not detect repo.')
+ return BuildPreparationResult(success=False,
+ image_repo_path=None,
+ repo_manager=None)
manager = repo_manager.RepoManager(self.config.project_src_path)
- return BuildPreparationResult(True, image_repo_path, manager)
+ return BuildPreparationResult(success=True,
+ image_repo_path=image_repo_path,
+ repo_manager=manager)
def get_diff_base(self):
return 'origin...'
-
-_IMAGE_BUILD_TRIES = 3
-_IMAGE_BUILD_BACKOFF = 2
+ def get_build_command(self, host_repo_path, image_repo_path): # pylint: disable=no-self-use
+ """Returns the command for building the project that is run inside the
+ project builder container. Command also replaces |image_repo_path| with
+ |host_repo_path|."""
+ return get_replace_repo_and_build_command(host_repo_path, image_repo_path)
@retry.wrap(_IMAGE_BUILD_TRIES, _IMAGE_BUILD_BACKOFF)
-def build_external_project_docker_image(project_name, project_src,
- build_integration_path):
+def build_external_project_docker_image(project_src, build_integration_path):
"""Builds the project builder image for an external (non-OSS-Fuzz) project.
Returns True on success."""
dockerfile_path = os.path.join(build_integration_path, 'Dockerfile')
- tag = 'gcr.io/oss-fuzz/{project_name}'.format(project_name=project_name)
- command = ['-t', tag, '-f', dockerfile_path, project_src]
+ command = [
+ '-t', docker.EXTERNAL_PROJECT_IMAGE, '-f', dockerfile_path, project_src
+ ]
return helper.docker_build(command)
+class ExternalGeneric(BaseCi):
+ """CI implementation for generic CI for external (non-OSS-Fuzz) projects."""
+
+ def get_diff_base(self):
+ return 'origin...'
+
+ def prepare_for_fuzzer_build(self):
+ logging.info('ExternalGeneric: preparing for fuzzer build.')
+ manager = repo_manager.RepoManager(self.config.project_src_path)
+ build_integration_abs_path = os.path.join(
+ manager.repo_dir, self.config.build_integration_path)
+ if not build_external_project_docker_image(manager.repo_dir,
+ build_integration_abs_path):
+ logging.error('Failed to build external project: %s.',
+ self.config.oss_fuzz_project_name)
+ return BuildPreparationResult(success=False,
+ image_repo_path=None,
+ repo_manager=None)
+
+ image_repo_path = os.path.join('/src', self.config.project_repo_name)
+ return BuildPreparationResult(success=True,
+ image_repo_path=image_repo_path,
+ repo_manager=manager)
+
+ def get_build_command(self, host_repo_path, image_repo_path): # pylint: disable=no-self-use
+ """Returns the command for building the project that is run inside the
+ project builder container."""
+ return get_build_command()
+
+
class ExternalGithub(GithubCiMixin, BaseCi):
"""Class representing CI for a non-OSS-Fuzz project on Github Actions."""
@@ -200,24 +295,32 @@ class ExternalGithub(GithubCiMixin, BaseCi):
projects are expected to bring their own source code to CIFuzz. Returns True
on success."""
logging.info('Building external project.')
- git_workspace = os.path.join(self.config.workspace, 'storage')
- os.makedirs(git_workspace, exist_ok=True)
+ os.makedirs(self.workspace.repo_storage, exist_ok=True)
# Checkout before building, so we don't need to rely on copying the source
# into the image.
# TODO(metzman): Figure out if we want second copy at all.
manager = repo_manager.clone_repo_and_get_manager(
self.config.git_url,
- git_workspace,
+ self.workspace.repo_storage,
repo_name=self.config.project_repo_name)
checkout_specified_commit(manager, self.config.pr_ref,
self.config.commit_sha)
- build_integration_path = os.path.join(manager.repo_dir,
- self.config.build_integration_path)
- if not build_external_project_docker_image(
- self.config.project_name, manager.repo_dir, build_integration_path):
+ build_integration_abs_path = os.path.join(
+ manager.repo_dir, self.config.build_integration_path)
+ if not build_external_project_docker_image(manager.repo_dir,
+ build_integration_abs_path):
logging.error('Failed to build external project.')
- return BuildPreparationResult(False, None, None)
+ return BuildPreparationResult(success=False,
+ image_repo_path=None,
+ repo_manager=None)
image_repo_path = os.path.join('/src', self.config.project_repo_name)
- return BuildPreparationResult(True, image_repo_path, manager)
+ return BuildPreparationResult(success=True,
+ image_repo_path=image_repo_path,
+ repo_manager=manager)
+
+ def get_build_command(self, host_repo_path, image_repo_path): # pylint: disable=no-self-use
+ """Returns the command for building the project that is run inside the
+ project builder container."""
+ return get_build_command()