Repository: mesonbuild/meson

Issue:
include_directories check for in-tree absolute paths gives false positives
**Describe the bug**
If the source directory is /bar/foo, and `include_directories` is called with an absolute path like `/bar/football/include`, then Meson incorrect reports `ERROR: Tried to form an absolute path to a dir in the source tree.`

**To Reproduce**
```meson
project('foo')
incdir = meson.current_source_dir() + 'xyz/include'
message(incdir)
include_directories(incdir)
```
(meson.current_source_dir is used just for this example; when I actually ran into this it was due to a Python virtual environment created in a sibling directory of the root).

**Expected behavior**
Meson should use functions appropriate for checking nesting of paths, rather than a [string test](https://github.com/mesonbuild/meson/blob/fe9af72684f85d709ce45096534aa51854a6da9b/mesonbuild/interpreter/interpreter.py#L2787).

Incidentally, it looks like there is a similar bug [here](https://github.com/mesonbuild/meson/blob/fe9af72684f85d709ce45096534aa51854a6da9b/mesonbuild/interpreter/interpreter.py#L556).

**system parameters**
- Not a cross build
- Ubuntu 22.04
- Python 3.10.12
- Meson 1.2.1


Discussion:

Resolving patch:
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -25,7 +25,7 @@
 from .. import mesonlib
 from ..mesonlib import (EnvironmentVariables, ExecutableSerialisation, MesonBugException, MesonException, HoldableObject,
                         FileMode, MachineChoice, OptionKey, listify,
-                        extract_as_list, has_path_sep, PerMachine)
+                        extract_as_list, has_path_sep, path_is_in_root, PerMachine)
 from ..programs import ExternalProgram, NonExistingExternalProgram
 from ..dependencies import Dependency
 from ..depfile import DepFile
@@ -553,7 +553,7 @@ def add_build_def_file(self, f: mesonlib.FileOrString) -> None:
             if f.is_built:
                 return
             f = os.path.normpath(f.relative_name())
-        elif os.path.isfile(f) and not f.startswith('/dev'):
+        elif os.path.isfile(f) and not f.startswith('/dev/'):
             srcdir = Path(self.environment.get_source_dir())
             builddir = Path(self.environment.get_build_dir())
             try:
@@ -2784,7 +2784,7 @@ def build_incdir_object(self, incdir_strings: T.List[str], is_system: bool = Fal
         absbase_build = os.path.join(build_root, self.subdir)
 
         for a in incdir_strings:
-            if a.startswith(src_root):
+            if path_is_in_root(Path(a), Path(src_root)):
                 raise InvalidArguments(textwrap.dedent('''\
                     Tried to form an absolute path to a dir in the source tree.
                     You should not do that but use relative paths instead, for


Test patch:
diff --git a/run_project_tests.py b/run_project_tests.py
--- a/run_project_tests.py
+++ b/run_project_tests.py
@@ -942,6 +942,8 @@ def gather_tests(testdir: Path, stdout_mandatory: bool, only: T.List[str], skip_
         # Filter non-tests files (dot files, etc)
         if not t.is_dir() or t.name.startswith('.'):
             continue
+        if t.name in {'18 includedirxyz'}:
+            continue
         if only and not any(t.name.startswith(prefix) for prefix in only):
             continue
         test_def = TestDef(t, None, [], skip_category=skip_category)
diff --git a/test cases/common/18 includedir/meson.build b/test cases/common/18 includedir/meson.build
--- a/test cases/common/18 includedir/meson.build	
+++ b/test cases/common/18 includedir/meson.build	
@@ -2,3 +2,31 @@ project('include dir test', 'c')
 
 inc = include_directories('include')
 subdir('src')
+
+errormsg = '''Tried to form an absolute path to a dir in the source tree.
+You should not do that but use relative paths instead, for
+directories that are part of your project.
+
+To get include path to any directory relative to the current dir do
+
+incdir = include_directories(dirname)
+
+After this incdir will contain both the current source dir as well as the
+corresponding build dir. It can then be used in any subdirectory and
+Meson will take care of all the busywork to make paths work.
+
+Dirname can even be '.' to mark the current directory. Though you should
+remember that the current source and build directories are always
+put in the include directories by default so you only need to do
+include_directories('.') if you intend to use the result in a
+different subdirectory.
+
+Note that this error message can also be triggered by
+external dependencies being installed within your source
+tree - it's not recommended to do this.
+'''
+testcase expect_error(errormsg)
+  include_directories(meson.current_source_dir() / 'include')
+endtestcase
+# Test for issue #12217
+include_directories(meson.current_source_dir() + 'xyz')
diff --git a/test cases/common/18 includedirxyz/do_not_delete b/test cases/common/18 includedirxyz/do_not_delete
new file mode 100644
--- /dev/null
+++ b/test cases/common/18 includedirxyz/do_not_delete	
@@ -0,0 +1 @@
+This file is to ensure this directory exists