Repository: mesonbuild/meson

Issue:
ninja: build stopped: 'some/path.o' not mentioned in its dyndep file 'some/path/depscan.dd'
**Describe the bug**
The follow error messages appears in my project.
```
ninja: build stopped: 'libscotchDecomp.so.p/plusplus.C.o' not mentioned in its dyndep file 'libscotchDecomp.so.p/depscan.dd'
```
Weirdly, it dissappears if I replace `project('SCOTCH', ['c', 'fortran'])` with `project('SCOTCH', ['c'])`, even though Fortran is never used in the example.

It would be nice if you could either fix the bug, or point me in the right direction to fix it.

Interestingly, this bug only appears if the C++ file ends with .C, not with .cpp. Feel free to say "told you so", as I'm the guy who wrote https://github.com/mesonbuild/meson/pull/8747.

**To Reproduce**
[withFortran.zip](https://github.com/mesonbuild/meson/files/9184433/withFortran.zip)


```
$ cd withFortran
$ meson setup build
$ ninja -C build/ libscotchDecomp.so.p/plusplus.C.o
ninja: Entering directory `build/'
[1/2] Module scanner.
ninja: build stopped: 'libscotchDecomp.so.p/plusplus.C.o' not mentioned in its dyndep file 'libscotchDecomp.so.p/depscan.dd'.
```

**Expected behavior**
`ninja -C build/ libscotchDecomp.so.p/plusplus.C.o` should run without errors, `ninja -C build` should error out.

**system parameters**
* Native Build
* Arch Linux
* Python 3.10.5
* meson 0.63.0
* ninja 1.11.0



Discussion:
There are two weird things. First, fortran uses the depscanner (dyndeps because modules), but no actual fortran code is being built or linked here. So why is the presence of it in languages causing dyndeps to be triggered on a C++ file? Moreover, why only... if there's a generated header in sources? Because .h is "can_compile" for gfortran.

Second, this only happens for C++ files named as .C, not ones which are named with better extensions such as .cc or .cpp;  this happens because we compare suffixes case insensitively and don't realize that `plusplus.C` is one of the files we tried to scan, here, so it's not listed in the json file.

If I fix that in the ninjabackend, then depscan errors out with `Can not scan files with suffix .c.`

After removing two calls to `.lower()`, this works for me, albeit wastefully running a fortran module scanner on a C++ file that doesn't use C++20 modules.
> Interestingly, this bug only appears if the C++ file ends with .C, not with .cpp. Feel free to say "told you so", as I'm the guy who wrote #8747.

Ah yes, nice edit, we discovered the same thing. I didn't want to say anything, but, you're quite correct there...
> There are two weird things. First, fortran uses the depscanner (dyndeps because modules), but no actual fortran code is being built or linked here. So why is the presence of it in languages causing dyndeps to be triggered on a C++ file? Moreover, why only... if there's a generated header in sources? Because .h is "can_compile" for gfortran.
> 
> Second, this only happens for C++ files named as .C, not ones which are named with better extensions such as .cc or .cpp; this happens because we compare suffixes case insensitively and don't realize that `plusplus.C` is one of the files we tried to scan, here, so it's not listed in the json file.
> 
> If I fix that in the ninjabackend, then depscan errors out with `Can not scan files with suffix .c.`
> 
> After removing two calls to `.lower()`, this works for me, albeit wastefully running a fortran module scanner on a C++ file that doesn't use C++20 modules.

You're a wizard. Figuring this out would take me thirty hours, not thirty minutes.

Resolving patch:
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -975,7 +975,9 @@ def select_sources_to_scan(self, compiled_sources):
         all_suffixes = set(compilers.lang_suffixes['cpp']) | set(compilers.lang_suffixes['fortran'])
         selected_sources = []
         for source in compiled_sources:
-            ext = os.path.splitext(source)[1][1:].lower()
+            ext = os.path.splitext(source)[1][1:]
+            if ext != 'C':
+                ext = ext.lower()
             if ext in all_suffixes:
                 selected_sources.append(source)
         return selected_sources
@@ -2706,7 +2708,9 @@ def add_dependency_scanner_entries_to_element(self, target, compiler, element, s
         if not self.should_use_dyndeps_for_target(target):
             return
         extension = os.path.splitext(src.fname)[1][1:]
-        if not (extension.lower() in compilers.lang_suffixes['fortran'] or extension in compilers.lang_suffixes['cpp']):
+        if extension != 'C':
+            extension = extension.lower()
+        if not (extension in compilers.lang_suffixes['fortran'] or extension in compilers.lang_suffixes['cpp']):
             return
         dep_scan_file = self.get_dep_scan_file_for(target)
         element.add_item('dyndep', dep_scan_file)
diff --git a/mesonbuild/scripts/depscan.py b/mesonbuild/scripts/depscan.py
--- a/mesonbuild/scripts/depscan.py
+++ b/mesonbuild/scripts/depscan.py
@@ -51,7 +51,9 @@ def __init__(self, pickle_file: str, outfile: str, sources: T.List[str]):
         self.sources_with_exports: T.List[str] = []
 
     def scan_file(self, fname: str) -> None:
-        suffix = os.path.splitext(fname)[1][1:].lower()
+        suffix = os.path.splitext(fname)[1][1:]
+        if suffix != 'C':
+            suffix = suffix.lower()
         if suffix in lang_suffixes['fortran']:
             self.scan_fortran_file(fname)
         elif suffix in lang_suffixes['cpp']:


Test patch: