Use include_directories for D impdirs.

Change the code to store D properties as plain data. Only convert them
to compiler flags in the backend. This also means we can fully parse D
arguments without needing to know the compiler being used.
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 8b616a6..8577bc7 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -2257,6 +2257,9 @@
                     depelem.write(outfile)
             commands += compiler.get_module_outdir_args(self.get_target_private_dir(target))
 
+        if compiler.language == 'd':
+            commands += compiler.get_feature_args(target.d_features, self.build_to_src)
+
         element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src)
         for d in header_deps:
             if isinstance(d, File):
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 400b9e5..c5fc7f6 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -355,6 +355,7 @@
         self.extra_args = {}
         self.generated = []
         self.extra_files = []
+        self.d_features = {}
         # Sources can be:
         # 1. Pre-existing source files in the source tree
         # 2. Pre-existing sources generated by configure_file in the build tree
@@ -682,12 +683,15 @@
         dfeature_versions = kwargs.get('d_module_versions', None)
         if dfeature_versions:
             dfeatures['versions'] = dfeature_versions
-        dfeature_import_dirs = kwargs.get('d_import_dirs', None)
-        if dfeature_import_dirs:
+        if 'd_import_dirs' in kwargs:
+            dfeature_import_dirs = extract_as_list(kwargs, 'd_import_dirs', unholder=True)
+            for d in dfeature_import_dirs:
+                if not isinstance(d, IncludeDirs):
+                    raise InvalidArguments('Arguments to d_import_dirs must be include_directories.')
             dfeatures['import_dirs'] = dfeature_import_dirs
         if dfeatures:
             if 'd' in self.compilers:
-                self.add_compiler_args('d', self.compilers['d'].get_feature_args(dfeatures))
+                self.d_features = dfeatures
 
         self.link_args = extract_as_list(kwargs, 'link_args')
         for i in self.link_args:
diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py
index 3320736..aeb6197 100644
--- a/mesonbuild/compilers/d.py
+++ b/mesonbuild/compilers/d.py
@@ -93,7 +93,7 @@
         # FIXME: Make this work for Windows, MacOS and cross-compiling
         return get_gcc_soname_args(GCC_STANDARD, prefix, shlib_name, suffix, path, soversion, is_shared_module)
 
-    def get_feature_args(self, kwargs):
+    def get_feature_args(self, kwargs, build_to_src):
         res = []
         if 'unittest' in kwargs:
             unittest = kwargs.pop('unittest')
@@ -122,8 +122,16 @@
             import_dir_arg = d_feature_args[self.id]['import_dir']
             if not import_dir_arg:
                 raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string())
-            for d in import_dirs:
-                res.append('{0}{1}'.format(import_dir_arg, d))
+            for idir_obj in import_dirs:
+                basedir = idir_obj.get_curdir()
+                for idir in idir_obj.get_incdirs():
+                     # Avoid superfluous '/.' at the end of paths when d is '.'
+                    if idir not in ('', '.'):
+                        expdir = os.path.join(basedir, idir)
+                    else:
+                        expdir = basedir
+                    srctreedir = os.path.join(build_to_src, expdir)
+                    res.append('{0}{1}'.format(import_dir_arg, srctreedir))
 
         if kwargs:
             raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys()))
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 8041526..5c333a8 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -37,6 +37,7 @@
 
 import importlib
 
+
 def stringifyUserArguments(args):
     if isinstance(args, list):
         return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args])
@@ -247,7 +248,7 @@
         return val
 
     def get(self, name):
-        return self.held_object.values[name]     # (val, desc)
+        return self.held_object.values[name] # (val, desc)
 
     def keys(self):
         return self.held_object.values.keys()
@@ -816,7 +817,8 @@
         '''
         if not hasattr(self.compiler, 'get_feature_args'):
             raise InterpreterException('This {} compiler has no feature arguments.'.format(self.compiler.get_display_language()))
-        return self.compiler.get_feature_args({'unittest': 'true'})
+        build_to_src = os.path.relpath(self.environment.get_source_dir(), self.environment.get_build_dir())
+        return self.compiler.get_feature_args({'unittest': 'true'}, build_to_src)
 
     def has_member_method(self, args, kwargs):
         if len(args) != 2:
@@ -1309,6 +1311,7 @@
                 return args[1]
             raise InterpreterException('Unknown cross property: %s.' % propname)
 
+
 pch_kwargs = set(['c_pch', 'cpp_pch'])
 
 lang_arg_kwargs = set([
@@ -2847,12 +2850,17 @@
     @permittedKwargs(permitted_kwargs['include_directories'])
     @stringArgs
     def func_include_directories(self, node, args, kwargs):
+        return self.build_incdir_object(args, kwargs.get('is_system', False))
+
+    def build_incdir_object(self, incdir_strings, is_system=False):
+        if not isinstance(is_system, bool):
+            raise InvalidArguments('Is_system must be boolean.')
         src_root = self.environment.get_source_dir()
         build_root = self.environment.get_build_dir()
         absbase_src = os.path.join(src_root, self.subdir)
         absbase_build = os.path.join(build_root, self.subdir)
 
-        for a in args:
+        for a in incdir_strings:
             if a.startswith(src_root):
                 raise InvalidArguments('''Tried to form an absolute path to a source dir. You should not do that but use
 relative paths instead.
