Repository: mesonbuild/meson

Issue:
Inconsistency in compiler flags checking vs building
This is an observation more than a bug report. I'm not sure if this actually is a bug. Please close and educate me if the latter is the case!

I was checking for `setenv` which according to internets and the man page:

```
CONFORMING TO
       4.3BSD, POSIX.1-2001.
```

Meaning, not part of the ISO C standard.

When checking for `setenv` I used

```
cc = meson.get_compiler('c')

conf = configuration_data()
if cc.has_function('setenv', prefix : '#include <stdlib.h>')
  conf.set('HAVE_SETENV', 1)
endif
configure_file(input : 'config.h.in', output : 'config.h', configuration : conf)
```

this invokes the compiler like this (according to the logs):

```
Native c compiler: cc (gcc 4.9.2-10ubuntu13)
Running compile:
Command line:  cc -c /tmp/tmpk_7alfwn.c
Code:
 #include <stdlib.h>
int main(int argc, char **argv) {
    void *ptr = (void*)(setenv);
    return 0;
};

Compiler stdout:

Compiler stderr:

Checking for function "setenv": YES
```

But when I build/compile my simple executable the compiler is invoked with -std=c11 which is default

```
[1/2] cc  '-pipe' '-Wall' '-Winvalid-pch' '-std=c11' '-g' '-Isetenvtest@exe' '-I.../meson/test cases/common/95 setenv' '-I.' '-MMD' '-MQ' 'setenvtest@exe/prog.c.o' '-MF' 'setenvtest@exe/prog.c.o.d' -o 'setenvtest@exe/prog.c.o' -c '.../meson/test cases/common/95 setenv/prog.c'
.../meson/test cases/common/95 setenv/prog.c: In function ‘main’:
.../meson/test cases/common/95 setenv/prog.c:8:3: warning: implicit declaration of function ‘setenv’ [-Wimplicit-function-declaration]
   return setenv ("FOO", "BAR", 0);
   ^
[2/2] cc    -o setenvtest 'setenvtest@exe/prog.c.o'  
```

This yields `__STRICT_ANSI__` and gives a warning because `setenv` isn't declared.

Are there anything I should do or anything meson could do to fix this? It seems a bit weird that the configuration step says yay, we have setenv, but when using it, it will complain.



Discussion:
The reason Meson does not automatically put anything in the compile flags for tests is that you may need to run compile tests without those flags. As an example you might need to compile some test first with -std=c99 and then with -std=c11 before choosing which one to use. If project flags were used automatically that would not be possible.

You can fix this particular issue by specifying extra arguments to the compile test with `args` keyword argument. There should probably be a "get the default flags" kinda thing that you could pass to the tests, but that requires some thinking as it is not immediately obvious how it should be done.


Resolving patch:
diff --git a/interpreter.py b/interpreter.py
--- a/interpreter.py
+++ b/interpreter.py
@@ -583,6 +583,18 @@ def version_method(self, args, kwargs):
     def cmd_array_method(self, args, kwargs):
         return self.compiler.exelist
 
+    def determine_args(self, kwargs):
+        nobuiltins = kwargs.get('no_builtin_args', False)
+        if not isinstance(nobuiltins, bool):
+            raise InterpreterException('Type of no_builtin_args not a boolean.')
+        args = []
+        if not nobuiltins:
+            opts = self.environment.coredata.compiler_options
+            args += self.compiler.get_option_compile_args(opts)
+            args += self.compiler.get_option_link_args(opts)
+        args += mesonlib.stringlistify(kwargs.get('args', []))
+        return args
+
     def alignment_method(self, args, kwargs):
         if len(args) != 1:
             raise InterpreterException('Alignment method takes exactly one positional argument.')
@@ -601,7 +613,7 @@ def run_method(self, args, kwargs):
         testname = kwargs.get('name', '')
         if not isinstance(testname, str):
             raise InterpreterException('Testname argument must be a string.')
-        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
+        extra_args = self.determine_args(kwargs)
         result = self.compiler.run(code, extra_args)
         if len(testname) > 0:
             if not result.compiled:
@@ -625,7 +637,7 @@ def has_member_method(self, args, kwargs):
         prefix = kwargs.get('prefix', '')
         if not isinstance(prefix, str):
             raise InterpreterException('Prefix argument of has_function must be a string.')
