From 700010e452517d0a0b11e8e460d65b257a449302 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Thu, 7 Apr 2016 20:19:52 +0530 Subject: [PATCH 1/4] New API: cc.has_header_symbol to check if a header defines a specific symbol Also supports a 'prefix' keyword argument for feature checks such as _GNU_SOURCE or for headers that need to be included first --- mesonbuild/compilers.py | 10 ++++++++++ mesonbuild/interpreter.py | 19 +++++++++++++++++++ .../common/111 has header symbol/meson.build | 18 ++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 test cases/common/111 has header symbol/meson.build diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 4f55be444..229d22434 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -258,6 +258,9 @@ class Compiler(): def has_header(self, *args, **kwargs): raise EnvironmentException('Language %s does not support header checks.' % self.language) + def has_header_symbol(self, *args, **kwargs): + raise EnvironmentException('Language %s does not support header symbol checks.' % self.language) + def compiles(self, *args, **kwargs): raise EnvironmentException('Language %s does not support compile checks.' % self.language) @@ -460,6 +463,13 @@ int someSymbolHereJustForFun; ''' return self.compiles(templ % hname, extra_args) + def has_header_symbol(self, hname, symbol, prefix, extra_args=[]): + templ = '''{2} +#include <{0}> +int main () {{ {1}; }}''' + # Pass -O0 to ensure that the symbol isn't optimized away + return self.compiles(templ.format(hname, symbol, prefix), extra_args + ['-O0']) + def compile(self, code, srcname, extra_args=[]): commands = self.get_exelist() commands.append(srcname) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 0a88ce499..d6a3a3e32 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -575,6 +575,7 @@ class CompilerHolder(InterpreterObject): 'get_id': self.get_id_method, 'sizeof': self.sizeof_method, 'has_header': self.has_header_method, + 'has_header_symbol': self.has_header_symbol_method, 'run' : self.run_method, 'has_function' : self.has_function_method, 'has_member' : self.has_member_method, @@ -752,6 +753,24 @@ class CompilerHolder(InterpreterObject): mlog.log('Has header "%s":' % string, h) return haz + def has_header_symbol_method(self, args, kwargs): + if len(args) != 2: + raise InterpreterException('has_header_symbol method takes exactly two arguments.') + check_stringlist(args) + hname = args[0] + symbol = args[1] + prefix = kwargs.get('prefix', '') + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of has_function must be a string.') + extra_args = self.determine_args(kwargs) + haz = self.compiler.has_header_symbol(hname, symbol, prefix, extra_args) + if haz: + h = mlog.green('YES') + else: + h = mlog.red('NO') + mlog.log('Header <{0}> has symbol "{1}":'.format(hname, symbol), h) + return haz + def find_library_method(self, args, kwargs): if len(args) != 1: raise InterpreterException('find_library method takes one argument.') diff --git a/test cases/common/111 has header symbol/meson.build b/test cases/common/111 has header symbol/meson.build new file mode 100644 index 000000000..e0afb4212 --- /dev/null +++ b/test cases/common/111 has header symbol/meson.build @@ -0,0 +1,18 @@ +project('has header symbol', 'c') + +cc = meson.get_compiler('c') + +assert (cc.has_header_symbol('stdio.h', 'int'), 'base types should always be available') +assert (cc.has_header_symbol('stdio.h', 'printf'), 'printf function not found') +assert (cc.has_header_symbol('stdio.h', 'FILE'), 'FILE structure not found') +assert (cc.has_header_symbol('limits.h', 'INT_MAX'), 'INT_MAX define not found') +assert (not cc.has_header_symbol('limits.h', 'guint64'), 'guint64 is not defined in limits.h') +assert (not cc.has_header_symbol('stdlib.h', 'FILE'), 'FILE structure is defined in stdio.h, not stdlib.h') +assert (not cc.has_header_symbol('stdlol.h', 'printf'), 'stdlol.h shouldn\'t exist') +assert (not cc.has_header_symbol('stdlol.h', 'int'), 'shouldn\'t be able to find "int" with invalid header') + +# This is likely only available on Glibc, so just test for it +if cc.has_function('ppoll') + assert (not cc.has_header_symbol('poll.h', 'ppoll'), 'ppoll should not be accessible without _GNU_SOURCE') + assert (cc.has_header_symbol('poll.h', 'ppoll', prefix : '#define _GNU_SOURCE'), 'ppoll should be accessible with _GNU_SOURCE') +endif From 1934ddfc5b21d3dd2bb16dfeae67605e11808bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 7 Apr 2016 20:19:52 +0530 Subject: [PATCH 2/4] Improve cc.has_function() check to not require any includes and detect stubs We now use .links() to detect if a C compiler function is available or not, that way the user doesn't need to specify all the possible includes for the check, which simplifies things considerably. Also detect glibc stub functions that will never work and return false for them. Closes #437 --- mesonbuild/compilers.py | 46 +++++++++++++++---- test cases/common/43 has function/meson.build | 24 ++++++++++ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 229d22434..0a4473bcc 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -647,15 +647,41 @@ int main(int argc, char **argv) { return align def has_function(self, funcname, prefix, env, extra_args=[]): - # This fails (returns true) if funcname is a ptr or a variable. - # The correct check is a lot more difficult. - # Fix this to do that eventually. - templ = '''%s -int main(int argc, char **argv) { - void *ptr = (void*)(%s); - return 0; -}; -''' + # Define the symbol to something else in case it is defined by the + # includes or defines listed by the user `{0}` or by the compiler. + # Then, undef the symbol to get rid of it completely. + templ = ''' + #define {1} meson_disable_define_of_{1} + {0} + #undef {1} + ''' + + # Override any GCC internal prototype and declare our own definition for + # the symbol. Use char because that's unlikely to be an actual return + # value for a function which ensures that we override the definition. + templ += ''' + #ifdef __cplusplus + extern "C" + #endif + char {1} (); + ''' + + # glibc defines functions that are not available on Linux as stubs that + # fail with ENOSYS (such as e.g. lchmod). In this case we want to fail + # instead of detecting the stub as a valid symbol. + templ += ''' + #if defined __stub_{1} || defined __stub___{1} + fail fail fail this function is not going to work + #endif + ''' + + # And finally the actual function call + templ += ''' + int + main () + {{ + return {1} (); + }}''' varname = 'has function ' + funcname varname = varname.replace(' ', '_') if self.is_cross: @@ -664,7 +690,7 @@ int main(int argc, char **argv) { if isinstance(val, bool): return val raise EnvironmentException('Cross variable {0} is not a boolean.'.format(varname)) - return self.compiles(templ % (prefix, funcname), extra_args) + return self.links(templ.format(prefix, funcname), extra_args) def has_member(self, typename, membername, prefix, extra_args=[]): templ = '''%s diff --git a/test cases/common/43 has function/meson.build b/test cases/common/43 has function/meson.build index 8fccaef1d..3736a3db6 100644 --- a/test cases/common/43 has function/meson.build +++ b/test cases/common/43 has function/meson.build @@ -6,6 +6,30 @@ if not cc.has_function('printf', prefix : '#include') error('Existing function not found.') endif +# Should also be able to detect it without specifying the header +# We check for a different function here to make sure the result is +# not taken from a cache (ie. the check above) +assert(cc.has_function('fprintf'), 'Existing function not found without include') + if cc.has_function('hfkerhisadf', prefix : '#include') error('Found non-existant function.') endif + +# With glibc on Linux lchmod is a stub that will always return an error, +# we want to detect that and declare that the function is not available. +# We can't check for the C library used here of course, but if it's not +# implemented in glibc it's probably not implemented in any other 'slimmer' +# C library variants either, so the check should be safe either way hopefully. +if host_machine.system() == 'linux' and cc.get_id() == 'gcc' + assert (cc.has_function('poll', prefix : '#include '), 'couldn\'t detect poll when defined by a header') + assert (not cc.has_function('lchmod', prefix : '''#include + #include '''), 'lchmod check should have failed') +endif + +# For some functions one needs to define _GNU_SOURCE before including the +# right headers to get them picked up. Make sure we can detect these functions +# as well without any prefix +if cc.has_header_symbol('sys/socket.h', 'recvmmsg', prefix : '#define _GNU_SOURCE') + # We assume that if recvmmsg exists sendmmsg does too + assert (cc.has_function('sendmmsg'), 'Failed to detect existing function') +endif From 4e084e7ac1339d7a48b08028b6371265ca2f2a9a Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Thu, 7 Apr 2016 20:19:52 +0530 Subject: [PATCH 3/4] compilers: Also support built-in functions in cc.has_function --- mesonbuild/compilers.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 0a4473bcc..1c672660e 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -690,7 +690,14 @@ int main(int argc, char **argv) { if isinstance(val, bool): return val raise EnvironmentException('Cross variable {0} is not a boolean.'.format(varname)) - return self.links(templ.format(prefix, funcname), extra_args) + if self.links(templ.format(prefix, funcname), extra_args): + return True + # Some functions like alloca() are defined as compiler built-ins which + # are inlined by the compiler, so test for that instead. Built-ins are + # special functions that ignore all includes and defines, so we just + # directly try to link via main(). + # Add -O0 to ensure that the symbol isn't optimized away by the compiler + return self.links('int main() {{ {0}; }}'.format('__builtin_' + funcname), extra_args + ['-O0']) def has_member(self, typename, membername, prefix, extra_args=[]): templ = '''%s From e72523ae410d780c3a703f0e3296105408fcc122 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 11 Apr 2016 04:29:09 +0530 Subject: [PATCH 4/4] compilers: Use compiler-specific no-optimization flags MSVC doesn't understand -O0. It uses -Od (or /Od) instead. --- mesonbuild/compilers.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 1c672660e..9e90bccde 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -350,6 +350,9 @@ class CCompiler(Compiler): def get_compile_only_args(self): return ['-c'] + def get_no_optimization_args(self): + return ['-O0'] + def get_output_args(self, target): return ['-o', target] @@ -468,7 +471,8 @@ int someSymbolHereJustForFun; #include <{0}> int main () {{ {1}; }}''' # Pass -O0 to ensure that the symbol isn't optimized away - return self.compiles(templ.format(hname, symbol, prefix), extra_args + ['-O0']) + extra_args += self.get_no_optimization_args() + return self.compiles(templ.format(hname, symbol, prefix), extra_args) def compile(self, code, srcname, extra_args=[]): commands = self.get_exelist() @@ -697,7 +701,8 @@ int main(int argc, char **argv) { # special functions that ignore all includes and defines, so we just # directly try to link via main(). # Add -O0 to ensure that the symbol isn't optimized away by the compiler - return self.links('int main() {{ {0}; }}'.format('__builtin_' + funcname), extra_args + ['-O0']) + extra_args += self.get_no_optimization_args() + return self.links('int main() {{ {0}; }}'.format('__builtin_' + funcname), extra_args) def has_member(self, typename, membername, prefix, extra_args=[]): templ = '''%s @@ -1298,6 +1303,9 @@ class VisualStudioCCompiler(CCompiler): def get_compile_only_args(self): return ['/c'] + def get_no_optimization_args(self): + return ['/Od'] + def get_output_args(self, target): if target.endswith('.exe'): return ['/Fe' + target]