Repository: mesonbuild/meson

Issue:
Future version of gcovr requires updated syntax
In testing the gcovr provided solution that was discussed in #4801 I discovered that the syntax for calling gcovr has changed slightly since the last release. I can see that this has happened before since there's handling for different root directory expectations already in coverage.py. 

Now there's yet another syntax, which is further different from the two already supported. Basically, in order to be compatible with the next version of gcovr we now need to call gcovr like this (note that both the build root and the source root are being passed):

`gcovr_base_cmd = [gcovr_exe, '-r', source_root, build_root]`

This complicates things as there will be three different ways of handling directories passed to gcovr based on the installed version, with the new version syntax being more of a departure compared to the currently supported versions.

Since I've already figured out how the command needs to be updated I would be happy to submit a PR to update the necessary parts once I've figured out a nice way of doing it with via detect_gcovr(). I'm creating this issue though to ensure that people are aware that this will be a problem once the new version of gcovr is released. I don't know exactly when that will happen, but anyone interested can simply follow [this ticket](https://github.com/gcovr/gcovr/issues/297).


Discussion:

Resolving patch:
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -83,7 +83,7 @@
 
 build_filename = 'meson.build'
 
-def detect_gcovr(version='3.1', log=False):
+def detect_gcovr(min_version='3.3', new_rootdir_version='4.2', log=False):
     gcovr_exe = 'gcovr'
     try:
         p, found = Popen_safe([gcovr_exe, '--version'])[0:2]
@@ -91,10 +91,10 @@ def detect_gcovr(version='3.1', log=False):
         # Doesn't exist in PATH or isn't executable
         return None, None
     found = search_version(found)
-    if p.returncode == 0:
+    if p.returncode == 0 and mesonlib.version_compare(found, '>=' + min_version):
         if log:
             mlog.log('Found gcovr-{} at {}'.format(found, shlex.quote(shutil.which(gcovr_exe))))
-        return gcovr_exe, mesonlib.version_compare(found, '>=' + version)
+        return gcovr_exe, mesonlib.version_compare(found, '>=' + new_rootdir_version)
     return None, None
 
 def find_coverage_tools():
diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py
--- a/mesonbuild/scripts/coverage.py
+++ b/mesonbuild/scripts/coverage.py
@@ -22,35 +22,33 @@ def coverage(outputs, source_root, subproject_root, build_root, log_dir):
 
     (gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe) = environment.find_coverage_tools()
 
-    # gcovr >= 3.1 interprets rootdir differently
+    # gcovr >= 4.2 requires a different syntax for out of source builds
     if gcovr_new_rootdir:
-        gcovr_rootdir = build_root
+        gcovr_base_cmd = [gcovr_exe, '-r', source_root, build_root]
     else:
-        gcovr_rootdir = source_root
+        gcovr_base_cmd = [gcovr_exe, '-r', build_root]
 
     if not outputs or 'xml' in outputs:
         if gcovr_exe:
-            subprocess.check_call([gcovr_exe,
-                                   '-x',
-                                   '-r', gcovr_rootdir,
+            subprocess.check_call(gcovr_base_cmd +
+                                  ['-x',
                                    '-e', subproject_root,
                                    '-o', os.path.join(log_dir, 'coverage.xml'),
                                    ])
             outfiles.append(('Xml', pathlib.Path(log_dir, 'coverage.xml')))
         elif outputs:
-            print('gcovr needed to generate Xml coverage report')
+            print('gcovr >= 3.3 needed to generate Xml coverage report')
             exitcode = 1
 
     if not outputs or 'text' in outputs:
         if gcovr_exe:
-            subprocess.check_call([gcovr_exe,
-                                   '-r', gcovr_rootdir,
-                                   '-e', subproject_root,
+            subprocess.check_call(gcovr_base_cmd +
+                                  ['-e', subproject_root,
                                    '-o', os.path.join(log_dir, 'coverage.txt'),
                                    ])
             outfiles.append(('Text', pathlib.Path(log_dir, 'coverage.txt')))
         elif outputs:
-            print('gcovr needed to generate text coverage report')
+            print('gcovr >= 3.3 needed to generate text coverage report')
             exitcode = 1
 
     if not outputs or 'html' in outputs:
@@ -101,21 +99,20 @@ def coverage(outputs, source_root, subproject_root, build_root, log_dir):
                                    '--branch-coverage',
                                    covinfo])
             outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html')))
-        elif gcovr_exe and gcovr_new_rootdir:
+        elif gcovr_exe:
             htmloutdir = os.path.join(log_dir, 'coveragereport')
             if not os.path.isdir(htmloutdir):
                 os.mkdir(htmloutdir)
-            subprocess.check_call([gcovr_exe,
-                                   '--html',
+            subprocess.check_call(gcovr_base_cmd +
+                                  ['--html',
                                    '--html-details',
                                    '--print-summary',
-                                   '-r', build_root,
                                    '-e', subproject_root,
                                    '-o', os.path.join(htmloutdir, 'index.html'),
                                    ])
             outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html')))
         elif outputs:
-            print('lcov/genhtml or gcovr >= 3.2 needed to generate Html coverage report')
+            print('lcov/genhtml or gcovr >= 3.3 needed to generate Html coverage report')
             exitcode = 1
 
     if not outputs and not outfiles:


Test patch: