diff options
-rw-r--r-- | azure-pipelines.yml | 9 | ||||
-rwxr-xr-x | ci/ciimage/arch/install.sh | 2 | ||||
-rwxr-xr-x | ci/ciimage/bionic/install.sh | 3 | ||||
-rwxr-xr-x | ci/ciimage/eoan/install.sh | 3 | ||||
-rwxr-xr-x | ci/ciimage/fedora/install.sh | 2 | ||||
-rwxr-xr-x | ci/ciimage/opensuse/install.sh | 4 | ||||
-rw-r--r-- | docs/markdown/howtox.md | 3 | ||||
-rw-r--r-- | docs/markdown/snippets/clang_coverage.md | 4 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 10 | ||||
-rw-r--r-- | mesonbuild/environment.py | 11 | ||||
-rw-r--r-- | mesonbuild/scripts/coverage.py | 46 | ||||
-rwxr-xr-x | run_unittests.py | 83 |
12 files changed, 140 insertions, 40 deletions
diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0408342..3822110 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -91,6 +91,7 @@ jobs: libjsoncpp19,^ librhash0,^ libuv1,^ + libxml2,^ ninja,^ python2-devel,^ python3-devel,^ @@ -102,8 +103,8 @@ jobs: displayName: Install Dependencies - script: | set PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32 - env.exe -- python3 -m pip --disable-pip-version-check install pefile pytest-xdist jsonschema - displayName: pip install pefile pytest-xdist jsonschema + env.exe -- python3 -m pip --disable-pip-version-check install gcovr pefile pytest-xdist jsonschema + displayName: pip install gcovr pefile pytest-xdist jsonschema - script: | set BOOST_ROOT= set PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32 @@ -165,6 +166,8 @@ jobs: git ^ mercurial ^ mingw-w64-$(MSYS2_ARCH)-cmake ^ + mingw-w64-$(MSYS2_ARCH)-lcov ^ + mingw-w64-$(MSYS2_ARCH)-libxml2 ^ mingw-w64-$(MSYS2_ARCH)-ninja ^ mingw-w64-$(MSYS2_ARCH)-pkg-config ^ mingw-w64-$(MSYS2_ARCH)-python2 ^ @@ -172,7 +175,7 @@ jobs: mingw-w64-$(MSYS2_ARCH)-python3-setuptools ^ mingw-w64-$(MSYS2_ARCH)-python3-pip ^ %TOOLCHAIN% - %MSYS2_ROOT%\usr\bin\bash -lc "python3 -m pip --disable-pip-version-check install pefile jsonschema" + %MSYS2_ROOT%\usr\bin\bash -lc "python3 -m pip --disable-pip-version-check install gcovr jsonschema pefile" displayName: Install Dependencies - script: | set BOOST_ROOT= diff --git a/ci/ciimage/arch/install.sh b/ci/ciimage/arch/install.sh index 6cbbb27..fb27c26 100755 --- a/ci/ciimage/arch/install.sh +++ b/ci/ciimage/arch/install.sh @@ -17,7 +17,7 @@ pkgs=( ) aur_pkgs=(scalapack) -pip_pkgs=(hotdoc) +pip_pkgs=(hotdoc gcovr) cleanup_pkgs=(go) AUR_USER=docker diff --git a/ci/ciimage/bionic/install.sh b/ci/ciimage/bionic/install.sh index 47deb2a..0bfcdfb 100755 --- a/ci/ciimage/bionic/install.sh +++ b/ci/ciimage/bionic/install.sh @@ -15,6 +15,7 @@ pkgs=( qt4-linguist-tools qt5-default qtbase5-private-dev python-dev libomp-dev + llvm lcov ldc libclang-dev libgcrypt20-dev @@ -45,7 +46,7 @@ done # packages eatmydata apt-get -y install "${pkgs[@]}" -eatmydata python3 -m pip install codecov jsonschema +eatmydata python3 -m pip install codecov gcovr jsonschema # Install the ninja 0.10 wget https://github.com/ninja-build/ninja/releases/download/v1.10.0/ninja-linux.zip diff --git a/ci/ciimage/eoan/install.sh b/ci/ciimage/eoan/install.sh index 7d7a1fd..36dec72 100755 --- a/ci/ciimage/eoan/install.sh +++ b/ci/ciimage/eoan/install.sh @@ -18,6 +18,7 @@ pkgs=( qt4-linguist-tools python-dev libomp-dev + llvm lcov dub ldc mingw-w64 mingw-w64-tools nim libclang-dev @@ -42,7 +43,7 @@ eatmydata apt-get -y build-dep meson eatmydata apt-get -y install "${pkgs[@]}" eatmydata apt-get -y install --no-install-recommends wine-stable # Wine is special -eatmydata python3 -m pip install hotdoc codecov jsonschema +eatmydata python3 -m pip install hotdoc codecov gcovr jsonschema # dub stuff dub_fetch urld diff --git a/ci/ciimage/fedora/install.sh b/ci/ciimage/fedora/install.sh index f61d97e..3beb11c 100755 --- a/ci/ciimage/fedora/install.sh +++ b/ci/ciimage/fedora/install.sh @@ -21,7 +21,7 @@ dnf -y upgrade # Install deps dnf -y install "${pkgs[@]}" -python3 -m pip install hotdoc gobject PyGObject +python3 -m pip install hotdoc gcovr gobject PyGObject # Cleanup dnf -y clean all diff --git a/ci/ciimage/opensuse/install.sh b/ci/ciimage/opensuse/install.sh index b9e440d..4c8e770 100755 --- a/ci/ciimage/opensuse/install.sh +++ b/ci/ciimage/opensuse/install.sh @@ -7,7 +7,7 @@ source /ci/common.sh pkgs=( python3-setuptools python3-wheel python3-pip python3-pytest-xdist python3 python3-lxml ninja make git autoconf automake patch python3-Cython python3-jsonschema - elfutils gcc gcc-c++ gcc-fortran gcc-objc gcc-obj-c++ vala rust bison flex curl + elfutils gcc gcc-c++ gcc-fortran gcc-objc gcc-obj-c++ vala rust bison flex curl lcov mono-core gtkmm3-devel gtest gmock protobuf-devel wxGTK3-3_2-devel gobject-introspection-devel itstool gtk3-devel java-15-openjdk-devel gtk-doc llvm-devel clang-devel libSDL2-devel graphviz-devel zlib-devel zlib-devel-static #hdf5-devel netcdf-devel libscalapack2-openmpi3-devel libscalapack2-gnu-openmpi3-hpc-devel openmpi3-devel @@ -26,7 +26,7 @@ zypper --non-interactive update # Install deps zypper install -y "${pkgs[@]}" -python3 -m pip install hotdoc gobject PyGObject +python3 -m pip install hotdoc gcovr gobject PyGObject echo 'export PKG_CONFIG_PATH="/usr/lib64/mpi/gcc/openmpi3/lib64/pkgconfig:$PKG_CONFIG_PATH"' >> /ci/env_vars.sh diff --git a/docs/markdown/howtox.md b/docs/markdown/howtox.md index 84546b7..0832060 100644 --- a/docs/markdown/howtox.md +++ b/docs/markdown/howtox.md @@ -155,8 +155,7 @@ $ ninja coverage-html (or coverage-xml) The coverage report can be found in the meson-logs subdirectory. -Note: Currently, Meson does not support generating coverage reports -with Clang. +*New in 0.55.0* llvm-cov support for use with clang ## Add some optimization to debug builds diff --git a/docs/markdown/snippets/clang_coverage.md b/docs/markdown/snippets/clang_coverage.md new file mode 100644 index 0000000..733a3d9 --- /dev/null +++ b/docs/markdown/snippets/clang_coverage.md @@ -0,0 +1,4 @@ +## Clang coverage support + +llvm-cov is now used to generate coverage information when clang is used as +the compiler.
\ No newline at end of file diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 8dbb57a..252f646 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -968,6 +968,13 @@ int dummy; self.processed_targets[target.get_id()] = True def generate_coverage_command(self, elem, outputs): + targets = self.build.get_targets().values() + use_llvm_cov = False + for target in targets: + for compiler in target.compilers.values(): + if compiler.get_id() == 'clang' and not compiler.info.is_darwin(): + use_llvm_cov = True + break elem.add_item('COMMAND', self.environment.get_build_command() + ['--internal', 'coverage'] + outputs + @@ -975,7 +982,8 @@ int dummy; os.path.join(self.environment.get_source_dir(), self.build.get_subproject_dir()), self.environment.get_build_dir(), - self.environment.get_log_dir()]) + self.environment.get_log_dir()] + + ['--use_llvm_cov'] if use_llvm_cov else []) def generate_coverage_rules(self): e = NinjaBuildElement(self.all_outputs, 'meson-coverage', 'CUSTOM_COMMAND', 'PHONY') diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index c02376e..afc2a63 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -134,9 +134,18 @@ def detect_gcovr(min_version='3.3', new_rootdir_version='4.2', log=False): return gcovr_exe, mesonlib.version_compare(found, '>=' + new_rootdir_version) return None, None +def detect_llvm_cov(): + tools = get_llvm_tool_names('llvm-cov') + for tool in tools: + if mesonlib.exe_exists([tool, '--version']): + return tool + return None + def find_coverage_tools(): gcovr_exe, gcovr_new_rootdir = detect_gcovr() + llvm_cov_exe = detect_llvm_cov() + lcov_exe = 'lcov' genhtml_exe = 'genhtml' @@ -145,7 +154,7 @@ def find_coverage_tools(): if not mesonlib.exe_exists([genhtml_exe, '--version']): genhtml_exe = None - return gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe + return gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe, llvm_cov_exe def detect_ninja(version: str = '1.7', log: bool = False) -> str: r = detect_ninja_command_and_version(version, log) diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py index 4bd41fe..7231972 100644 --- a/mesonbuild/scripts/coverage.py +++ b/mesonbuild/scripts/coverage.py @@ -12,15 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from mesonbuild import environment +from mesonbuild import environment, mesonlib -import argparse, sys, os, subprocess, pathlib +import argparse, sys, os, subprocess, pathlib, stat -def coverage(outputs, source_root, subproject_root, build_root, log_dir): +def coverage(outputs, source_root, subproject_root, build_root, log_dir, use_llvm_cov): outfiles = [] exitcode = 0 - (gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe) = environment.find_coverage_tools() + (gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe, llvm_cov_exe) = environment.find_coverage_tools() # gcovr >= 4.2 requires a different syntax for out of source builds if gcovr_new_rootdir: @@ -28,13 +28,18 @@ def coverage(outputs, source_root, subproject_root, build_root, log_dir): 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: subprocess.check_call(gcovr_base_cmd + ['-x', '-e', subproject_root, - '-o', os.path.join(log_dir, 'coverage.xml'), - ]) + '-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') @@ -44,8 +49,8 @@ def coverage(outputs, source_root, subproject_root, build_root, log_dir): if gcovr_exe: subprocess.check_call(gcovr_base_cmd + ['-e', subproject_root, - '-o', os.path.join(log_dir, 'coverage.txt'), - ]) + '-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') @@ -58,19 +63,34 @@ def coverage(outputs, source_root, subproject_root, build_root, log_dir): initial_tracefile = covinfo + '.initial' run_tracefile = covinfo + '.run' raw_tracefile = covinfo + '.raw' + 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') as llvm_cov_bat: + llvm_cov_bat.write('@"{}" gcov %*'.format(llvm_cov_exe)) + else: + llvm_cov_shim_path = os.path.join(log_dir, 'llvm-cov.sh') + with open(llvm_cov_shim_path, 'w') as llvm_cov_sh: + llvm_cov_sh.write('#!/usr/bin/env sh\nexec "{}" gcov $@'.format(llvm_cov_exe)) + 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]) + initial_tracefile] + + gcov_tool_args) subprocess.check_call([lcov_exe, '--directory', build_root, '--capture', '--output-file', run_tracefile, '--no-checksum', - '--rc', 'lcov_branch_coverage=1', - ]) + '--rc', 'lcov_branch_coverage=1'] + + gcov_tool_args) # Join initial and test results. subprocess.check_call([lcov_exe, '-a', initial_tracefile, @@ -137,6 +157,8 @@ def run(args): const='xml', help='generate 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('source_root') parser.add_argument('subproject_root') parser.add_argument('build_root') @@ -144,7 +166,7 @@ def run(args): options = parser.parse_args(args) return coverage(options.outputs, options.source_root, options.subproject_root, options.build_root, - options.log_dir) + options.log_dir, options.use_llvm_cov) if __name__ == '__main__': sys.exit(run(sys.argv[1:])) diff --git a/run_unittests.py b/run_unittests.py index 827e3c8..0a61935 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -4829,6 +4829,74 @@ recommended as it is not supported on some platforms''') self.assertEqual(parsed_help['usage'], parsed_section['usage']) self.assertEqual(parsed_help['arguments'], parsed_section['arguments']) + def test_coverage(self): + gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + if not gcovr_exe: + raise unittest.SkipTest('gcovr not found, or too old') + testdir = os.path.join(self.common_test_dir, '1 trivial') + env = get_fake_env(testdir, self.builddir, self.prefix) + cc = env.detect_c_compiler(MachineChoice.HOST) + if cc.get_id() == 'clang': + if not mesonbuild.environment.detect_llvm_cov(): + raise unittest.SkipTest('llvm-cov not found') + if cc.get_id() == 'msvc': + raise unittest.SkipTest('Test only applies to non-MSVC compilers') + self.init(testdir, extra_args=['-Db_coverage=true']) + self.build() + self.run_tests() + self.run_target('coverage') + + def test_coverage_html(self): + gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + if not gcovr_exe: + raise unittest.SkipTest('gcovr not found, or too old') + testdir = os.path.join(self.common_test_dir, '1 trivial') + env = get_fake_env(testdir, self.builddir, self.prefix) + cc = env.detect_c_compiler(MachineChoice.HOST) + if cc.get_id() == 'clang': + if not mesonbuild.environment.detect_llvm_cov(): + raise unittest.SkipTest('llvm-cov not found') + if cc.get_id() == 'msvc': + raise unittest.SkipTest('Test only applies to non-MSVC compilers') + self.init(testdir, extra_args=['-Db_coverage=true']) + self.build() + self.run_tests() + self.run_target('coverage-html') + + def test_coverage_text(self): + gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + if not gcovr_exe: + raise unittest.SkipTest('gcovr not found, or too old') + testdir = os.path.join(self.common_test_dir, '1 trivial') + env = get_fake_env(testdir, self.builddir, self.prefix) + cc = env.detect_c_compiler(MachineChoice.HOST) + if cc.get_id() == 'clang': + if not mesonbuild.environment.detect_llvm_cov(): + raise unittest.SkipTest('llvm-cov not found') + if cc.get_id() == 'msvc': + raise unittest.SkipTest('Test only applies to non-MSVC compilers') + self.init(testdir, extra_args=['-Db_coverage=true']) + self.build() + self.run_tests() + self.run_target('coverage-text') + + def test_coverage_xml(self): + gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + if not gcovr_exe: + raise unittest.SkipTest('gcovr not found, or too old') + testdir = os.path.join(self.common_test_dir, '1 trivial') + env = get_fake_env(testdir, self.builddir, self.prefix) + cc = env.detect_c_compiler(MachineChoice.HOST) + if cc.get_id() == 'clang': + if not mesonbuild.environment.detect_llvm_cov(): + raise unittest.SkipTest('llvm-cov not found') + if cc.get_id() == 'msvc': + raise unittest.SkipTest('Test only applies to non-MSVC compilers') + self.init(testdir, extra_args=['-Db_coverage=true']) + self.build() + self.run_tests() + self.run_target('coverage-xml') + class FailureTests(BasePlatformTests): ''' Tests that test failure conditions. Build files here should be dynamically @@ -6301,21 +6369,6 @@ class LinuxlikeTests(BasePlatformTests): for i in compdb: self.assertIn("-fsanitize=address", i["command"]) - def test_coverage(self): - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() - if not gcovr_exe: - raise unittest.SkipTest('gcovr 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!') - testdir = os.path.join(self.common_test_dir, '1 trivial') - self.init(testdir, extra_args=['-Db_coverage=true']) - self.build() - self.run_tests() - self.run_target('coverage-html') - def test_cross_find_program(self): testdir = os.path.join(self.unit_test_dir, '11 cross prog') crossfile = tempfile.NamedTemporaryFile(mode='w') |