Repository: mesonbuild/meson

Issue:
No support for conditional debug compilation in D
Similar to D's [version identifiers](http://mesonbuild.com/D.html#compiling-different-versions), D also supports conditional compilation with debug flags, with various levels of 'debugness' and debug identifiers. For example with DMD compiler, debug builds should always have the `-debug` flag (not to be confused with `-g`) passed on compilers, which compiles the code inside `debug { }` blocks. The level of 'debugness' can be defined with `-debug=3`, where all the debug levels from 1 to 3 are compiled, and on top of that, extra debug identifiers can also passed with `-debug=DebugFeature`, which similarly compiles the code inside `debug (DebugFeature) { }` block, just like with version identifiers.

I can start working on this after the D fixes for Windows are merged in, if nobody has any objections against it.


Discussion:

Resolving patch:
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -37,6 +37,7 @@
     'd_import_dirs',
     'd_unittest',
     'd_module_versions',
+    'd_debug',
     'fortran_args',
     'java_args',
     'objc_args',
@@ -737,9 +738,12 @@ def process_kwargs(self, kwargs, environment):
         dfeature_unittest = kwargs.get('d_unittest', False)
         if dfeature_unittest:
             dfeatures['unittest'] = dfeature_unittest
-        dfeature_versions = kwargs.get('d_module_versions', None)
+        dfeature_versions = kwargs.get('d_module_versions', [])
         if dfeature_versions:
             dfeatures['versions'] = dfeature_versions
+        dfeature_debug = kwargs.get('d_debug', [])
+        if dfeature_debug:
+            dfeatures['debug'] = dfeature_debug
         if 'd_import_dirs' in kwargs:
             dfeature_import_dirs = extract_as_list(kwargs, 'd_import_dirs', unholder=True)
             for d in dfeature_import_dirs:
diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py
--- a/mesonbuild/compilers/d.py
+++ b/mesonbuild/compilers/d.py
@@ -30,14 +30,17 @@
 )
 
 d_feature_args = {'gcc':  {'unittest': '-funittest',
+                           'debug': '-fdebug',
                            'version': '-fversion',
                            'import_dir': '-J'
                            },
                   'llvm': {'unittest': '-unittest',
+                           'debug': '-d-debug',
                            'version': '-d-version',
                            'import_dir': '-J'
                            },
                   'dmd':  {'unittest': '-unittest',
+                           'debug': '-debug',
                            'version': '-version',
                            'import_dir': '-J'
                            }
@@ -168,16 +171,53 @@ def get_feature_args(self, kwargs, build_to_src):
             if unittest:
                 res.append(unittest_arg)
 
+        if 'debug' in kwargs:
+            debug_level = -1
+            debugs = kwargs.pop('debug')
+            if not isinstance(debugs, list):
+                debugs = [debugs]
+
+            debug_arg = d_feature_args[self.id]['debug']
+            if not debug_arg:
+                raise EnvironmentException('D compiler %s does not support conditional debug identifiers.' % self.name_string())
+
+            # Parse all debug identifiers and the largest debug level identifier
+            for d in debugs:
+                if isinstance(d, int):
+                    if d > debug_level:
+                        debug_level = d
+                elif isinstance(d, str) and d.isdigit():
+                    if int(d) > debug_level:
+                        debug_level = int(d)
+                else:
+                    res.append('{0}={1}'.format(debug_arg, d))
+
+            if debug_level >= 0:
+                res.append('{0}={1}'.format(debug_arg, debug_level))
+
         if 'versions' in kwargs:
+            version_level = -1
             versions = kwargs.pop('versions')
             if not isinstance(versions, list):
                 versions = [versions]
 
             version_arg = d_feature_args[self.id]['version']
             if not version_arg:
-                raise EnvironmentException('D compiler %s does not support the "feature versions" feature.' % self.name_string())
+                raise EnvironmentException('D compiler %s does not support conditional version identifiers.' % self.name_string())
+
+            # Parse all version identifiers and the largest version level identifier
             for v in versions:
-                res.append('{0}={1}'.format(version_arg, v))
+                if isinstance(v, int):
+                    if v > version_level:
+                        version_level = v
+                elif isinstance(v, str) and v.isdigit():
+                    if int(v) > version_level:
+                        version_level = int(v)
+                else:
+                    res.append('{0}={1}'.format(version_arg, v))
+
+            if version_level >= 0:
+                res.append('{0}={1}'.format(version_arg, version_level))
 
         if 'import_dirs' in kwargs:
             import_dirs = kwargs.pop('import_dirs')
@@ -378,7 +418,11 @@ def translate_arg_to_osx(cls, arg):
         return args
 
     def get_debug_args(self, is_debug):
-        return clike_debug_args[is_debug]
+        ddebug_args = []
+        if is_debug:
+            ddebug_args = [d_feature_args[self.id]['debug']]
+
+        return clike_debug_args[is_debug] + ddebug_args
 
     def get_crt_args(self, crt_val, buildtype):
         if not is_windows():


Test patch:
diff --git a/test cases/d/9 features/app.d b/test cases/d/9 features/app.d
--- a/test cases/d/9 features/app.d	
+++ b/test cases/d/9 features/app.d	
@@ -41,6 +41,30 @@ void main (string[] args)
             exit (1);
         }
     }
