Repository: mesonbuild/meson

Issue:
meson instrospect without configured directory broken by b72b5365afe319e576e254f12437aeff899eb03e
Before said commit `meson introspect --buildoptions meson.build` would return something, now it returns nothing.


Discussion:
This seems to be related to having a `build_by_default : array.contains('foo')`. Still haven't figured it all out yet.
I am unable to reproduce this bug on the current master?

What project are you using?
Mesa in this case. I've pushed https://github.com/dcbaker/meson/tree/reproduce-5884, which has a patch to the introspect test that reproduces the problem

Resolving patch:
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py
--- a/mesonbuild/ast/interpreter.py
+++ b/mesonbuild/ast/interpreter.py
@@ -245,7 +245,7 @@ def assignment(self, node):
             self.reverse_assignment[node.value.ast_id] = node
         self.assign_vals[node.var_name] = [self.evaluate_statement(node.value)] # Evaluate the value just in case
 
-    def flatten_args(self, args: Any, include_unknown_args: bool = False, id_loop_detect: Optional[List[str]] = None) -> List[str]:
+    def resolve_node(self, node: BaseNode, include_unknown_args: bool = False, id_loop_detect: Optional[List[str]] = None) -> Optional[Any]:
         def quick_resolve(n: BaseNode, loop_detect: Optional[List[str]] = None) -> Any:
             if loop_detect is None:
                 loop_detect = []
@@ -260,74 +260,98 @@ def quick_resolve(n: BaseNode, loop_detect: Optional[List[str]] = None) -> Any:
 
         if id_loop_detect is None:
             id_loop_detect = []
-        flattend_args = []
+        result = None
+
+        if not isinstance(node, BaseNode):
+            return None
+
+        assert(hasattr(node, 'ast_id'))
+        if node.ast_id in id_loop_detect:
+            return None # Loop detected
+        id_loop_detect += [node.ast_id]
 
-        if isinstance(args, BaseNode):
-            assert(hasattr(args, 'ast_id'))
-            if args.ast_id in id_loop_detect:
-                return [] # Loop detected
-            id_loop_detect += [args.ast_id]
+        # Try to evealuate the value of the node
+        if isinstance(node, IdNode):
+            result = quick_resolve(node)
 
-        if isinstance(args, ArrayNode):
-            args = [x for x in args.args.arguments]
+        elif isinstance(node, ElementaryNode):
+            result = node.value
 
-        elif isinstance(args, ArgumentNode):
-            args = [x for x in args.arguments]
+        elif isinstance(node, ArrayNode):
+            result = [x for x in node.args.arguments]
 
-        elif isinstance(args, ArithmeticNode):
-            if args.operation != 'add':
-                return [] # Only handle string and array concats
-            l = quick_resolve(args.left)
-            r = quick_resolve(args.right)
+        elif isinstance(node, ArgumentNode):
+            result = [x for x in node.arguments]
+
+        elif isinstance(node, ArithmeticNode):
+            if node.operation != 'add':
+                return None # Only handle string and array concats
+            l = quick_resolve(node.left)
+            r = quick_resolve(node.right)
             if isinstance(l, str) and isinstance(r, str):
-                args = [l + r] # String concatination detected
+                result = l + r # String concatination detected
             else:
-                args = self.flatten_args(l, include_unknown_args, id_loop_detect) + self.flatten_args(r, include_unknown_args, id_loop_detect)
+                result = self.flatten_args(l, include_unknown_args, id_loop_detect) + self.flatten_args(r, include_unknown_args, id_loop_detect)
 
-        elif isinstance(args, MethodNode):
-            src = quick_resolve(args.source_object)
-            margs = self.flatten_args(args.args, include_unknown_args, id_loop_detect)
+        elif isinstance(node, MethodNode):
+            src = quick_resolve(node.source_object)
+            margs = self.flatten_args(node.args, include_unknown_args, id_loop_detect)
             try:
                 if isinstance(src, str):
-                    args = [self.string_method_call(src, args.name, margs)]
+                    result = self.string_method_call(src, node.name, margs)
                 elif isinstance(src, bool):
