1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
# SPDX-License-Identifier: Apache-2.0
# Copyright 2017 The Meson development team
from __future__ import annotations
from mesonbuild import environment, mesonlib
import argparse, re, sys, os, subprocess, pathlib, stat
import typing as T
def coverage(outputs: T.List[str], source_root: str, subproject_root: str, build_root: str, log_dir: str, use_llvm_cov: bool,
gcovr_exe: str, llvm_cov_exe: str) -> int:
outfiles = []
exitcode = 0
if gcovr_exe == '':
gcovr_exe = None
else:
gcovr_exe, gcovr_version = environment.detect_gcovr(gcovr_exe)
if llvm_cov_exe == '' or not mesonlib.exe_exists([llvm_cov_exe, '--version']):
llvm_cov_exe = None
lcov_exe, lcov_version, genhtml_exe = environment.detect_lcov_genhtml()
# load config files for tools if available in the source tree
# - lcov requires manually specifying a per-project config
# - gcovr picks up the per-project config, and also supports filtering files
# so don't exclude subprojects ourselves, if the project has a config,
# because they either don't want that, or should set it themselves
lcovrc = os.path.join(source_root, '.lcovrc')
if os.path.exists(lcovrc):
lcov_config = ['--config-file', lcovrc]
else:
lcov_config = []
if lcov_exe and mesonlib.version_compare(lcov_version, '>=2.0'):
lcov_exe_rc_branch_coverage = ['--rc', 'branch_coverage=1']
else:
lcov_exe_rc_branch_coverage = ['--rc', 'lcov_branch_coverage=1']
gcovr_config = ['-e', re.escape(subproject_root)]
# gcovr >= 4.2 requires a different syntax for out of source builds
if gcovr_exe and mesonlib.version_compare(gcovr_version, '>=4.2'):
gcovr_base_cmd = [gcovr_exe, '-r', source_root, build_root]
# it also started supporting the config file
if os.path.exists(os.path.join(source_root, 'gcovr.cfg')):
gcovr_config = []
else:
gcovr_base_cmd = [gcovr_exe, '-r', build_root]
if use_llvm_cov:
gcov_exe_args = ['--gcov-executable', llvm_cov_exe + ' gcov']
else:
gcov_exe_args = []
if not outputs or 'xml' in outputs:
if gcovr_exe and mesonlib.version_compare(gcovr_version, '>=3.3'):
subprocess.check_call(gcovr_base_cmd + gcovr_config +
['-x',
'-o', os.path.join(log_dir, 'coverage.xml')
] + gcov_exe_args)
outfiles.append(('Xml', pathlib.Path(log_dir, 'coverage.xml')))
elif outputs:
print('gcovr >= 3.3 needed to generate Xml coverage report')
exitcode = 1
if not outputs or 'sonarqube' in outputs:
if gcovr_exe and mesonlib.version_compare(gcovr_version, '>=4.2'):
subprocess.check_call(gcovr_base_cmd + gcovr_config +
['--sonarqube',
'-o', os.path.join(log_dir, 'sonarqube.xml'),
] + gcov_exe_args)
outfiles.append(('Sonarqube', pathlib.Path(log_dir, 'sonarqube.xml')))
elif outputs:
print('gcovr >= 4.2 needed to generate Xml coverage report')
exitcode = 1
if not outputs or 'text' in outputs:
if gcovr_exe and mesonlib.version_compare(gcovr_version, '>=3.3'):
subprocess.check_call(gcovr_base_cmd + gcovr_config +
['-o', os.path.join(log_dir, 'coverage.txt')] +
gcov_exe_args)
outfiles.append(('Text', pathlib.Path(log_dir, 'coverage.txt')))
elif outputs:
print('gcovr >= 3.3 needed to generate text coverage report')
exitcode = 1
if not outputs or 'html' in outputs:
if lcov_exe and genhtml_exe:
htmloutdir = os.path.join(log_dir, 'coveragereport')
covinfo = os.path.join(log_dir, 'coverage.info')
initial_tracefile = covinfo + '.initial'
run_tracefile = covinfo + '.run'
raw_tracefile = covinfo + '.raw'
lcov_subpoject_exclude = []
if os.path.exists(subproject_root):
lcov_subpoject_exclude.append(os.path.join(subproject_root, '*'))
if use_llvm_cov:
# Create a shim to allow using llvm-cov as a gcov tool.
if mesonlib.is_windows():
llvm_cov_shim_path = os.path.join(log_dir, 'llvm-cov.bat')
with open(llvm_cov_shim_path, 'w', encoding='utf-8') as llvm_cov_bat:
llvm_cov_bat.write(f'@"{llvm_cov_exe}" gcov %*')
else:
llvm_cov_shim_path = os.path.join(log_dir, 'llvm-cov.sh')
with open(llvm_cov_shim_path, 'w', encoding='utf-8') as llvm_cov_sh:
llvm_cov_sh.write(f'#!/usr/bin/env sh\nexec "{llvm_cov_exe}" gcov $@')
os.chmod(llvm_cov_shim_path, os.stat(llvm_cov_shim_path).st_mode | stat.S_IEXEC)
gcov_tool_args = ['--gcov-tool', llvm_cov_shim_path]
else:
gcov_tool_args = []
subprocess.check_call([lcov_exe,
'--directory', build_root,
'--capture',
'--initial',
'--output-file',
initial_tracefile] +
lcov_config +
gcov_tool_args)
subprocess.check_call([lcov_exe,
'--directory', build_root,
'--capture',
'--output-file', run_tracefile,
'--no-checksum',
*lcov_exe_rc_branch_coverage] +
lcov_config +
gcov_tool_args)
# Join initial and test results.
subprocess.check_call([lcov_exe,
'-a', initial_tracefile,
'-a', run_tracefile,
*lcov_exe_rc_branch_coverage,
'-o', raw_tracefile] + lcov_config)
# Remove all directories outside the source_root from the covinfo
subprocess.check_call([lcov_exe,
'--extract', raw_tracefile,
os.path.join(source_root, '*'),
*lcov_exe_rc_branch_coverage,
'--output-file', covinfo] + lcov_config)
# Remove all directories inside subproject dir
subprocess.check_call([lcov_exe,
'--remove', covinfo,
*lcov_subpoject_exclude,
*lcov_exe_rc_branch_coverage,
'--ignore-errors', 'unused',
'--output-file', covinfo] + lcov_config)
subprocess.check_call([genhtml_exe,
'--prefix', build_root,
'--prefix', source_root,
'--output-directory', htmloutdir,
'--title', 'Code coverage',
'--legend',
'--show-details',
'--branch-coverage',
covinfo] + lcov_config)
outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html')))
elif gcovr_exe and mesonlib.version_compare(gcovr_version, '>=3.3'):
htmloutdir = os.path.join(log_dir, 'coveragereport')
if not os.path.isdir(htmloutdir):
os.mkdir(htmloutdir)
subprocess.check_call(gcovr_base_cmd + gcovr_config +
['--html',
'--html-nested',
'--print-summary',
'-o', os.path.join(htmloutdir, 'index.html'),
] + gcov_exe_args)
outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html')))
elif outputs:
print('lcov/genhtml or gcovr >= 3.3 needed to generate Html coverage report')
exitcode = 1
if not outputs and not outfiles:
print('Need gcovr or lcov/genhtml to generate any coverage reports')
exitcode = 1
if outfiles:
print('')
for (filetype, path) in outfiles:
print(filetype + ' coverage report can be found at', path.as_uri())
return exitcode
def run(args: T.List[str]) -> int:
if not os.path.isfile('build.ninja'):
print('Coverage currently only works with the Ninja backend.')
return 1
parser = argparse.ArgumentParser(description='Generate coverage reports')
parser.add_argument('--text', dest='outputs', action='append_const',
const='text', help='generate Text report')
parser.add_argument('--xml', dest='outputs', action='append_const',
const='xml', help='generate Xml report')
parser.add_argument('--sonarqube', dest='outputs', action='append_const',
const='sonarqube', help='generate Sonarqube Xml report')
parser.add_argument('--html', dest='outputs', action='append_const',
const='html', help='generate Html report')
parser.add_argument('--use-llvm-cov', action='store_true',
help='use llvm-cov')
parser.add_argument('--gcovr', action='store', default='',
help='The gcovr executable to use if specified')
parser.add_argument('--llvm-cov', action='store', default='',
help='The llvm-cov executable to use if specified')
parser.add_argument('source_root')
parser.add_argument('subproject_root')
parser.add_argument('build_root')
parser.add_argument('log_dir')
options = parser.parse_args(args)
return coverage(options.outputs, options.source_root,
options.subproject_root, options.build_root,
options.log_dir, options.use_llvm_cov,
options.gcovr, options.llvm_cov)
if __name__ == '__main__':
sys.exit(run(sys.argv[1:]))
|