+    
+    version (With_VersionInteger)
+        version(3) exit(0);
+
+    version (With_Debug)
+        debug exit(0);
+    
+    version (With_DebugInteger)
+        debug(3) exit(0);
+    
+    version (With_DebugIdentifier)
+        debug(DebugIdentifier) exit(0);
+    
+    version (With_DebugAll) {
+        int dbg = 0;
+        debug dbg++;
+        debug(2) dbg++;
+        debug(3) dbg++;
+        debug(4) dbg++;
+        debug(DebugIdentifier) dbg++;
+        
+        if (dbg == 5)
+            exit(0);
+    }
 
     // we fail here
     exit (1);
diff --git a/test cases/d/9 features/meson.build b/test cases/d/9 features/meson.build
--- a/test cases/d/9 features/meson.build	
+++ b/test cases/d/9 features/meson.build	
@@ -1,4 +1,4 @@
-project('D Features', 'd')
+project('D Features', 'd', default_options : ['debug=false'])
 
 # ONLY FOR BACKWARDS COMPATIBILITY.
 # DO NOT DO THIS IN NEW CODE!
@@ -44,3 +44,63 @@ e_test = executable('dapp_test',
     d_unittest: true
 )
 test('dapp_test', e_test)
+
+# test version level
+e_version_int = executable('dapp_version_int',
+    test_src,
+    d_import_dirs: [data_dir],
+    d_module_versions: ['With_VersionInteger', 3],
+)
+test('dapp_version_int_t', e_version_int, args: ['debug'])
+
+# test version level failure
+e_version_int_fail = executable('dapp_version_int_fail',
+    test_src,
+    d_import_dirs: [data_dir],
+    d_module_versions: ['With_VersionInteger', 2],
+)
+test('dapp_version_int_t_fail', e_version_int_fail, args: ['debug'], should_fail: true)
+
+# test debug conditions: disabled
+e_no_debug = executable('dapp_no_debug',
+    test_src,
+    d_import_dirs: [data_dir],
+    d_module_versions: ['With_Debug'],
+)
+test('dapp_no_debug_t_fail', e_no_debug, args: ['debug'], should_fail: true)
+
+# test debug conditions: enabled
+e_debug = executable('dapp_debug',
+    test_src,
+    d_import_dirs: [data_dir],
+    d_module_versions: ['With_Debug'],
+    d_debug: 1,
+)
+test('dapp_debug_t', e_debug, args: ['debug'])
+
+# test debug conditions: integer
+e_debug_int = executable('dapp_debug_int',
+    test_src,
+    d_import_dirs: [data_dir],
+    d_module_versions: ['With_DebugInteger'],
+    d_debug: 3,
+)
+test('dapp_debug_int_t', e_debug_int, args: ['debug'])
+
+# test debug conditions: identifier
+e_debug_ident = executable('dapp_debug_ident',
+    test_src,
+    d_import_dirs: [data_dir],
+    d_module_versions: ['With_DebugIdentifier'],
+    d_debug: 'DebugIdentifier',
+)
+test('dapp_debug_ident_t', e_debug_ident, args: ['debug'])
+
+# test with all debug conditions at once, and with redundant values
+e_debug_all = executable('dapp_debug_all',
+    test_src,
+    d_import_dirs: [data_dir],
+    d_module_versions: ['With_DebugAll'],
+    d_debug: ['4', 'DebugIdentifier', 2, 'DebugIdentifierUnused'],
+)
+test('dapp_debug_all_t', e_debug_all, args: ['debug'])