-                    args = [self.bool_method_call(src, args.name, margs)]
+                    result = self.bool_method_call(src, node.name, margs)
                 elif isinstance(src, int):
-                    args = [self.int_method_call(src, args.name, margs)]
+                    result = self.int_method_call(src, node.name, margs)
                 elif isinstance(src, list):
-                    args = [self.array_method_call(src, args.name, margs)]
+                    result = self.array_method_call(src, node.name, margs)
                 elif isinstance(src, dict):
-                    args = [self.dict_method_call(src, args.name, margs)]
-                else:
-                    return []
+                    result = self.dict_method_call(src, node.name, margs)
             except mesonlib.MesonException:
-                return []
+                return None
+
+        # Ensure that the result is fully resolved (no more nodes)
+        if isinstance(result, BaseNode):
+            result = self.resolve_node(result, include_unknown_args, id_loop_detect)
+        elif isinstance(result, list):
+            new_res = []
+            for i in result:
+                if isinstance(i, BaseNode):
+                    resolved = self.resolve_node(i, include_unknown_args, id_loop_detect)
+                    if resolved is not None:
+                        new_res += self.flatten_args(resolved, include_unknown_args, id_loop_detect)
+                else:
+                    new_res += [i]
+            result = new_res
+
+        return result
 
+    def flatten_args(self, args: Any, include_unknown_args: bool = False, id_loop_detect: Optional[List[str]] = None) -> List[Any]:
         # Make sure we are always dealing with lists
         if not isinstance(args, list):
             args = [args]
 
+        flattend_args = []
+
         # Resolve the contents of args
         for i in args:
-            if isinstance(i, IdNode):
-                flattend_args += self.flatten_args(quick_resolve(i), include_unknown_args, id_loop_detect)
-            elif isinstance(i, (ArrayNode, ArgumentNode, ArithmeticNode, MethodNode)):
-                flattend_args += self.flatten_args(i, include_unknown_args, id_loop_detect)
-            elif isinstance(i, mparser.ElementaryNode):
-                flattend_args += [i.value]
-            elif isinstance(i, (str, bool, int, float)):
-                flattend_args += [i]
-            elif include_unknown_args:
+            if isinstance(i, BaseNode):
+                resolved = self.resolve_node(i, include_unknown_args, id_loop_detect)
+                if resolved is not None:
+                    if not isinstance(resolved, list):
+                        resolved = [resolved]
+                    flattend_args += resolved
+            elif isinstance(i, (str, bool, int, float)) or include_unknown_args:
                 flattend_args += [i]
         return flattend_args
 
     def flatten_kwargs(self, kwargs: object, include_unknown_args: bool = False):
         flattend_kwargs = {}
         for key, val in kwargs.items():
-            if isinstance(val, ElementaryNode):
-                flattend_kwargs[key] = val.value
-            elif isinstance(val, (ArrayNode, ArgumentNode, ArithmeticNode, MethodNode)):
-                flattend_kwargs[key] = self.flatten_args(val, include_unknown_args)
+            if isinstance(val, BaseNode):
+                resolved = self.resolve_node(val, include_unknown_args)
+                if resolved is not None:
+                    flattend_kwargs[key] = resolved
             elif isinstance(val, (str, bool, int, float)) or include_unknown_args:
                 flattend_kwargs[key] = val
         return flattend_kwargs


Test patch:
diff --git a/test cases/unit/59 introspect buildoptions/meson.build b/test cases/unit/59 introspect buildoptions/meson.build
--- a/test cases/unit/59 introspect buildoptions/meson.build	
+++ b/test cases/unit/59 introspect buildoptions/meson.build	
@@ -5,7 +5,9 @@ subA = subproject('projectA')
 target_name = 'MAIN'
 target_src = ['main.c']
 
-executable(target_name, target_src)
+foo = false
+
+executable(target_name, target_src, build_by_default : foo)
 
 r = run_command(find_program('c_compiler.py'))
 if r.returncode() != 0