@@ -2875,10 +2883,7 @@
             absdir_build = os.path.join(absbase_build, a)
             if not os.path.isdir(absdir_src) and not os.path.isdir(absdir_build):
                 raise InvalidArguments('Include dir %s does not exist.' % a)
-        is_system = kwargs.get('is_system', False)
-        if not isinstance(is_system, bool):
-            raise InvalidArguments('Is_system must be boolean.')
-        i = IncludeDirsHolder(build.IncludeDirs(self.subdir, args, is_system))
+        i = IncludeDirsHolder(build.IncludeDirs(self.subdir, incdir_strings, is_system))
         return i
 
     @permittedKwargs(permitted_kwargs['add_test_setup'])
@@ -3106,6 +3111,7 @@
         else:
             mlog.debug('Unknown target type:', str(targetholder))
             raise RuntimeError('Unreachable code')
+        self.kwarg_strings_to_includedirs(kwargs)
         target = targetclass(name, self.subdir, self.subproject, is_cross, sources, objs, self.environment, kwargs)
         if is_cross:
             self.add_cross_stdlib_info(target)
@@ -3114,6 +3120,23 @@
         self.project_args_frozen = True
         return l
 
+    def kwarg_strings_to_includedirs(self, kwargs):
+        if 'd_import_dirs' in kwargs:
+            items = mesonlib.extract_as_list(kwargs, 'd_import_dirs')
+            cleaned_items = []
+            for i in items:
+                if isinstance(i, str):
+                    # BW compatibility. This was permitted so we must support it
+                    # for a few releases so people can transition to "correct"
+                    # path declarations.
+                    if i.startswith(self.environment.get_source_dir()):
+                        mlog.warning('''Building a path to the source dir is not supported. Use a relative path instead.
+This will become a hard error in the future.''')
+                        i = os.path.relpath(i, os.path.join(self.environment.get_source_dir(), self.subdir))
+                        i = self.build_incdir_object([i])
+                cleaned_items.append(i)
+            kwargs['d_import_dirs'] = cleaned_items
+
     def get_used_languages(self, target):
         result = {}
         for i in target.sources:
@@ -3152,6 +3175,7 @@
             if idx >= len(arg_strings):
                 raise InterpreterException('Format placeholder @{}@ out of range.'.format(idx))
             return arg_strings[idx]
+
         return re.sub(r'@(\d+)@', arg_replace, templ)
 
     # Only permit object extraction from the same subproject
diff --git a/test cases/d/9 features/meson.build b/test cases/d/9 features/meson.build
index 9e63710..c714f22 100644
--- a/test cases/d/9 features/meson.build
+++ b/test cases/d/9 features/meson.build
@@ -1,8 +1,22 @@
 project('D Features', 'd')
 
-# directory for data
+# ONLY FOR BACKWARDS COMPATIBILITY.
+# DO NOT DO THIS ON NEW CODE.
+# USE include_directories() INSTEAD OF BUILDING
+# STRINGS TO PATHS MANUALLY!
 data_dir = join_paths(meson.current_source_dir(), 'data')
 
+e_plain_bcompat = executable('dapp_menu_bcompat',
+    'app.d',
+    d_import_dirs: [data_dir]
+)
+test('dapp_menu_t_fail_bcompat', e_plain_bcompat, should_fail: true)
+test('dapp_menu_t_bcompat', e_plain_bcompat, args: ['menu'])
+
+# directory for data
+# This is the correct way to do this.
+data_dir = include_directories('data')
+
 e_plain = executable('dapp_menu',
     'app.d',
     d_import_dirs: [data_dir]
@@ -10,6 +24,7 @@
 test('dapp_menu_t_fail', e_plain, should_fail: true)
 test('dapp_menu_t', e_plain, args: ['menu'])
 
+
 # test feature versions and string imports
 e_versions = executable('dapp_versions',
     'app.d',