diff options
-rw-r--r-- | mesonbuild/compilers.py | 181 | ||||
-rw-r--r-- | mesonbuild/environment.py | 12 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 5 | ||||
-rw-r--r-- | test cases/osx/3 has function xcode8/meson.build | 26 |
4 files changed, 138 insertions, 86 deletions
diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index d7cd1f7..94e8a54 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -17,7 +17,7 @@ import subprocess, os.path import tempfile from .import mesonlib from . import mlog -from .mesonlib import MesonException +from .mesonlib import MesonException, version_compare from . import coredata """This file contains the data files of all compilers Meson knows @@ -707,12 +707,16 @@ int main () {{ {1}; }}''' args = self.unix_link_flags_to_native(cargs + extra_args) # Read c_args/cpp_args/etc from the cross-info file (if needed) args += self.get_cross_extra_flags(env, compile=True, link=False) + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env + # We assume that the user has ensured these are compiler-specific + args += env.coredata.external_args[self.language] # We only want to compile; not link args += self.get_compile_only_args() with self.compile(code, args) as p: return p.returncode == 0 - def links(self, code, env, extra_args=None, dependencies=None): + def _links_wrapper(self, code, env, extra_args, dependencies): + "Shares common code between self.links and self.run" if extra_args is None: extra_args = [] elif isinstance(extra_args, str): @@ -729,27 +733,19 @@ int main () {{ {1}; }}''' args += self.get_linker_debug_crt_args() # Read c_args/c_link_args/cpp_args/cpp_link_args/etc from the cross-info file (if needed) args += self.get_cross_extra_flags(env, compile=True, link=True) - with self.compile(code, args) as p: + # Add LDFLAGS from the env. We assume that the user has ensured these + # are compiler-specific + args += env.coredata.external_link_args[self.language] + return self.compile(code, args) + + def links(self, code, env, extra_args=None, dependencies=None): + with self._links_wrapper(code, env, extra_args, dependencies) as p: return p.returncode == 0 def run(self, code, env, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] - if dependencies is None: - dependencies = [] - elif not isinstance(dependencies, list): - dependencies = [dependencies] if self.is_cross and self.exe_wrapper is None: raise CrossNoRunException('Can not run test applications in this cross environment.') - cargs = [a for d in dependencies for a in d.get_compile_args()] - link_args = [a for d in dependencies for a in d.get_link_args()] - # Convert flags to the native type of the selected compiler - args = self.unix_link_flags_to_native(cargs + link_args + extra_args) - # Select a CRT if needed since we're linking - args += self.get_linker_debug_crt_args() - # Read c_link_args/cpp_link_args/etc from the cross-info file - args += self.get_cross_extra_flags(env, compile=True, link=True) - with self.compile(code, args) as p: + with self._links_wrapper(code, env, extra_args, dependencies) as p: if p.returncode != 0: mlog.debug('Could not compile test file %s: %d\n' % ( p.input_name, @@ -878,55 +874,65 @@ int main(int argc, char **argv) { raise EnvironmentException('Could not determine alignment of %s. Sorry. You might want to file a bug.' % typename) return align - def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None): + @staticmethod + def _no_prototype_templ(): """ - First, this function looks for the symbol in the default libraries - provided by the compiler (stdlib + a few others usually). If that - fails, it checks if any of the headers specified in the prefix provide - an implementation of the function, and if that fails, it checks if it's - implemented as a compiler-builtin. + Try to find the function without a prototype from a header by defining + our own dummy prototype and trying to link with the C library (and + whatever else the compiler links in by default). This is very similar + to the check performed by Autoconf for AC_CHECK_FUNCS. """ - if extra_args is None: - extra_args = [] - # 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 the symbol to something else since it is defined by the + # includes or defines listed by the user (prefix -> {0}) or by the + # compiler. Then, undef the symbol to get rid of it completely. + head = ''' #define {1} meson_disable_define_of_{1} #include <limits.h> {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 += ''' + head += ''' #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. - # We always include limits.h above to ensure that these are defined for - # stub functions. - stubs_fail = ''' - #if defined __stub_{1} || defined __stub___{1} - fail fail fail this function is not going to work - #endif - ''' - templ += stubs_fail - - # And finally the actual function call - templ += ''' - int - main () + # The actual function call + main = ''' + int main () {{ return {1} (); }}''' + return head, main + + @staticmethod + def _have_prototype_templ(): + """ + Returns a head-er and main() call that uses the headers listed by the + user for the function prototype while checking if a function exists. + """ + # Add the 'prefix', aka defines, includes, etc that the user provides + head = '#include <limits.h>\n{0}\n' + # We don't know what the function takes or returns, so try to use it as + # a function pointer + main = '\nint main() {{ int a = (int) &{1}; }}' + return head, main + + def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None): + """ + First, this function looks for the symbol in the default libraries + provided by the compiler (stdlib + a few others usually). If that + fails, it checks if any of the headers specified in the prefix provide + an implementation of the function, and if that fails, it checks if it's + implemented as a compiler-builtin. + """ + if extra_args is None: + extra_args = [] + + # Short-circuit if the check is already provided by the cross-info file varname = 'has function ' + funcname varname = varname.replace(' ', '_') if self.is_cross: @@ -935,16 +941,35 @@ int main(int argc, char **argv) { if isinstance(val, bool): return val raise EnvironmentException('Cross variable {0} is not a boolean.'.format(varname)) - if self.links(templ.format(prefix, funcname), env, extra_args, dependencies): - return True + + # 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. + # We already included limits.h earlier to ensure that these are defined + # for stub functions. + stubs_fail = ''' + #if defined __stub_{1} || defined __stub___{1} + fail fail fail this function is not going to work + #endif + ''' + + # If we have any includes in the prefix supplied by the user, assume + # that the user wants us to use the symbol prototype defined in those + # includes. If not, then try to do the Autoconf-style check with + # a dummy prototype definition of our own. + # This is needed when the linker determines symbol availability from an + # SDK based on the prototype in the header provided by the SDK. + # Ignoring this prototype would result in the symbol always being + # marked as available. + if '#include' in prefix: + head, main = self._have_prototype_templ() + else: + head, main = self._no_prototype_templ() + templ = head + stubs_fail + main + # Add -O0 to ensure that the symbol isn't optimized away by the compiler args = extra_args + self.get_no_optimization_args() - # Sometimes the implementation is provided by the header, or the header - # redefines the symbol to be something else. In that case, we want to - # still detect the function. We still want to fail if __stub_foo or - # _stub_foo are defined, of course. - header_templ = '#include <limits.h>\n{0}\n' + stubs_fail + '\nint main() {{ {1}; }}' - if self.links(header_templ.format(prefix, funcname), env, args, dependencies): + if self.links(templ.format(prefix, funcname), env, extra_args, dependencies): 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 @@ -2073,6 +2098,20 @@ class ClangCompiler(): raise MesonException('Unreachable code when converting clang type to gcc type.') return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion) + def has_argument(self, arg, env): + return super().has_argument(['-Werror=unknown-warning-option', arg], env) + + def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None): + if extra_args is None: + extra_args = [] + # Starting with XCode 8, we need to pass this to force linker + # visibility to obey OS X and iOS minimum version targets with + # -mmacosx-version-min, -miphoneos-version-min, etc. + # https://github.com/Homebrew/homebrew-core/issues/3727 + if self.clang_type == CLANG_OSX and version_compare(self.version, '>=8.0'): + extra_args.append('-Wl,-no_weak_imports') + return super().has_function(funcname, prefix, env, extra_args, dependencies) + class ClangCCompiler(ClangCompiler, CCompiler): def __init__(self, exelist, version, clang_type, is_cross, exe_wrapper=None): CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) @@ -2099,11 +2138,8 @@ class ClangCCompiler(ClangCompiler, CCompiler): def get_option_link_args(self, options): return [] - def has_argument(self, arg, env): - return super().has_argument(['-Werror=unknown-warning-option', arg], env) - -class ClangCPPCompiler(ClangCompiler, CPPCompiler): +class ClangCPPCompiler(ClangCompiler, CPPCompiler): def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None): CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) ClangCompiler.__init__(self, cltype) @@ -2127,28 +2163,17 @@ class ClangCPPCompiler(ClangCompiler, CPPCompiler): def get_option_link_args(self, options): return [] - def has_argument(self, arg, env): - return super().has_argument(['-Werror=unknown-warning-option', arg], env) - -class ClangObjCCompiler(GnuObjCCompiler): +class ClangObjCCompiler(ClangCompiler, GnuObjCCompiler): def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None): - super().__init__(exelist, version, is_cross, exe_wrapper) - self.id = 'clang' + GnuObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) + ClangCompiler.__init__(self, cltype) self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] - self.clang_type = cltype - if self.clang_type != CLANG_OSX: - self.base_options.append('b_lundef') - self.base_options.append('b_asneeded') -class ClangObjCPPCompiler(GnuObjCPPCompiler): +class ClangObjCPPCompiler(ClangCompiler, GnuObjCPPCompiler): def __init__(self, exelist, version, cltype, is_cross, exe_wrapper=None): - super().__init__(exelist, version, is_cross, exe_wrapper) - self.id = 'clang' - self.clang_type = cltype + GnuObjCPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) + ClangCompiler.__init__(self, cltype) self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage'] - if self.clang_type != CLANG_OSX: - self.base_options.append('b_lundef') - self.base_options.append('b_asneeded') class FortranCompiler(Compiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None): diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 86c23ae..b810e20 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -833,9 +833,9 @@ class Environment(): return self.coredata.get_builtin_option('datadir') -def get_args_from_envvars(lang, compiler_is_linker): +def get_args_from_envvars(compiler): """ - @lang: Language to fetch environment flags for + @compiler: Compiler to fetch environment flags for Returns a tuple of (compile_flags, link_flags) for the specified language from the inherited environment @@ -844,14 +844,18 @@ def get_args_from_envvars(lang, compiler_is_linker): if val: mlog.log('Appending {} from environment: {!r}'.format(var, val)) + lang = compiler.get_language() + compiler_is_linker = False + if hasattr(compiler, 'get_linker_exelist'): + compiler_is_linker = (compiler.get_exelist() == compiler.get_linker_exelist()) + if lang not in ('c', 'cpp', 'objc', 'objcpp', 'fortran', 'd'): return ([], []) # Compile flags cflags_mapping = {'c': 'CFLAGS', 'cpp': 'CXXFLAGS', 'objc': 'OBJCFLAGS', 'objcpp': 'OBJCXXFLAGS', - 'fortran': 'FFLAGS', - 'd': 'DFLAGS'} + 'fortran': 'FFLAGS', 'd': 'DFLAGS'} compile_flags = os.environ.get(cflags_mapping[lang], '') log_var(cflags_mapping[lang], compile_flags) compile_flags = compile_flags.split() diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index dd2a227..26c8aef 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1792,11 +1792,8 @@ class Interpreter(): else: raise mlog.log('Native %s compiler: ' % lang, mlog.bold(' '.join(comp.get_exelist())), ' (%s %s)' % (comp.id, comp.version), sep='') - compiler_is_linker = False - if hasattr(comp, 'get_linker_exelist'): - compiler_is_linker = (comp.get_exelist() == comp.get_linker_exelist()) if not comp.get_language() in self.coredata.external_args: - (ext_compile_args, ext_link_args) = environment.get_args_from_envvars(comp.get_language(), compiler_is_linker) + (ext_compile_args, ext_link_args) = environment.get_args_from_envvars(comp) self.coredata.external_args[comp.get_language()] = ext_compile_args self.coredata.external_link_args[comp.get_language()] = ext_link_args self.build.add_compiler(comp) diff --git a/test cases/osx/3 has function xcode8/meson.build b/test cases/osx/3 has function xcode8/meson.build new file mode 100644 index 0000000..300d352 --- /dev/null +++ b/test cases/osx/3 has function xcode8/meson.build @@ -0,0 +1,26 @@ +project('has function xcode8', 'c') + +cc = meson.get_compiler('c') + +# XCode 8 location for the macOS 10.12 SDK +sdk_args = ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk'] +args_10_11 = ['-mmacosx-version-min=10.11'] + sdk_args +args_10_12 = ['-mmacosx-version-min=10.12'] + sdk_args + +# Test requires XCode 8 which has the MacOSX 10.12 SDK +if cc.version().version_compare('>=8.0') + if cc.has_function('clock_gettime', args : args_10_11, prefix : '#include <time.h>') + error('Should not have found clock_gettime via <time.h> when targetting Mac OS X 10.11') + endif + if not cc.has_function('clock_gettime', args : args_10_12, prefix : '#include <time.h>') + error('Did NOT find clock_gettime via <time.h> when targetting Mac OS X 10.12') + endif + if not cc.has_function('clock_gettime', args : args_10_11) + error('Did NOT find clock_gettime w/o a prototype when targetting Mac OS X 10.11') + endif + if not cc.has_function('clock_gettime', args : args_10_12) + error('Did NOT find clock_gettime w/o a prototype when targetting Mac OS X 10.12') + endif +else + message('Test needs XCode 8, skipping...') +endif |