Skip to content

Commit

Permalink
Merge pull request #949 from centricular/has-function-xcode8-fixes
Browse files Browse the repository at this point in the history
Fix has_function with XCode 8 and related changes
  • Loading branch information
jpakkane committed Nov 2, 2016
2 parents afe0069 + eea951c commit 97c2321
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 86 deletions.
181 changes: 103 additions & 78 deletions mesonbuild/compilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
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
Expand Down Expand Up @@ -707,12 +707,16 @@ def compiles(self, code, env, extra_args=None, dependencies=None):
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):
Expand All @@ -729,27 +733,19 @@ def links(self, code, env, extra_args=None, dependencies=None):
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,
Expand Down Expand Up @@ -878,55 +874,65 @@ def alignment(self, typename, env, extra_args=None, dependencies=None):
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:
Expand All @@ -935,16 +941,35 @@ def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None
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
Expand Down Expand Up @@ -2073,6 +2098,20 @@ def get_soname_args(self, prefix, shlib_name, suffix, path, soversion):
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)
Expand All @@ -2099,11 +2138,8 @@ def get_option_compile_args(self, options):
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)
Expand All @@ -2127,28 +2163,17 @@ def get_option_compile_args(self, options):
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):
Expand Down
12 changes: 8 additions & 4 deletions mesonbuild/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,9 +833,9 @@ def get_datadir(self):
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
Expand All @@ -844,14 +844,18 @@ def log_var(var, val):
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()
Expand Down
5 changes: 1 addition & 4 deletions mesonbuild/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1792,11 +1792,8 @@ def add_languages(self, args, required):
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)
Expand Down
26 changes: 26 additions & 0 deletions test cases/osx/3 has function xcode8/meson.build
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 97c2321

Please sign in to comment.