
This CL implements the Clang code coverage compiler wrapper, which can be used to instrument a subset of source files, and the main use case is to enable generating code coverage reports at per-cl level during try jobs. This CL only focuses on the wrapper itself, how to hook this wrapper into the build system will be addressed in a separate CL. Bug: 898695 Change-Id: Idb21640b5566ce78089059b3d0116390b488a383 Reviewed-on: https://chromium-review.googlesource.com/c/1301969 Commit-Queue: Yuke Liao <liaoyuke@chromium.org> Reviewed-by: Dirk Pranke <dpranke@chromium.org> Reviewed-by: Takuto Ikuta <tikuta@chromium.org> Cr-Commit-Position: refs/heads/master@{#604381}
84 lines
3.3 KiB
Python
Executable File
84 lines
3.3 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Copyright 2018 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
"""Adds code coverage flags to the invocations of the Clang C/C++ compiler.
|
|
|
|
This script is used to instrument a subset of the source files, and the list of
|
|
files to instrument is specified by an input file that is passed to this script
|
|
as a command-line argument.
|
|
|
|
The path to the coverage instrumentation input file should be relative to the
|
|
root build directory, and the file consists of multiple lines where each line
|
|
represents a path to a source file, and the specified paths must be relative to
|
|
the root build directory. e.g. ../../base/task/post_task.cc for build
|
|
directory 'out/Release'.
|
|
|
|
One caveat with this compiler wrapper is that it may introduce unexpected
|
|
behaviors in incremental builds when the file path to the coverage
|
|
instrumentation input file changes between consecutive runs, so callers of this
|
|
script are strongly advised to always use the same path such as
|
|
"${root_build_dir}/coverage_instrumentation_input.txt".
|
|
|
|
It's worth noting on try job builders, if the contents of the instrumentation
|
|
file changes so that a file doesn't need to be instrumented any longer, it will
|
|
be recompiled automatically because if try job B runs after try job A, the files
|
|
that were instrumented in A will be updated (i.e., reverted to the checked in
|
|
version) in B, and so they'll be considered out of date by ninja and recompiled.
|
|
|
|
Example usage:
|
|
clang_code_coverage_wrapper.py \\
|
|
--files-to-instrument=coverage_instrumentation_input.txt
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
# Flags used to enable coverage instrumentation.
|
|
_COVERAGE_FLAGS = ['-fprofile-instr-generate', '-fcoverage-mapping']
|
|
|
|
|
|
def main():
|
|
# TODO(crbug.com/898695): Make this wrapper work on Windows platform.
|
|
arg_parser = argparse.ArgumentParser()
|
|
arg_parser.usage = __doc__
|
|
arg_parser.add_argument(
|
|
'--files-to-instrument',
|
|
type=str,
|
|
required=True,
|
|
help='Path to a file that contains a list of file names to instrument.')
|
|
arg_parser.add_argument('args', nargs=argparse.REMAINDER)
|
|
parsed_args = arg_parser.parse_args()
|
|
|
|
if not os.path.isfile(parsed_args.files_to_instrument):
|
|
raise Exception('Path to the coverage instrumentation file: "%s" doesn\'t '
|
|
'exist.' % parsed_args.files_to_instrument)
|
|
|
|
compile_command = parsed_args.args
|
|
try:
|
|
# The command is assumed to use Clang as the compiler, and the path to the
|
|
# source file is behind the -c argument, and the path to the source path is
|
|
# relative to the root build directory. For example:
|
|
# clang++ -fvisibility=hidden -c ../../base/files/file_path.cc -o \
|
|
# obj/base/base/file_path.o
|
|
index_dash_c = compile_command.index('-c')
|
|
except ValueError:
|
|
print '-c argument is not found in the compile command.'
|
|
raise
|
|
|
|
if index_dash_c + 1 >= len(compile_command):
|
|
raise Exception('Source file to be compiled is missing from the command.')
|
|
|
|
compile_source_file = compile_command[index_dash_c + 1]
|
|
with open(parsed_args.files_to_instrument) as f:
|
|
if compile_source_file + '\n' in f.read():
|
|
compile_command.extend(_COVERAGE_FLAGS)
|
|
|
|
return subprocess.call(compile_command)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|