-        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
+        extra_args = self.determine_args(kwargs)
         had = self.compiler.has_member(typename, membername, prefix, extra_args)
         if had:
             hadtxt = mlog.green('YES')
@@ -643,7 +655,7 @@ def has_function_method(self, args, kwargs):
         prefix = kwargs.get('prefix', '')
         if not isinstance(prefix, str):
             raise InterpreterException('Prefix argument of has_function must be a string.')
-        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
+        extra_args = self.determine_args(kwargs)
         had = self.compiler.has_function(funcname, prefix, self.environment, extra_args)
         if had:
             hadtxt = mlog.green('YES')
@@ -660,7 +672,7 @@ def has_type_method(self, args, kwargs):
         prefix = kwargs.get('prefix', '')
         if not isinstance(prefix, str):
             raise InterpreterException('Prefix argument of has_type must be a string.')
-        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
+        extra_args = self.determine_args(kwargs)
         had = self.compiler.has_type(typename, prefix, extra_args)
         if had:
             hadtxt = mlog.green('YES')
@@ -677,7 +689,7 @@ def sizeof_method(self, args, kwargs):
         prefix = kwargs.get('prefix', '')
         if not isinstance(prefix, str):
             raise InterpreterException('Prefix argument of sizeof must be a string.')
-        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
+        extra_args = self.determine_args(kwargs)
         esize = self.compiler.sizeof(element, prefix, self.environment, extra_args)
         mlog.log('Checking for size of "%s": %d' % (element, esize))
         return esize
@@ -690,7 +702,7 @@ def compiles_method(self, args, kwargs):
         testname = kwargs.get('name', '')
         if not isinstance(testname, str):
             raise InterpreterException('Testname argument must be a string.')
-        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
+        extra_args = self.determine_args(kwargs)
         result = self.compiler.compiles(code, extra_args)
         if len(testname) > 0:
             if result:
@@ -708,7 +720,7 @@ def links_method(self, args, kwargs):
         testname = kwargs.get('name', '')
         if not isinstance(testname, str):
             raise InterpreterException('Testname argument must be a string.')
-        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
+        extra_args = self.determine_args(kwargs)
         result = self.compiler.links(code, extra_args)
         if len(testname) > 0:
             if result:
@@ -723,7 +735,7 @@ def has_header_method(self, args, kwargs):
             raise InterpreterException('has_header method takes exactly one argument.')
         check_stringlist(args)
         string = args[0]
-        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
+        extra_args = self.determine_args(kwargs)
         haz = self.compiler.has_header(string, extra_args)
         if haz:
             h = mlog.green('YES')
@@ -1329,7 +1341,8 @@ def parse_default_options(self, default_options):
                     setattr(self.coredata, key, value)
                 # If this was set on the command line, do not override.
             else:
-                self.environment.cmd_line_options.projectoptions.insert(0, option)
+                newoptions = [option] + self.environment.cmd_line_options.projectoptions
+                self.environment.cmd_line_options.projectoptions = newoptions
 
 
     @stringArgs


Test patch:
diff --git a/test cases/common/94 default options/meson.build b/test cases/common/94 default options/meson.build
--- a/test cases/common/94 default options/meson.build	
+++ b/test cases/common/94 default options/meson.build	
@@ -1,15 +1,25 @@
-project('default options', 'cpp', default_options : [
+project('default options', 'cpp', 'c', default_options : [
   'buildtype=debugoptimized',
   'cpp_std=c++03',
-  'cpp_eh=none'
+  'cpp_eh=none',
+  'c_std=-crashcompiler',
   ])
 
-cc = meson.get_compiler('cpp')
+cpp = meson.get_compiler('cpp')
 
 assert(get_option('buildtype') == 'debugoptimized', 'Build type default value wrong.')
 
-if cc.get_id() == 'msvc'
+if cpp.get_id() == 'msvc'
   assert(get_option('cpp_eh') == 'none', 'MSVC eh value wrong.')
 else
   assert(get_option('cpp_std') == 'c++03', 'C++ std value wrong.')
 endif
+
+# Verify that project args are not used when told not to.
+# MSVC plain C does not have a simple arg to test so skip it.
+if cpp.get_id() != 'msvc'
+  cc = meson.get_compiler('c')
+  assert(not cc.compiles('int foobar;'), 'Default arg not used in test.')
+  assert(cc.compiles('int foobar;', no_builtin_args : true), 'No_builtin did not disable builtins.')
+endif
+