diff --git a/docs/markdown/Feature-autodetection.md b/docs/markdown/Feature-autodetection.md index 65318ecd3..f865174be 100644 --- a/docs/markdown/Feature-autodetection.md +++ b/docs/markdown/Feature-autodetection.md @@ -16,4 +16,4 @@ If you do not wish to use CCache for some reason, just specify your compiler wit Coverage -- -When doing a code coverage build, Meson will check the existence of binaries `gcovr`, `lcov` and `genhtml`. If the first one is found, it will create targets called *coverage-text* and *coverage-xml*. If the latter two are found, it generates the target *coverage-html*. You can then generate coverage reports just by calling e.g. `ninja coverage-xml`. +When doing a code coverage build, Meson will check the existence of binaries `gcovr`, `lcov` and `genhtml`. If the first one is found, it will create targets called *coverage-text* and *coverage-xml*. If the latter two or a new enough `gcovr` is found, it generates the target *coverage-html*. You can then generate coverage reports just by calling e.g. `ninja coverage-xml`. diff --git a/docs/markdown/Unit-tests.md b/docs/markdown/Unit-tests.md index afbeaa0e6..53ce9ec57 100644 --- a/docs/markdown/Unit-tests.md +++ b/docs/markdown/Unit-tests.md @@ -30,7 +30,7 @@ Note how you need to specify multiple values as an array. Coverage -- -If you enable coverage measurements by giving Meson the command line flag `-Db_coverage=true`, you can generate coverage reports. Meson will autodetect what coverage generator tools you have installed and will generate the corresponding targets. These targets are `coverage-xml` and `coverage-text` which are both provided by [Gcovr](http://gcovr.com) and `coverage-html`, which requires [Lcov](https://ltp.sourceforge.io/coverage/lcov.php) and [GenHTML](https://linux.die.net/man/1/genhtml). +If you enable coverage measurements by giving Meson the command line flag `-Db_coverage=true`, you can generate coverage reports. Meson will autodetect what coverage generator tools you have installed and will generate the corresponding targets. These targets are `coverage-xml` and `coverage-text` which are both provided by [Gcovr](http://gcovr.com) and `coverage-html`, which requires [Lcov](https://ltp.sourceforge.io/coverage/lcov.php) and [GenHTML](https://linux.die.net/man/1/genhtml) or [Gcovr](http://gcovr.com) with html support. The output of these commands is written to the log directory `meson-logs` in your build directory. diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 0c774c15a..df1f4271f 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -628,19 +628,24 @@ int dummy; self.generate_coverage_legacy_rules(outfile) def generate_coverage_legacy_rules(self, outfile): - (gcovr_exe, lcov_exe, genhtml_exe) = environment.find_coverage_tools() + (gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe) = environment.find_coverage_tools() added_rule = False if gcovr_exe: + # gcovr >= 3.1 interprets rootdir differently + if gcovr_new_rootdir: + rootdir = self.environment.get_build_dir() + else: + rootdir = self.environment.get_source_dir(), added_rule = True elem = NinjaBuildElement(self.all_outputs, 'meson-coverage-xml', 'CUSTOM_COMMAND', '') - elem.add_item('COMMAND', [gcovr_exe, '-x', '-r', self.environment.get_source_dir(), + elem.add_item('COMMAND', [gcovr_exe, '-x', '-r', rootdir, '-o', os.path.join(self.environment.get_log_dir(), 'coverage.xml')]) elem.add_item('DESC', 'Generating XML coverage report.') elem.write(outfile) # Alias that runs the target defined above self.create_target_alias('meson-coverage-xml', outfile) elem = NinjaBuildElement(self.all_outputs, 'meson-coverage-text', 'CUSTOM_COMMAND', '') - elem.add_item('COMMAND', [gcovr_exe, '-r', self.environment.get_source_dir(), + elem.add_item('COMMAND', [gcovr_exe, '-r', rootdir, '-o', os.path.join(self.environment.get_log_dir(), 'coverage.txt')]) elem.add_item('DESC', 'Generating text coverage report.') elem.write(outfile) @@ -682,6 +687,19 @@ int dummy; elem.add_item('COMMAND', command) elem.add_item('DESC', 'Generating HTML coverage report.') elem.write(outfile) + elif gcovr_exe and gcovr_new_rootdir: + added_rule = True + htmloutdir = os.path.join(self.environment.get_log_dir(), 'coveragereport') + phony_elem = NinjaBuildElement(self.all_outputs, 'meson-coverage-html', 'phony', os.path.join(htmloutdir, 'index.html')) + phony_elem.write(outfile) + # Alias that runs the target defined above + self.create_target_alias('meson-coverage-html', outfile) + elem = NinjaBuildElement(self.all_outputs, os.path.join(htmloutdir, 'index.html'), 'CUSTOM_COMMAND', '') + command = [gcovr_exe, '--html', '--html-details', '-r', self.environment.get_build_dir(), + '-o', os.path.join(htmloutdir, 'index.html')] + elem.add_item('COMMAND', command) + elem.add_item('DESC', 'Generating HTML coverage report.') + elem.write(outfile) if not added_rule: mlog.warning('coverage requested but neither gcovr nor lcov/genhtml found.') diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 31ca2a2dc..ff7c706e8 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -76,19 +76,32 @@ cflags_mapping = {'c': 'CFLAGS', 'd': 'DFLAGS', 'vala': 'VALAFLAGS'} +def detect_gcovr(version='3.1', log=False): + gcovr_exe = 'gcovr' + try: + p, found = Popen_safe([gcovr_exe, '--version'])[0:2] + except (FileNotFoundError, PermissionError): + # Doesn't exist in PATH or isn't executable + return None, None + found = search_version(found) + if p.returncode == 0: + if log: + mlog.log('Found gcovr-{} at {}'.format(found, shlex.quote(shutil.which(gcovr_exe)))) + return gcovr_exe, mesonlib.version_compare(found, '>=' + version) + return None, None def find_coverage_tools(): - gcovr_exe = 'gcovr' + gcovr_exe, gcovr_new_rootdir = detect_gcovr() + lcov_exe = 'lcov' genhtml_exe = 'genhtml' - if not mesonlib.exe_exists([gcovr_exe, '--version']): - gcovr_exe = None if not mesonlib.exe_exists([lcov_exe, '--version']): lcov_exe = None if not mesonlib.exe_exists([genhtml_exe, '--version']): genhtml_exe = None - return gcovr_exe, lcov_exe, genhtml_exe + + return gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe def detect_ninja(version='1.5', log=False): for n in ['ninja', 'ninja-build']: diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py index 47f4cda29..2d1f8c390 100644 --- a/mesonbuild/scripts/coverage.py +++ b/mesonbuild/scripts/coverage.py @@ -17,15 +17,20 @@ from mesonbuild import environment import sys, os, subprocess, pathlib def coverage(source_root, build_root, log_dir): - (gcovr_exe, lcov_exe, genhtml_exe) = environment.find_coverage_tools() + (gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe) = environment.find_coverage_tools() if gcovr_exe: + # gcovr >= 3.1 interprets rootdir differently + if gcovr_new_rootdir: + rootdir = build_root + else: + rootdir = source_root subprocess.check_call([gcovr_exe, '-x', - '-r', source_root, + '-r', rootdir, '-o', os.path.join(log_dir, 'coverage.xml'), ]) subprocess.check_call([gcovr_exe, - '-r', source_root, + '-r', rootdir, '-o', os.path.join(log_dir, 'coverage.txt'), ]) if lcov_exe and genhtml_exe: @@ -65,13 +70,21 @@ def coverage(source_root, build_root, log_dir): '--show-details', '--branch-coverage', covinfo]) + elif gcovr_exe and gcovr_new_rootdir: + htmloutdir = os.path.join(log_dir, 'coveragereport') + subprocess.check_call([gcovr_exe, + '--html', + '--html-details', + '-r', build_root, + '-o', os.path.join(htmloutdir, 'index.html'), + ]) if gcovr_exe: print('') print('XML coverage report can be found at', pathlib.Path(log_dir, 'coverage.xml').as_uri()) print('Text coverage report can be found at', pathlib.Path(log_dir, 'coverage.txt').as_uri()) - if lcov_exe and genhtml_exe: + if (lcov_exe and genhtml_exe) or (gcovr_exe and gcovr_new_rootdir): print('Html coverage report can be found at', pathlib.Path(htmloutdir, 'index.html').as_uri()) return 0 diff --git a/run_unittests.py b/run_unittests.py index 9c7b16bd5..bb8ce4617 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -2639,10 +2639,11 @@ class LinuxlikeTests(BasePlatformTests): self.assertIn("-fsanitize=address", i["command"]) def test_coverage(self): - if not shutil.which('gcovr'): + gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + if not gcovr_exe: raise unittest.SkipTest('gcovr not found') - if not shutil.which('genhtml'): - raise unittest.SkipTest('genhtml not found') + if not shutil.which('genhtml') and not gcovr_new_rootdir: + raise unittest.SkipTest('genhtml not found and gcovr is too old') if 'clang' in os.environ.get('CC', ''): # We need to use llvm-cov instead of gcovr with clang raise unittest.SkipTest('Coverage does not work with clang right now, help wanted!')