gecko-dev/build/moz.configure/toolchain.configure

1180 строки
44 KiB
Python
Executable File

# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# PGO
# ==============================================================
option(env='MOZ_PGO', help='Build with profile guided optimizations')
set_config('MOZ_PGO', depends('MOZ_PGO')(lambda x: bool(x)))
add_old_configure_assignment('MOZ_PGO', depends('MOZ_PGO')(lambda x: bool(x)))
# yasm detection
# ==============================================================
yasm = check_prog('YASM', ['yasm'], allow_missing=True)
@depends_if(yasm)
@checking('yasm version')
def yasm_version(yasm):
version = check_cmd_output(
yasm, '--version',
onerror=lambda: die('Failed to get yasm version.')
).splitlines()[0].split()[1]
return Version(version)
# Until we move all the yasm consumers out of old-configure.
# bug 1257904
add_old_configure_assignment('_YASM_MAJOR_VERSION',
yasm_version.major)
add_old_configure_assignment('_YASM_MINOR_VERSION',
yasm_version.minor)
@depends(yasm, target)
def yasm_asflags(yasm, target):
if yasm:
asflags = {
('OSX', 'x86'): '-f macho32',
('OSX', 'x86_64'): '-f macho64',
('WINNT', 'x86'): '-f win32',
('WINNT', 'x86_64'): '-f x64',
}.get((target.os, target.cpu), None)
if asflags is None:
# We're assuming every x86 platform we support that's
# not Windows or Mac is ELF.
if target.cpu == 'x86':
asflags = '-f elf32'
elif target.cpu == 'x86_64':
asflags = '-f elf64'
if asflags:
asflags += ' -rnasm -pnasm'
return asflags
set_config('YASM_ASFLAGS', yasm_asflags)
@depends(yasm_asflags)
def have_yasm(value):
if value:
return True
set_config('HAVE_YASM', have_yasm)
# Until the YASM variable is not necessary in old-configure.
add_old_configure_assignment('YASM', have_yasm)
# Android NDK
# ==============================================================
@depends('--disable-compile-environment', build_project, '--help')
def compiling_android(compile_env, build_project, _):
return compile_env and build_project in ('mobile/android', 'js')
include('android-ndk.configure', when=compiling_android)
# MacOS deployment target version
# ==============================================================
# This needs to happen before any compilation test is done.
option('--enable-macos-target', env='MACOSX_DEPLOYMENT_TARGET', nargs=1,
default='10.7', help='Set the minimum MacOS version needed at runtime')
@depends('--enable-macos-target', target)
@imports(_from='os', _import='environ')
def macos_target(value, target):
if value and target.os == 'OSX':
# Ensure every compiler process we spawn uses this value.
environ['MACOSX_DEPLOYMENT_TARGET'] = value[0]
return value[0]
if value and value.origin != 'default':
die('--enable-macos-target cannot be used when targeting %s',
target.os)
set_config('MACOSX_DEPLOYMENT_TARGET', macos_target)
add_old_configure_assignment('MACOSX_DEPLOYMENT_TARGET', macos_target)
# Compiler wrappers
# ==============================================================
# Normally, we'd use js_option and automatically have those variables
# propagated to js/src, but things are complicated by possible additional
# wrappers in CC/CXX, and by other subconfigures that do not handle those
# options and do need CC/CXX altered.
option('--with-compiler-wrapper', env='COMPILER_WRAPPER', nargs=1,
help='Enable compiling with wrappers such as distcc and ccache')
option('--with-ccache', env='CCACHE', nargs='?',
help='Enable compiling with ccache')
@depends_if('--with-ccache')
def ccache(value):
if len(value):
return value
# If --with-ccache was given without an explicit value, we default to
# 'ccache'.
return 'ccache'
ccache = check_prog('CCACHE', progs=(), input=ccache)
# Distinguish ccache from sccache.
@depends_if(ccache)
def ccache_is_sccache(ccache):
return check_cmd_output(ccache, '--version').startswith('sccache')
@depends(ccache, ccache_is_sccache)
def using_ccache(ccache, ccache_is_sccache):
return ccache and not ccache_is_sccache
@depends_if(ccache, ccache_is_sccache)
def using_sccache(ccache, ccache_is_sccache):
return ccache and ccache_is_sccache
set_config('MOZ_USING_CCACHE', using_ccache)
set_config('MOZ_USING_SCCACHE', using_sccache)
option(env='SCCACHE_VERBOSE_STATS', help='Print verbose sccache stats after build')
@depends(using_sccache, 'SCCACHE_VERBOSE_STATS')
def sccache_verbose_stats(using_sccache, verbose_stats):
return using_sccache and bool(verbose_stats)
set_config('SCCACHE_VERBOSE_STATS', sccache_verbose_stats)
@depends('--with-compiler-wrapper', ccache)
@imports(_from='mozbuild.shellutil', _import='split', _as='shell_split')
def compiler_wrapper(wrapper, ccache):
if wrapper:
raw_wrapper = wrapper[0]
wrapper = shell_split(raw_wrapper)
wrapper_program = find_program(wrapper[0])
if not wrapper_program:
die('Cannot find `%s` from the given compiler wrapper `%s`',
wrapper[0], raw_wrapper)
wrapper[0] = wrapper_program
if ccache:
if wrapper:
return tuple([ccache] + wrapper)
else:
return (ccache,)
elif wrapper:
return tuple(wrapper)
add_old_configure_assignment('COMPILER_WRAPPER', compiler_wrapper)
@depends_if(compiler_wrapper)
def using_compiler_wrapper(compiler_wrapper):
return True
set_config('MOZ_USING_COMPILER_WRAPPER', using_compiler_wrapper)
# GC rooting and hazard analysis.
# ==============================================================
option(env='MOZ_HAZARD', help='Build for the GC rooting hazard analysis')
@depends('MOZ_HAZARD')
def hazard_analysis(value):
if value:
return True
set_config('MOZ_HAZARD', hazard_analysis)
# Cross-compilation related things.
# ==============================================================
js_option('--with-toolchain-prefix', env='TOOLCHAIN_PREFIX', nargs=1,
help='Prefix for the target toolchain')
@depends('--with-toolchain-prefix', target, cross_compiling)
def toolchain_prefix(value, target, cross_compiling):
if value:
return tuple(value)
if cross_compiling:
return ('%s-' % target.toolchain, '%s-' % target.alias)
@depends(toolchain_prefix, target)
def first_toolchain_prefix(toolchain_prefix, target):
# Pass TOOLCHAIN_PREFIX down to the build system if it was given from the
# command line/environment (in which case there's only one value in the tuple),
# or when cross-compiling for Android.
if toolchain_prefix and (target.os == 'Android' or len(toolchain_prefix) == 1):
return toolchain_prefix[0]
set_config('TOOLCHAIN_PREFIX', first_toolchain_prefix)
add_old_configure_assignment('TOOLCHAIN_PREFIX', first_toolchain_prefix)
# Compilers
# ==============================================================
include('compilers-util.configure')
def try_preprocess(compiler, language, source):
return try_invoke_compiler(compiler, language, source, ['-E'])
@imports(_from='mozbuild.configure.constants', _import='CompilerType')
@imports(_from='mozbuild.configure.constants',
_import='CPU_preprocessor_checks')
@imports(_from='mozbuild.configure.constants',
_import='kernel_preprocessor_checks')
@imports(_from='textwrap', _import='dedent')
def get_compiler_info(compiler, language):
'''Returns information about the given `compiler` (command line in the
form of a list or tuple), in the given `language`.
The returned information includes:
- the compiler type (msvc, clang-cl, clang or gcc)
- the compiler version
- the compiler supported language
- the compiler supported language version
'''
# Note: MSVC doesn't expose __STDC_VERSION__. It does expose __STDC__,
# but only when given the -Za option, which disables compiler
# extensions.
# Note: We'd normally do a version check for clang, but versions of clang
# in Xcode have a completely different versioning scheme despite exposing
# the version with the same defines.
# So instead, we make things such that the version is missing when the
# clang used is below the minimum supported version (currently clang 3.6).
# We then only include the version information when the C++ compiler
# matches the feature check, so that an unsupported version of clang would
# have no version number.
check = dedent('''\
#if defined(_MSC_VER)
#if defined(__clang__)
%COMPILER "clang-cl"
%VERSION _MSC_FULL_VER
#else
%COMPILER "msvc"
%VERSION _MSC_FULL_VER
#endif
#elif defined(__clang__)
%COMPILER "clang"
# if !__cplusplus || __has_feature(cxx_alignof)
%VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
# endif
#elif defined(__GNUC__)
%COMPILER "gcc"
%VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
#endif
#if __cplusplus
%cplusplus __cplusplus
#elif __STDC_VERSION__
%STDC_VERSION __STDC_VERSION__
#elif __STDC__
%STDC_VERSION 198900L
#endif
''')
# While we're doing some preprocessing, we might as well do some more
# preprocessor-based tests at the same time, to check the toolchain
# matches what we want.
for name, preprocessor_checks in (
('CPU', CPU_preprocessor_checks),
('KERNEL', kernel_preprocessor_checks),
):
for n, (value, condition) in enumerate(preprocessor_checks.iteritems()):
check += dedent('''\
#%(if)s %(condition)s
%%%(name)s "%(value)s"
''' % {
'if': 'elif' if n else 'if',
'condition': condition,
'name': name,
'value': value,
})
check += '#endif\n'
# Also check for endianness. The advantage of living in modern times is
# that all the modern compilers we support now have __BYTE_ORDER__ defined
# by the preprocessor, except MSVC, which only supports little endian.
check += dedent('''\
#if _MSC_VER || __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
%ENDIANNESS "little"
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
%ENDIANNESS "big"
#endif
''')
result = try_preprocess(compiler, language, check)
if not result:
raise FatalCheckError(
'Unknown compiler or compiler not supported.')
# Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may
# have non-ASCII characters. Treat the output as bytearray.
data = {}
for line in result.splitlines():
if line.startswith(b'%'):
k, _, v = line.partition(' ')
k = k.lstrip('%')
data[k] = v.replace(' ', '').lstrip('"').rstrip('"')
log.debug('%s = %s', k, data[k])
try:
type = CompilerType(data['COMPILER'])
except:
raise FatalCheckError(
'Unknown compiler or compiler not supported.')
cplusplus = int(data.get('cplusplus', '0L').rstrip('L'))
stdc_version = int(data.get('STDC_VERSION', '0L').rstrip('L'))
version = data.get('VERSION')
if version and type in ('msvc', 'clang-cl'):
msc_ver = version
version = msc_ver[0:2]
if len(msc_ver) > 2:
version += '.' + msc_ver[2:4]
if len(msc_ver) > 4:
version += '.' + msc_ver[4:]
if version:
version = Version(version)
return namespace(
type=type,
version=version,
cpu=data.get('CPU'),
kernel=data.get('KERNEL'),
endianness=data.get('ENDIANNESS'),
language='C++' if cplusplus else 'C',
language_version=cplusplus if cplusplus else stdc_version,
)
@imports(_from='mozbuild.shellutil', _import='quote')
def check_compiler(compiler, language, target):
info = get_compiler_info(compiler, language)
flags = []
def append_flag(flag):
if flag not in flags:
if info.type == 'clang-cl':
flags.append('-Xclang')
flags.append(flag)
# Check language standards
# --------------------------------------------------------------------
if language != info.language:
raise FatalCheckError(
'`%s` is not a %s compiler.' % (quote(*compiler), language))
# Note: We do a strict version check because there sometimes are backwards
# incompatible changes in the standard, and not all code that compiles as
# C99 compiles as e.g. C11 (as of writing, this is true of libnestegg, for
# example)
if info.language == 'C' and info.language_version != 199901:
if info.type in ('clang-cl', 'clang', 'gcc'):
append_flag('-std=gnu99')
# Note: MSVC, while supporting C++11, still reports 199711L for __cplusplus.
# Note: this is a strict version check because we used to always add
# -std=gnu++11.
if info.language == 'C++':
if info.type in ('clang', 'gcc') and info.language_version != 201103:
append_flag('-std=gnu++11')
# MSVC 2015 headers include C++14 features, but don't guard them
# with appropriate checks.
if info.type == 'clang-cl' and info.language_version != 201402:
append_flag('-std=c++14')
# We force clang-cl to emulate Visual C++ 2015 Update 3.
if info.type == 'clang-cl' and info.version != '19.00.24213':
# This flag is a direct clang-cl flag that doesn't need -Xclang,
# add it directly.
flags.append('-fms-compatibility-version=19.00.24213')
# Check compiler target
# --------------------------------------------------------------------
if not info.cpu or info.cpu != target.cpu:
if info.type == 'clang':
append_flag('--target=%s' % target.toolchain)
elif info.type == 'gcc':
same_arch_different_bits = (
('x86', 'x86_64'),
('ppc', 'ppc64'),
('sparc', 'sparc64'),
)
if (target.cpu, info.cpu) in same_arch_different_bits:
append_flag('-m32')
elif (info.cpu, target.cpu) in same_arch_different_bits:
append_flag('-m64')
if not info.kernel or info.kernel != target.kernel:
if info.type == 'clang':
append_flag('--target=%s' % target.toolchain)
if not info.endianness or info.endianness != target.endianness:
if info.type == 'clang':
append_flag('--target=%s' % target.toolchain)
return namespace(
type=info.type,
version=info.version,
target_cpu=info.cpu,
target_kernel=info.kernel,
target_endianness=info.endianness,
flags=flags,
)
@imports(_from='__builtin__', _import='open')
@imports('json')
@imports('subprocess')
@imports('sys')
def get_vc_paths(topsrcdir):
def vswhere(args):
encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
return json.loads(subprocess.check_output([os.path.join(topsrcdir, 'build/win32/vswhere.exe'), '-format', 'json'] + args).decode(encoding, 'replace'))
# Can't pass -requires with -legacy, so query each separately.
# Legacy versions first (VS2015)
for install in vswhere(['-legacy', '-version', '[14.0,15.0)']):
version = Version(install['installationVersion'])
# Skip anything older than VS2015.
if version < '14':
continue
path = install['installationPath']
yield (Version(install['installationVersion']), {
'x64': [os.path.join(path, r'VC\bin\amd64')],
# The x64->x86 cross toolchain requires DLLs from the native x64 toolchain.
'x86': [os.path.join(path, r'VC\bin\amd64_x86'), os.path.join(path, r'VC\bin\amd64')],
})
# Then VS2017 and newer.
for install in vswhere(['-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64']):
path = install['installationPath']
tools_version = open(os.path.join(path, r'VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt'), 'rb').read().strip()
tools_path = os.path.join(path, r'VC\Tools\MSVC', tools_version, r'bin\HostX64')
yield (Version(install['installationVersion']), {
'x64': [os.path.join(tools_path, 'x64')],
# The x64->x86 cross toolchain requires DLLs from the native x64 toolchain.
'x86': [os.path.join(tools_path, 'x86'), os.path.join(tools_path, 'x64')],
})
option('--with-visual-studio-version', nargs=1,
choices=('2015', '2017'),
help='Select a specific Visual Studio version to use')
@depends('--with-visual-studio-version')
def vs_major_version(value):
if value:
return {'2015': 14,
'2017': 15}[value[0]]
@depends(host, target, vs_major_version, check_build_environment, '--with-visual-studio-version')
@imports(_from='__builtin__', _import='sorted')
@imports(_from='operator', _import='itemgetter')
@imports('platform')
def vc_compiler_path(host, target, vs_major_version, env, vs_release_name):
if host.kernel != 'WINNT':
return
vc_target = {
'x86': 'x86',
'x86_64': 'x64',
'arm': 'arm',
}.get(target.cpu)
if vc_target is None:
return
all_versions = sorted(get_vc_paths(env.topsrcdir), key=itemgetter(0))
if not all_versions:
return
if vs_major_version:
versions = [d for (v, d) in all_versions if v.major == vs_major_version]
if not versions:
die('Visual Studio %s could not be found!' % vs_release_name)
data = versions[0]
else:
# Choose the newest version.
data = all_versions[-1][1]
paths = data.get(vc_target)
if not paths:
return
return paths
@depends(vc_compiler_path)
@imports('os')
def toolchain_search_path(vc_compiler_path):
if vc_compiler_path:
result = [os.environ.get('PATH')]
result.extend(vc_compiler_path)
# We're going to alter PATH for good in windows.configure, but we also
# need to do it for the valid_compiler() check below.
os.environ['PATH'] = os.pathsep.join(result)
return result
@template
def default_c_compilers(host_or_target):
'''Template defining the set of default C compilers for the host and
target platforms.
`host_or_target` is either `host` or `target` (the @depends functions
from init.configure.
'''
assert host_or_target in (host, target)
@depends(host_or_target, target, toolchain_prefix)
def default_c_compilers(host_or_target, target, toolchain_prefix):
gcc = ('gcc',)
if toolchain_prefix and host_or_target is target:
gcc = tuple('%sgcc' % p for p in toolchain_prefix) + gcc
if host_or_target.kernel == 'WINNT':
return ('cl', 'clang-cl') + gcc + ('clang',)
if host_or_target.kernel == 'Darwin':
return ('clang',)
return gcc + ('clang',)
return default_c_compilers
@template
def default_cxx_compilers(c_compiler):
'''Template defining the set of default C++ compilers for the host and
target platforms.
`c_compiler` is the @depends function returning a Compiler instance for
the desired platform.
Because the build system expects the C and C++ compilers to be from the
same compiler suite, we derive the default C++ compilers from the C
compiler that was found if none was provided.
'''
@depends(c_compiler)
def default_cxx_compilers(c_compiler):
dir = os.path.dirname(c_compiler.compiler)
file = os.path.basename(c_compiler.compiler)
if c_compiler.type == 'gcc':
return (os.path.join(dir, file.replace('gcc', 'g++')),)
if c_compiler.type == 'clang':
return (os.path.join(dir, file.replace('clang', 'clang++')),)
return (c_compiler.compiler,)
return default_cxx_compilers
@template
def compiler(language, host_or_target, c_compiler=None, other_compiler=None,
other_c_compiler=None):
'''Template handling the generic base checks for the compiler for the
given `language` on the given platform (`host_or_target`).
`host_or_target` is either `host` or `target` (the @depends functions
from init.configure.
When the language is 'C++', `c_compiler` is the result of the `compiler`
template for the language 'C' for the same `host_or_target`.
When `host_or_target` is `host`, `other_compiler` is the result of the
`compiler` template for the same `language` for `target`.
When `host_or_target` is `host` and the language is 'C++',
`other_c_compiler` is the result of the `compiler` template for the
language 'C' for `target`.
'''
assert host_or_target in (host, target)
assert language in ('C', 'C++')
assert language == 'C' or c_compiler is not None
assert host_or_target == target or other_compiler is not None
assert language == 'C' or host_or_target == target or \
other_c_compiler is not None
host_or_target_str = {
host: 'host',
target: 'target',
}[host_or_target]
var = {
('C', target): 'CC',
('C++', target): 'CXX',
('C', host): 'HOST_CC',
('C++', host): 'HOST_CXX',
}[language, host_or_target]
default_compilers = {
'C': lambda: default_c_compilers(host_or_target),
'C++': lambda: default_cxx_compilers(c_compiler),
}[language]()
what='the %s %s compiler' % (host_or_target_str, language)
option(env=var, nargs=1, help='Path to %s' % what)
# Handle the compiler given by the user through one of the CC/CXX/HOST_CC/
# HOST_CXX variables.
@depends_if(var)
@imports(_from='itertools', _import='takewhile')
@imports(_from='mozbuild.shellutil', _import='split', _as='shell_split')
def provided_compiler(cmd):
# Historically, the compiler variables have contained more than the
# path to the compiler itself. So for backwards compatibility, try to
# find what is what in there, assuming the first dash-prefixed item is
# a compiler option, the item before that is the compiler, and anything
# before that is a compiler wrapper.
cmd = shell_split(cmd[0])
without_flags = list(takewhile(lambda x: not x.startswith('-'), cmd))
return namespace(
wrapper=without_flags[:-1],
compiler=without_flags[-1],
flags=cmd[len(without_flags):],
)
# Derive the host compiler from the corresponding target compiler when no
# explicit compiler was given and we're not cross compiling. For the C++
# compiler, though, prefer to derive from the host C compiler when it
# doesn't match the target C compiler.
# As a special case, since clang supports all kinds of targets in the same
# executable, when cross compiling with clang, default to the same compiler
# as the target compiler, resetting flags.
if host_or_target == host:
if other_c_compiler is not None:
args = (c_compiler, other_c_compiler)
else:
args = ()
@depends(provided_compiler, other_compiler, cross_compiling, *args)
def provided_compiler(value, other_compiler, cross_compiling, *args):
if value:
return value
c_compiler, other_c_compiler = args if args else (None, None)
if not cross_compiling and c_compiler == other_c_compiler:
return other_compiler
if cross_compiling and other_compiler.type == 'clang':
return namespace(**{
k: [] if k == 'flags' else v
for k, v in other_compiler.__dict__.iteritems()
})
# Normally, we'd use `var` instead of `_var`, but the interaction with
# old-configure complicates things, and for now, we a) can't take the plain
# result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
# old-configure AC_SUBST it (because it's autoconf doing it, not us)
compiler = check_prog('_%s' % var, what=what, progs=default_compilers,
input=provided_compiler.compiler,
paths=toolchain_search_path)
@depends(compiler, provided_compiler, compiler_wrapper, host_or_target)
@checking('whether %s can be used' % what, lambda x: bool(x))
@imports(_from='mozbuild.shellutil', _import='quote')
def valid_compiler(compiler, provided_compiler, compiler_wrapper,
host_or_target):
wrapper = list(compiler_wrapper or ())
if provided_compiler:
provided_wrapper = list(provided_compiler.wrapper)
# When doing a subconfigure, the compiler is set by old-configure
# and it contains the wrappers from --with-compiler-wrapper and
# --with-ccache.
if provided_wrapper[:len(wrapper)] == wrapper:
provided_wrapper = provided_wrapper[len(wrapper):]
wrapper.extend(provided_wrapper)
flags = provided_compiler.flags
else:
flags = []
# Ideally, we'd always use the absolute path, but unfortunately, on
# Windows, the compiler is very often in a directory containing spaces.
# Unfortunately, due to the way autoconf does its compiler tests with
# eval, that doesn't work out. So in that case, check that the
# compiler can still be found in $PATH, and use the file name instead
# of the full path.
if quote(compiler) != compiler:
full_path = os.path.abspath(compiler)
compiler = os.path.basename(compiler)
found_compiler = find_program(compiler)
if not found_compiler:
die('%s is not in your $PATH'
% quote(os.path.dirname(full_path)))
if os.path.normcase(find_program(compiler)) != os.path.normcase(
full_path):
die('Found `%s` before `%s` in your $PATH. '
'Please reorder your $PATH.',
quote(os.path.dirname(found_compiler)),
quote(os.path.dirname(full_path)))
info = check_compiler(wrapper + [compiler] + flags, language,
host_or_target)
# Check that the additional flags we got are enough to not require any
# more flags.
if info.flags:
flags += info.flags
info = check_compiler(wrapper + [compiler] + flags, language,
host_or_target)
if not info.target_cpu or info.target_cpu != host_or_target.cpu:
raise FatalCheckError(
'%s %s compiler target CPU (%s) does not match --%s CPU (%s)'
% (host_or_target_str.capitalize(), language,
info.target_cpu or 'unknown', host_or_target_str,
host_or_target.raw_cpu))
if not info.target_kernel or (info.target_kernel !=
host_or_target.kernel):
raise FatalCheckError(
'%s %s compiler target kernel (%s) does not match --%s kernel (%s)'
% (host_or_target_str.capitalize(), language,
info.target_kernel or 'unknown', host_or_target_str,
host_or_target.kernel))
if not info.target_endianness or (info.target_endianness !=
host_or_target.endianness):
raise FatalCheckError(
'%s %s compiler target endianness (%s) does not match --%s '
'endianness (%s)'
% (host_or_target_str.capitalize(), language,
info.target_endianness or 'unknown', host_or_target_str,
host_or_target.endianness))
if info.flags:
raise FatalCheckError(
'Unknown compiler or compiler not supported.')
# Compiler version checks
# ===================================================
# Check the compiler version here instead of in `compiler_version` so
# that the `checking` message doesn't pretend the compiler can be used
# to then bail out one line later.
if info.type == 'gcc' and info.version < '4.9.0':
raise FatalCheckError(
'Only GCC 4.9 or newer is supported (found version %s).'
% info.version)
# If you want to bump the version check here search for
# __cpp_static_assert above, and see the associated comment.
if info.type == 'clang' and not info.version:
raise FatalCheckError(
'Only clang/llvm 3.6 or newer is supported.')
if info.type == 'msvc':
if info.version < '19.00.24213':
raise FatalCheckError(
'This version (%s) of the MSVC compiler is not '
'supported.\n'
'You must install Visual C++ 2015 Update 3 or newer in '
'order to build.\n'
'See https://developer.mozilla.org/en/'
'Windows_Build_Prerequisites' % info.version)
return namespace(
wrapper=wrapper,
compiler=compiler,
flags=flags,
type=info.type,
version=info.version,
language=language,
)
@depends(valid_compiler)
@checking('%s version' % what)
def compiler_version(compiler):
return compiler.version
if language == 'C++':
@depends(valid_compiler, c_compiler)
def valid_compiler(compiler, c_compiler):
if compiler.type != c_compiler.type:
die('The %s C compiler is %s, while the %s C++ compiler is '
'%s. Need to use the same compiler suite.',
host_or_target_str, c_compiler.type,
host_or_target_str, compiler.type)
if compiler.version != c_compiler.version:
die('The %s C compiler is version %s, while the %s C++ '
'compiler is version %s. Need to use the same compiler '
'version.',
host_or_target_str, c_compiler.version,
host_or_target_str, compiler.version)
return compiler
# Set CC/CXX/HOST_CC/HOST_CXX for old-configure, which needs the wrapper
# and the flags that were part of the user input for those variables to
# be provided.
add_old_configure_assignment(var, depends_if(valid_compiler)(
lambda x: list(x.wrapper) + [x.compiler] + list(x.flags)))
# Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
# old-configure to do some of its still existing checks.
if language == 'C':
set_config(
'%s_TYPE' % var, valid_compiler.type)
add_old_configure_assignment(
'%s_TYPE' % var, valid_compiler.type)
add_old_configure_assignment(
'%s_VERSION' % var, valid_compiler.version)
valid_compiler = compiler_class(valid_compiler)
def compiler_error():
raise FatalCheckError('Failed compiling a simple %s source with %s'
% (language, what))
valid_compiler.try_compile(check_msg='%s works' % what,
onerror=compiler_error)
# Set CPP/CXXCPP for both the build system and old-configure. We don't
# need to check this works for preprocessing, because we already relied
# on $CC -E/$CXX -E doing preprocessing work to validate the compiler
# in the first place.
if host_or_target == target:
pp_var = {
'C': 'CPP',
'C++': 'CXXCPP',
}[language]
preprocessor = depends_if(valid_compiler)(
lambda x: list(x.wrapper) + [x.compiler, '-E'] + list(x.flags))
set_config(pp_var, preprocessor)
add_old_configure_assignment(pp_var, preprocessor)
if language == 'C':
linker_var = {
target: 'LD',
host: 'HOST_LD',
}[host_or_target]
@deprecated_option(env=linker_var, nargs=1)
def linker(value):
if value:
return value[0]
@depends(valid_compiler, linker)
def unused_linker(compiler, linker):
if linker and compiler.type != 'msvc':
log.warning('The value of %s is not used by this build system.'
% linker_var)
if host_or_target == target:
@depends(valid_compiler)
def is_msvc(compiler):
return compiler.type == 'msvc'
imply_option('LINK', linker, reason='LD', when=is_msvc)
return valid_compiler
c_compiler = compiler('C', target)
cxx_compiler = compiler('C++', target, c_compiler=c_compiler)
host_c_compiler = compiler('C', host, other_compiler=c_compiler)
host_cxx_compiler = compiler('C++', host, c_compiler=host_c_compiler,
other_compiler=cxx_compiler,
other_c_compiler=c_compiler)
# Generic compiler-based conditions.
non_msvc_compiler = depends(c_compiler)(lambda info: info.type != 'msvc')
building_with_gcc = depends(c_compiler)(lambda info: info.type == 'gcc')
@depends(c_compiler)
def msvs_version(info):
# clang-cl emulates the same version scheme as cl. And MSVS_VERSION needs to
# be set for GYP on Windows.
if info.type in ('clang-cl', 'msvc'):
if info.version >= '19.10':
return '2017'
elif info.version >= '19.00':
return '2015'
return ''
set_config('MSVS_VERSION', msvs_version)
include('compile-checks.configure')
@depends(have_64_bit,
try_compile(body='static_assert(sizeof(void *) == 8, "")',
check_msg='for 64-bit OS'))
def check_have_64_bit(have_64_bit, compiler_have_64_bit):
if have_64_bit != compiler_have_64_bit:
configure_error('The target compiler does not agree with configure '
'about the target bitness.')
option(env='BINDGEN_CFLAGS',
nargs=1,
help='Options bindgen should pass to the C/C++ parser')
@depends('BINDGEN_CFLAGS')
@checking('bindgen cflags', lambda s: s if s and s.strip() else 'no')
def bindgen_cflags(value):
if value and len(value):
# Reformat the env value for substitution into a toml list.
flags = value[0].split()
return ', '.join('"' + flag + '"' for flag in flags)
return ''
set_config('BINDGEN_CFLAGS', bindgen_cflags)
@depends(c_compiler)
def default_debug_flags(compiler_info):
# Debug info is ON by default.
if compiler_info.type in ('msvc', 'clang-cl'):
return '-Zi'
return '-g'
option(env='MOZ_DEBUG_FLAGS',
nargs=1,
help='Debug compiler flags')
imply_option('--enable-debug-symbols',
depends_if('--enable-debug')(lambda v: v))
js_option('--enable-debug-symbols',
nargs='?',
default=True,
help='Enable debug symbols using the given compiler flags')
set_config('MOZ_DEBUG_SYMBOLS',
depends_if('--enable-debug-symbols')(lambda _: True))
@depends('MOZ_DEBUG_FLAGS', '--enable-debug-symbols', default_debug_flags)
def debug_flags(env_debug_flags, enable_debug_flags, default_debug_flags):
# If MOZ_DEBUG_FLAGS is set, and --enable-debug-symbols is set to a value,
# --enable-debug-symbols takes precedence. Note, the value of
# --enable-debug-symbols may be implied by --enable-debug.
if len(enable_debug_flags):
return enable_debug_flags[0]
if env_debug_flags:
return env_debug_flags[0]
return default_debug_flags
set_config('MOZ_DEBUG_FLAGS', debug_flags)
add_old_configure_assignment('MOZ_DEBUG_FLAGS', debug_flags)
@depends(c_compiler)
def color_cflags(info):
# We could test compiling with flags. By why incur the overhead when
# color support should always be present in a specific toolchain
# version?
# Code for auto-adding this flag to compiler invocations needs to
# determine if an existing flag isn't already present. That is likely
# using exact string matching on the returned value. So if the return
# value changes to e.g. "<x>=always", exact string match may fail and
# multiple color flags could be added. So examine downstream consumers
# before adding flags to return values.
if info.type == 'gcc' and info.version >= '4.9.0':
return '-fdiagnostics-color'
elif info.type == 'clang':
return '-fcolor-diagnostics'
else:
return ''
set_config('COLOR_CFLAGS', color_cflags)
# Some standard library headers (notably bionic on Android) declare standard
# functions (e.g. getchar()) and also #define macros for those standard
# functions. libc++ deals with this by doing something like the following
# (explanatory comments added):
#
# #ifdef FUNC
# // Capture the definition of FUNC.
# inline _LIBCPP_INLINE_VISIBILITY int __libcpp_FUNC(...) { return FUNC(...); }
# #undef FUNC
# // Use a real inline definition.
# inline _LIBCPP_INLINE_VISIBILITY int FUNC(...) { return _libcpp_FUNC(...); }
# #endif
#
# _LIBCPP_INLINE_VISIBILITY is typically defined as:
#
# __attribute__((__visibility__("hidden"), __always_inline__))
#
# Unfortunately, this interacts badly with our system header wrappers, as the:
#
# #pragma GCC visibility push(default)
#
# that they do prior to including the actual system header is treated by the
# compiler as an explicit declaration of visibility on every function declared
# in the header. Therefore, when the libc++ code above is encountered, it is
# as though the compiler has effectively seen:
#
# int FUNC(...) __attribute__((__visibility__("default")));
# int FUNC(...) __attribute__((__visibility__("hidden")));
#
# and the compiler complains about the mismatched visibility declarations.
#
# However, libc++ will only define _LIBCPP_INLINE_VISIBILITY if there is no
# existing definition. We can therefore define it to the empty string (since
# we are properly managing visibility ourselves) and avoid this whole mess.
# Note that we don't need to do this with gcc, as libc++ detects gcc and
# effectively does the same thing we are doing here.
#
# _LIBCPP_ALWAYS_INLINE needs similar workarounds, since it too declares
# hidden visibility.
@depends(c_compiler, target)
def libcxx_override_visibility(c_compiler, target):
if c_compiler.type == 'clang' and target.os == 'Android':
return ''
set_define('_LIBCPP_INLINE_VISIBILITY', libcxx_override_visibility)
set_define('_LIBCPP_INLINE_VISIBILITY_EXCEPT_GCC49', libcxx_override_visibility)
set_define('_LIBCPP_ALWAYS_INLINE', libcxx_override_visibility)
set_define('_LIBCPP_ALWAYS_INLINE_EXCEPT_GCC49', libcxx_override_visibility)
@depends(target, check_build_environment)
def visibility_flags(target, env):
if target.os != 'WINNT':
if target.kernel == 'Darwin':
return ('-fvisibility=hidden', '-fvisibility-inlines-hidden')
return ('-I%s/system_wrappers' % os.path.join(env.dist),
'-include',
'%s/config/gcc_hidden.h' % env.topsrcdir)
@depends(target, visibility_flags)
def wrap_system_includes(target, visibility_flags):
if visibility_flags and target.kernel != 'Darwin':
return True
set_define('HAVE_VISIBILITY_HIDDEN_ATTRIBUTE',
depends(visibility_flags)(lambda v: bool(v) or None))
set_define('HAVE_VISIBILITY_ATTRIBUTE',
depends(visibility_flags)(lambda v: bool(v) or None))
set_config('WRAP_SYSTEM_INCLUDES', wrap_system_includes)
set_config('VISIBILITY_FLAGS', visibility_flags)
# We only want to include windows.configure when we are compiling on
# Windows, for Windows.
@depends(target, host)
def is_windows(target, host):
return host.kernel == 'WINNT' and target.kernel == 'WINNT'
include('windows.configure', when=is_windows)
# Shader Compiler for Windows (and MinGW Cross Compile)
# ==============================================================
fxc = check_prog('FXC', ('fxc.exe', 'fxc2.exe'), when=depends(target)
(lambda t: t.kernel == 'WINNT'))
wine = check_prog('WINE', ['wine'], when=depends(target, host)
(lambda t, h: t.kernel == 'WINNT' and h.kernel == 'Linux'))
# Security Hardening
# ==============================================================
option('--enable-hardening', env='MOZ_SECURITY_HARDENING',
help='Enables security hardening compiler options')
@depends('--enable-hardening', c_compiler)
def security_hardening_cflags(value, c_compiler):
if value and c_compiler.type in ['gcc', 'clang']:
return '-fstack-protector-strong'
add_old_configure_assignment('HARDENING_CFLAGS', security_hardening_cflags)
imply_option('--enable-pie', depends_if('--enable-hardening')(lambda v: v))
option(env='RUSTFLAGS',
nargs=1,
help='Rust compiler flags')
set_config('RUSTFLAGS', depends('RUSTFLAGS')(lambda flags: flags))
imply_option('--enable-release', mozilla_official)
imply_option('--enable-release', depends_if('MOZ_AUTOMATION')(lambda x: True))
js_option('--enable-release',
help='Build with more conservative, release engineering-oriented '
'options. This may slow down builds.')
@depends('--enable-release')
def developer_options(value):
if not value:
return True
add_old_configure_assignment('DEVELOPER_OPTIONS', developer_options)
# Linker detection
# ==============================================================
@depends(target)
def build_not_win_mac(target):
if target.kernel not in ('Darwin', 'WINNT'):
return True
option('--enable-gold',
env='MOZ_FORCE_GOLD',
help='Enable GNU Gold Linker when it is not already the default',
when=build_not_win_mac)
@depends('--enable-gold', c_compiler, developer_options, check_build_environment, when=build_not_win_mac)
@checking('for ld', lambda x: x.KIND)
@imports('os')
@imports('shutil')
def enable_gold(enable_gold_option, c_compiler, developer_options, build_env):
linker = None
# Used to check the kind of linker
version_check = ['-Wl,--version']
cmd_base = c_compiler.wrapper + [c_compiler.compiler] + c_compiler.flags
def resolve_gold():
# Try to force the usage of gold
targetDir = os.path.join(build_env.topobjdir, 'build', 'unix', 'gold')
gold_detection_arg = '-print-prog-name=ld.gold'
gold = check_cmd_output(c_compiler.compiler, gold_detection_arg).strip()
if not gold:
return
goldFullPath = find_program(gold)
if goldFullPath is None:
return
if os.path.exists(targetDir):
shutil.rmtree(targetDir)
os.makedirs(targetDir)
os.symlink(goldFullPath, os.path.join(targetDir, 'ld'))
linker = ['-B', targetDir]
cmd = cmd_base + linker + version_check
if 'GNU gold' in check_cmd_output(*cmd).decode('utf-8'):
# We have detected gold, will build with the -B workaround
return namespace(
KIND='gold',
LINKER_FLAG=linker,
)
else:
# The -B trick didn't work, removing the directory
shutil.rmtree(targetDir)
if enable_gold_option or developer_options:
result = resolve_gold()
if result:
return result
# gold is only required if --enable-gold is used.
elif enable_gold_option:
die('Could not find gold')
# Else fallthrough.
cmd = cmd_base + version_check
cmd_output = check_cmd_output(*cmd).decode('utf-8')
# using decode because ld can be localized and python will
# have problems with french accent for example
if 'GNU ld' in cmd_output:
# We are using the normal linker
return namespace(
KIND='bfd'
)
# Special case for Android. In the ndk, it is gold
if 'GNU gold' in cmd_output:
return namespace(
KIND='gold'
)
# For other platforms without gold or the GNU linker
return namespace(
KIND='other'
)
set_config('LD_IS_BFD', depends(enable_gold.KIND)(lambda x: x == 'bfd' or None))
set_config('LINKER_LDFLAGS', enable_gold.LINKER_FLAG)