• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright 2019 Google Inc.
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################################################################################
17"""Build modified projects."""
18
19from __future__ import print_function
20
21import os
22import re
23import subprocess
24import yaml
25
26DEFAULT_ARCHITECTURES = ['x86_64']
27DEFAULT_ENGINES = ['afl', 'libfuzzer']
28DEFAULT_SANITIZERS = ['address', 'undefined']
29
30
31def get_modified_buildable_projects():
32  """Returns a list of all the projects modified in this commit that have a
33  build.sh file."""
34  master_head_sha = subprocess.check_output(
35      ['git', 'merge-base', 'HEAD', 'FETCH_HEAD']).decode().strip()
36  output = subprocess.check_output(
37      ['git', 'diff', '--name-only', 'HEAD', master_head_sha]).decode()
38  projects_regex = '.*projects/(?P<name>.*)/.*\n'
39  modified_projects = set(re.findall(projects_regex, output))
40  projects_dir = os.path.join(get_oss_fuzz_root(), 'projects')
41  # Filter out projects without build.sh files since new projects and reverted
42  # projects frequently don't have them. In these cases we don't want Travis's
43  # builds to fail.
44  modified_buildable_projects = []
45  for project in modified_projects:
46    if not os.path.exists(os.path.join(projects_dir, project, 'build.sh')):
47      print('Project {0} does not have a build.sh. skipping build.'.format(
48          project))
49      continue
50    modified_buildable_projects.append(project)
51  return modified_buildable_projects
52
53
54def get_oss_fuzz_root():
55  """Get the absolute path of the root of the oss-fuzz checkout."""
56  script_path = os.path.realpath(__file__)
57  return os.path.abspath(
58      os.path.dirname(os.path.dirname(os.path.dirname(script_path))))
59
60
61def execute_helper_command(helper_command):
62  """Execute |helper_command| using helper.py."""
63  root = get_oss_fuzz_root()
64  script_path = os.path.join(root, 'infra', 'helper.py')
65  command = ['python', script_path] + helper_command
66  print('Running command: %s' % ' '.join(command))
67  subprocess.check_call(command)
68
69
70def build_fuzzers(project, engine, sanitizer, architecture):
71  """Execute helper.py's build_fuzzers command on |project|. Build the fuzzers
72  with |engine| and |sanitizer| for |architecture|."""
73  execute_helper_command([
74      'build_fuzzers', project, '--engine', engine, '--sanitizer', sanitizer,
75      '--architecture', architecture
76  ])
77
78
79def check_build(project, engine, sanitizer, architecture):
80  """Execute helper.py's check_build command on |project|, assuming it was most
81  recently built with |engine| and |sanitizer| for |architecture|."""
82  execute_helper_command([
83      'check_build', project, '--engine', engine, '--sanitizer', sanitizer,
84      '--architecture', architecture
85  ])
86
87
88def should_build(project_yaml):
89  """Is the build specified by travis enabled in the |project_yaml|?"""
90
91  def is_enabled(env_var, yaml_name, defaults):
92    """Is the value of |env_var| enabled in |project_yaml| (in the |yaml_name|
93    section)? Uses |defaults| if |yaml_name| section is unspecified."""
94    return os.getenv(env_var) in project_yaml.get(yaml_name, defaults)
95
96  return (is_enabled('TRAVIS_ENGINE', 'fuzzing_engines', DEFAULT_ENGINES) and
97          is_enabled('TRAVIS_SANITIZER', 'sanitizers', DEFAULT_SANITIZERS) and
98          is_enabled('TRAVIS_ARCHITECTURE', 'architectures',
99                     DEFAULT_ARCHITECTURES))
100
101
102def build_project(project):
103  """Do the build of |project| that is specified by the TRAVIS_* environment
104  variables (TRAVIS_SANITIZER, TRAVIS_ENGINE, and TRAVIS_ARCHITECTURE)."""
105  root = get_oss_fuzz_root()
106  project_yaml_path = os.path.join(root, 'projects', project, 'project.yaml')
107  with open(project_yaml_path) as fp:
108    project_yaml = yaml.safe_load(fp)
109
110  if project_yaml.get('disabled', False):
111    print('Project {0} is disabled, skipping build.'.format(project))
112    return
113
114  engine = os.getenv('TRAVIS_ENGINE')
115  sanitizer = os.getenv('TRAVIS_SANITIZER')
116  architecture = os.getenv('TRAVIS_ARCHITECTURE')
117
118  if not should_build(project_yaml):
119    print(('Specified build: engine: {0}, sanitizer: {1}, architecture: {2} '
120           'not enabled for this project: {3}. skipping build.').format(
121               engine, sanitizer, architecture, project))
122
123    return
124
125  print('Building project', project)
126  build_fuzzers(project, engine, sanitizer, architecture)
127  if engine != 'none':
128    check_build(project, engine, sanitizer, architecture)
129
130
131def main():
132  projects = get_modified_buildable_projects()
133  failed_projects = []
134  for project in projects:
135    try:
136      build_project(project)
137    except subprocess.CalledProcessError:
138      failed_projects.append(project)
139
140  if failed_projects:
141    print('Failed projects:', ' '.join(failed_projects))
142    exit(1)
143
144
145if __name__ == '__main__':
146  main()
147