diff options
Diffstat (limited to 'build/moz.configure')
-rw-r--r-- | build/moz.configure/android-ndk.configure | 150 | ||||
-rw-r--r-- | build/moz.configure/checks.configure | 144 | ||||
-rw-r--r-- | build/moz.configure/compile-checks.configure | 152 | ||||
-rw-r--r-- | build/moz.configure/compilers-util.configure | 62 | ||||
-rw-r--r-- | build/moz.configure/headers.configure | 93 | ||||
-rw-r--r-- | build/moz.configure/init.configure | 780 | ||||
-rw-r--r-- | build/moz.configure/java.configure | 62 | ||||
-rw-r--r-- | build/moz.configure/keyfiles.configure | 68 | ||||
-rw-r--r-- | build/moz.configure/memory.configure | 98 | ||||
-rw-r--r-- | build/moz.configure/old.configure | 425 | ||||
-rw-r--r-- | build/moz.configure/pkg.configure | 97 | ||||
-rw-r--r-- | build/moz.configure/rust.configure | 166 | ||||
-rw-r--r-- | build/moz.configure/toolchain.configure | 910 | ||||
-rw-r--r-- | build/moz.configure/util.configure | 440 | ||||
-rw-r--r-- | build/moz.configure/warnings.configure | 111 | ||||
-rw-r--r-- | build/moz.configure/windows.configure | 431 |
16 files changed, 4189 insertions, 0 deletions
diff --git a/build/moz.configure/android-ndk.configure b/build/moz.configure/android-ndk.configure new file mode 100644 index 000000000..6a0b30529 --- /dev/null +++ b/build/moz.configure/android-ndk.configure @@ -0,0 +1,150 @@ +# -*- 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/. + + +js_option('--with-android-ndk', nargs=1, + help='location where the Android NDK can be found') + +js_option('--with-android-toolchain', nargs=1, + help='location of the Android toolchain') + +js_option('--with-android-gnu-compiler-version', nargs=1, + help='GNU compiler version to use') + +min_android_version = dependable(lambda: '9') + +js_option('--with-android-version', + nargs=1, + help='android platform version', + default=min_android_version) + +@depends('--with-android-version', min_android_version) +@imports(_from='__builtin__', _import='ValueError') +def android_version(value, min_version): + if not value: + # Someone has passed --without-android-version. + die('--with-android-version cannot be disabled.') + + try: + version = int(value[0]) + except ValueError: + die('--with-android-version expects an integer value') + + if version < int(min_version): + die('--with-android-version must be at least %s (got %s)', + min_version, value[0]) + + return version + +add_old_configure_assignment('android_version', android_version) + +@depends('--with-android-ndk', build_project) +def ndk(value, build_project): + if build_project == 'mobile/android' and not value: + die('You must specify --with-android-ndk=/path/to/ndk when ' + 'building mobile/android') + if value: + return value[0] + +set_config('ANDROID_NDK', ndk) +add_old_configure_assignment('android_ndk', ndk) + +@depends(target, android_version, ndk) +@checking('for android platform directory') +@imports(_from='os.path', _import='isdir') +def android_platform(target, android_version, ndk): + if target.os != 'Android': + return + + if 'mips' in target.cpu: + target_dir_name = 'mips' + elif 'aarch64' == target.cpu: + target_dir_name = 'arm64' + else: + target_dir_name = target.cpu + + # Not all Android releases have their own platform release. We use + # the next lower platform version in these cases. + if android_version in (11, 10): + platform_version = 9 + elif android_version in (20, 22): + platform_version = android_version - 1 + else: + platform_version = android_version + + platform_dir = os.path.join(ndk, + 'platforms', + 'android-%s' % platform_version, + 'arch-%s' % target_dir_name) + + if not isdir(platform_dir): + die("Android platform directory not found. With the current " + "configuration, it should be in %s" % platform_dir) + + return platform_dir + +add_old_configure_assignment('android_platform', android_platform) + +@depends(android_platform) +def extra_toolchain_flags(platform_dir): + if not platform_dir: + return [] + return ['-idirafter', + os.path.join(platform_dir, 'usr', 'include')] + +@depends(target, host, ndk, '--with-android-toolchain', + '--with-android-gnu-compiler-version') +@checking('for the Android toolchain directory', lambda x: x or 'not found') +@imports(_from='os.path', _import='isdir') +@imports(_from='mozbuild.shellutil', _import='quote') +def android_toolchain(target, host, ndk, toolchain, gnu_compiler_version): + if not ndk: + return + if toolchain: + return toolchain[0] + else: + if target.cpu == 'arm' and target.endianness == 'little': + target_base = 'arm-linux-androideabi' + elif target.cpu == 'x86': + target_base = 'x86' + elif target.cpu == 'mips32' and target.endianness == 'little': + target_base = 'mipsel-linux-android' + elif target.cpu == 'aarch64' and target.endianness == 'little': + target_base = 'aarch64-linux-android' + else: + die('Target cpu is not supported.') + + toolchain_format = '%s/toolchains/%s-%s/prebuilt/%s-%s' + + for version in gnu_compiler_version or ['4.9', '4.8', '4.7']: + toolchain = toolchain_format % (ndk, target_base, version, + host.kernel.lower(), host.cpu) + log.debug('Trying %s' % quote(toolchain)) + if not isdir(toolchain) and host.cpu == 'x86_64': + toolchain = toolchain_format % (ndk, target_base, version, + host.kernel.lower(), 'x86') + log.debug('Trying %s' % quote(toolchain)) + if isdir(toolchain): + return toolchain + else: + if gnu_compiler_version: + die('Your --with-android-gnu-compiler-version may be wrong') + die('You have to specify --with-android-toolchain=' + '/path/to/ndk/toolchain.') + +set_config('ANDROID_TOOLCHAIN', android_toolchain) + +@depends(target, android_toolchain) +def android_toolchain_prefix(target, toolchain): + if toolchain: + if target.cpu == 'x86': + # Ideally, the --target should just have the right x86 variant + # in the first place. + return '%s/bin/i686-linux-android-' % toolchain + return '%s/bin/%s-' % (toolchain, target.toolchain) + +imply_option('--with-toolchain-prefix', android_toolchain_prefix, + reason='--with-android-ndk') diff --git a/build/moz.configure/checks.configure b/build/moz.configure/checks.configure new file mode 100644 index 000000000..8c2dbc0cc --- /dev/null +++ b/build/moz.configure/checks.configure @@ -0,0 +1,144 @@ +# -*- 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/. + +# Templates implementing some generic checks. +# ============================================================== + +# Declare some exceptions. This is cumbersome, but since we shouldn't need a +# lot of them, let's stack them all here. When adding a new one, put it in the +# _declare_exceptions template, and add it to the return statement. Then +# destructure in the assignment below the function declaration. +@template +@imports(_from='__builtin__', _import='Exception') +def _declare_exceptions(): + class FatalCheckError(Exception): + '''An exception to throw from a function decorated with @checking. + It will result in calling die() with the given message. + Debugging messages emitted from the decorated function will also be + printed out.''' + return (FatalCheckError,) + +(FatalCheckError,) = _declare_exceptions() + +del _declare_exceptions + +# Helper to display "checking" messages +# @checking('for foo') +# def foo(): +# return 'foo' +# is equivalent to: +# def foo(): +# log.info('checking for foo... ') +# ret = foo +# log.info(ret) +# return ret +# This can be combined with e.g. @depends: +# @depends(some_option) +# @checking('for something') +# def check(value): +# ... +# An optional callback can be given, that will be used to format the returned +# value when displaying it. +@template +def checking(what, callback=None): + def decorator(func): + def wrapped(*args, **kwargs): + log.info('checking %s... ', what) + with log.queue_debug(): + error, ret = None, None + try: + ret = func(*args, **kwargs) + except FatalCheckError as e: + error = e.message + display_ret = callback(ret) if callback else ret + if display_ret is True: + log.info('yes') + elif display_ret is False or display_ret is None: + log.info('no') + else: + log.info(display_ret) + if error is not None: + die(error) + return ret + return wrapped + return decorator + + +# Template to check for programs in $PATH. +# - `var` is the name of the variable that will be set with `set_config` when +# the program is found. +# - `progs` is a list (or tuple) of program names that will be searched for. +# It can also be a reference to a @depends function that returns such a +# list. If the list is empty and there is no input, the check is skipped. +# - `what` is a human readable description of what is being looked for. It +# defaults to the lowercase version of `var`. +# - `input` is a string reference to an existing option or a reference to a +# @depends function resolving to explicit input for the program check. +# The default is to create an option for the environment variable `var`. +# This argument allows to use a different kind of option (possibly using a +# configure flag), or doing some pre-processing with a @depends function. +# - `allow_missing` indicates whether not finding the program is an error. +# - `paths` is a list of paths or @depends function returning a list of paths +# that will cause the given path(s) to be searched rather than $PATH. Input +# paths may either be individual paths or delimited by os.pathsep, to allow +# passing $PATH (for example) as an element. +# +# The simplest form is: +# check_prog('PROG', ('a', 'b')) +# This will look for 'a' or 'b' in $PATH, and set_config PROG to the one +# it can find. If PROG is already set from the environment or command line, +# use that value instead. +@template +@imports(_from='mozbuild.shellutil', _import='quote') +def check_prog(var, progs, what=None, input=None, allow_missing=False, + paths=None): + if input: + # Wrap input with type checking and normalization. + @depends(input) + def input(value): + if not value: + return + if isinstance(value, str): + return (value,) + if isinstance(value, (tuple, list)) and len(value) == 1: + return value + configure_error('input must resolve to a tuple or a list with a ' + 'single element, or a string') + else: + option(env=var, nargs=1, + help='Path to %s' % (what or 'the %s program' % var.lower())) + input = var + what = what or var.lower() + + # Trick to make a @depends function out of an immediate value. + progs = dependable(progs) + paths = dependable(paths) + + @depends_if(input, progs, paths) + @checking('for %s' % what, lambda x: quote(x) if x else 'not found') + def check(value, progs, paths): + if progs is None: + progs = () + + if not isinstance(progs, (tuple, list)): + configure_error('progs must resolve to a list or tuple!') + + for prog in value or progs: + log.debug('%s: Trying %s', var.lower(), quote(prog)) + result = find_program(prog, paths) + if result: + return result + + if not allow_missing or value: + raise FatalCheckError('Cannot find %s' % what) + + @depends_if(check, progs) + def normalized_for_config(value, progs): + return ':' if value is None else value + + set_config(var, normalized_for_config) + + return check diff --git a/build/moz.configure/compile-checks.configure b/build/moz.configure/compile-checks.configure new file mode 100644 index 000000000..9f0ebcd61 --- /dev/null +++ b/build/moz.configure/compile-checks.configure @@ -0,0 +1,152 @@ +# -*- Mode: python; c-basic-offset: 4; 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/. + + +# Generates a test program and attempts to compile it. In case of failure, the +# resulting check will return None. If the test program succeeds, it will return +# the output of the test program. +# - `includes` are the includes (as file names) that will appear at the top of +# the generated test program. +# - `body` is the code that will appear in the main function of the generated +# test program. `return 0;` is appended to the function body automatically. +# - `language` is the language selection, so that the appropriate compiler is +# used. +# - `flags` are the flags to be passed to the compiler, in addition to `-c`. +# - `check_msg` is the message to be printed to accompany compiling the test +# program. +@template +def try_compile(includes=None, body='', language='C++', flags=None, check_msg=None, + when=None): + compiler = { + 'C': c_compiler, + 'C++': cxx_compiler, + }[language] + + return compiler.try_compile(includes, body, flags, check_msg, when=when) + + +# Checks for the presence of the given header on the target system by compiling +# a test program including that header. The return value of the template is a +# check function returning True if the header is present, and None if it is not. +# The value of this check function is also used to set a variable (with set_define) +# corresponding to the checked header. For instance, HAVE_MALLOC_H will be set in +# defines if check_header if called with 'malloc.h' as input and malloc.h is +# present on the target. +# - `header` is the header, as a file name, to check for. +# - `language` is the language selection, so that the appropriate compiler is +# used. +# - `flags` are the flags to be passed to the compiler, in addition to `-c`. +# - `includes` are additional includes, as file names, to appear before the +# header checked for. +# - `when` is a depends function that if present will make performing the check +# conditional on the value of that function. +@template +def check_header(header, language='C++', flags=None, includes=None, when=None): + when = when or always + + if includes: + includes = includes[:] + else: + includes = [] + includes.append(header) + + have_header = try_compile(includes=includes, language=language, flags=flags, + check_msg='for %s' % header, when=when) + header_var = 'HAVE_%s' % (header.upper() + .replace('-', '_') + .replace('/', '_') + .replace('.', '_')) + set_define(header_var, have_header) + return have_header + +# A convenience wrapper for check_header for checking multiple headers. +# returns an array of the resulting checks in order corresponding to the +# provided headers. +# - `headers` are the headers to be checked. +# - `kwargs` are keyword arguments passed verbatim to check_header. +@template +def check_headers(*headers, **kwargs): + checks = [] + for header in headers: + checks.append(check_header(header, **kwargs)) + return checks + + +@depends(c_compiler) +def warnings_cflags(c_compiler): + return [] + +@depends(cxx_compiler) +def warnings_cxxflags(cxx_compiler): + return [] + + +# Tests whether GCC or clang support the given warning flag, and if it is, +# add it to the list of warning flags for the build. +# - `warning` is the warning flag (e.g. -Wfoo) +# - `compiler` (optional) is the compiler to test against (c_compiler or +# cxx_compiler, from toolchain.configure). When omitted, both compilers +# are tested. +# - `when` (optional) is a @depends function or option name conditioning +# when the warning flag is wanted. +# - `check`, when not set, skips checking whether the flag is supported and +# adds it to the list of warning flags unconditionally. This is only meant +# for add_gcc_warning(). +@template +def check_and_add_gcc_warning(warning, compiler=None, when=None, check=True): + if compiler: + compilers = (compiler,) + else: + compilers = (c_compiler, cxx_compiler) + + when = when or always + + for c in compilers: + assert c in (c_compiler, cxx_compiler) + lang, warnings_flags = { + c_compiler: ('C', warnings_cflags), + cxx_compiler: ('C++', warnings_cxxflags), + }[c] + + # GCC and clang will fail if given an unknown warning option like + # -Wfoobar. But later versions won't fail if given an unknown negated + # warning option like -Wno-foobar. So when we are checking for support + # of a negated warning option, we actually test the positive form, but + # add the negated form to the flags variable. + if (warning.startswith('-Wno-') and + not warning.startswith('-Wno-error=')): + flags = ['-Werror', '-W' + warning[5:]] + elif warning.startswith('-Werror='): + flags = [warning] + else: + flags = ['-Werror', warning] + + @depends(c, when) + def result(c, when): + if when and c.type in ('clang', 'gcc'): + return True + + if check: + result = c.try_compile( + flags=flags, when=result, + check_msg='whether the %s compiler supports %s' % (lang, + warning)) + + @depends(result, warnings_flags) + def maybe_add_flag(result, warnings_flags): + if result: + warnings_flags.append(warning) + +# Add the given warning to the list of warning flags for the build. +# - `warning` is the warning flag (e.g. -Wfoo) +# - `compiler` (optional) is the compiler to add the flag for (c_compiler or +# cxx_compiler, from toolchain.configure). When omitted, the warning flag +# is added for both compilers. +# - `when` (optional) is a @depends function or option name conditioning +# when the warning flag is wanted. +@template +def add_gcc_warning(warning, compiler=None, when=None): + check_and_add_gcc_warning(warning, compiler, when, check=False) diff --git a/build/moz.configure/compilers-util.configure b/build/moz.configure/compilers-util.configure new file mode 100644 index 000000000..32271a010 --- /dev/null +++ b/build/moz.configure/compilers-util.configure @@ -0,0 +1,62 @@ +# -*- 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/. + +@template +@imports('textwrap') +@imports(_from='mozbuild.configure', _import='SandboxDependsFunction') +def compiler_class(compiler): + class Compiler(SandboxDependsFunction): + # Generates a test program and attempts to compile it. In case of + # failure, the resulting check will return None. If the test program + # succeeds, it will return the output of the test program. + # - `includes` are the includes (as file names) that will appear at the + # top of the generated test program. + # - `body` is the code that will appear in the main function of the + # generated test program. `return 0;` is appended to the function + # body automatically. + # - `flags` are the flags to be passed to the compiler, in addition to + # `-c`. + # - `check_msg` is the message to be printed to accompany compiling the + # test program. + def try_compile(self, includes=None, body='', flags=None, + check_msg=None, when=None, onerror=lambda: None): + includes = includes or [] + source_lines = ['#include <%s>' % f for f in includes] + source = '\n'.join(source_lines) + '\n' + source += textwrap.dedent('''\ + int + main(void) + { + %s + ; + return 0; + } + ''' % body) + + if check_msg: + def checking_fn(fn): + return checking(check_msg)(fn) + else: + def checking_fn(fn): + return fn + + @depends_when(self, dependable(flags), extra_toolchain_flags, when=when) + @checking_fn + def func(compiler, flags, extra_flags): + flags = flags or [] + flags += extra_flags or [] + flags.append('-c') + + if try_invoke_compiler( + compiler.wrapper + [compiler.compiler] + compiler.flags, + compiler.language, source, flags, + onerror=onerror) is not None: + return True + + return func + + compiler.__class__ = Compiler + return compiler diff --git a/build/moz.configure/headers.configure b/build/moz.configure/headers.configure new file mode 100644 index 000000000..52ffa2f89 --- /dev/null +++ b/build/moz.configure/headers.configure @@ -0,0 +1,93 @@ +# -*- Mode: python; c-basic-offset: 4; 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/. + +# Check for headers defining standard int types. +check_header('stdint.h') +have_inttypes = check_header('inttypes.h') + +# Assume we have ansi C header files available. +set_define('STDC_HEADERS', True) + +set_config('HAVE_INTTYPES_H', have_inttypes) + +building_linux = depends(target)(lambda target: target.kernel == 'Linux') + +have_malloc = check_header('malloc.h') + +check_header('alloca.h') + +add_old_configure_assignment('HAVE_MALLOC_H', have_malloc) + +check_headers( + 'sys/byteorder.h', + 'getopt.h', + 'unistd.h', + 'nl_types.h', + 'cpuid.h', + when=non_msvc_compiler, +) + +# These are all the places some variant of statfs can be hiding. +check_headers( + 'sys/statvfs.h', + 'sys/statfs.h', + 'sys/vfs.h', + 'sys/mount.h', + when=non_msvc_compiler, +) + +# Quota support +check_header('sys/quota.h', + when=non_msvc_compiler) +check_header('linux/quota.h', + includes=['sys/socket.h'], + when=building_linux) + +# SCTP support - needs various network include headers +check_headers( + 'linux/if_addr.h', + 'linux/rtnetlink.h', + includes=['sys/socket.h'], + when=building_linux, +) + +check_header('sys/queue.h', + when=non_msvc_compiler) + +check_headers( + 'sys/types.h', + 'netinet/in.h', + 'byteswap.h', + when=non_msvc_compiler, +) + +# TODO: Move these checks to file specific to --enable-project=js. +have_perf_event_h = check_header('linux/perf_event.h', + when=building_linux) + +js_option('--with-linux-headers', + help='location where the Linux kernel headers can be found', + nargs=1) + +passed_linux_header_flags = depends_if('--with-linux-headers')(lambda v: ['-I%s' % v[0]]) + +@depends_when(try_compile(includes=['asm/unistd.h'], + body='return sizeof(__NR_perf_event_open);', + flags=passed_linux_header_flags, + check_msg='for perf_event_open system call'), + when=have_perf_event_h) +def have_perf_event_open(have_perf_event_open): + if have_perf_event_open: + return True + +set_config('HAVE_LINUX_PERF_EVENT_H', have_perf_event_open) + +@depends(passed_linux_header_flags, have_perf_event_open) +def linux_headers_includes(passed_linux_header_flags, have_perf_event_open): + if have_perf_event_open and passed_linux_header_flags: + return passed_linux_header_flags[0] + +set_config('LINUX_HEADERS_INCLUDES', linux_headers_includes) diff --git a/build/moz.configure/init.configure b/build/moz.configure/init.configure new file mode 100644 index 000000000..2123bebc9 --- /dev/null +++ b/build/moz.configure/init.configure @@ -0,0 +1,780 @@ +# -*- 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/. + +include('util.configure') +include('checks.configure') + +option(env='DIST', nargs=1, help='DIST directory') + +# Do not allow objdir == srcdir builds. +# ============================================================== +@depends('--help', 'DIST') +@imports(_from='os.path', _import='exists') +def check_build_environment(help, dist): + topobjdir = os.path.realpath(os.path.abspath('.')) + topsrcdir = os.path.realpath(os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..'))) + + if dist: + dist = normsep(dist[0]) + else: + dist = os.path.join(topobjdir, 'dist') + + result = namespace( + topsrcdir=topsrcdir, + topobjdir=topobjdir, + dist=dist, + ) + + if help: + return result + + if topsrcdir == topobjdir: + die(' ***\n' + ' * Building directly in the main source directory is not allowed.\n' + ' *\n' + ' * To build, you must run configure from a separate directory\n' + ' * (referred to as an object directory).\n' + ' *\n' + ' * If you are building with a mozconfig, you will need to change your\n' + ' * mozconfig to point to a different object directory.\n' + ' ***' + ) + + # Check for a couple representative files in the source tree + conflict_files = [ + '* %s' % f for f in ('Makefile', 'config/autoconf.mk') + if exists(os.path.join(topsrcdir, f)) + ] + if conflict_files: + die(' ***\n' + ' * Your source tree contains these files:\n' + ' %s\n' + ' * This indicates that you previously built in the source tree.\n' + ' * A source tree build can confuse the separate objdir build.\n' + ' *\n' + ' * To clean up the source tree:\n' + ' * 1. cd %s\n' + ' * 2. gmake distclean\n' + ' ***' + % ('\n '.join(conflict_files), topsrcdir) + ) + + return result + +set_config('TOPSRCDIR', delayed_getattr(check_build_environment, 'topsrcdir')) +set_config('TOPOBJDIR', delayed_getattr(check_build_environment, 'topobjdir')) +set_config('MOZ_BUILD_ROOT', delayed_getattr(check_build_environment, + 'topobjdir')) +set_config('DIST', delayed_getattr(check_build_environment, 'dist')) + + +option(env='MOZ_AUTOMATION', help='Enable options for automated builds') +set_config('MOZ_AUTOMATION', depends_if('MOZ_AUTOMATION')(lambda x: True)) + + +option(env='OLD_CONFIGURE', nargs=1, help='Path to the old configure script') + +option(env='MOZ_CURRENT_PROJECT', nargs=1, help='Current build project') +option(env='MOZCONFIG', nargs=1, help='Mozconfig location') + +# Read user mozconfig +# ============================================================== +# Note: the dependency on --help is only there to always read the mozconfig, +# even when --help is passed. Without this dependency, the function wouldn't +# be called when --help is passed, and the mozconfig wouldn't be read. +@depends('MOZ_CURRENT_PROJECT', 'MOZCONFIG', 'OLD_CONFIGURE', + check_build_environment, '--help') +@imports(_from='mozbuild.mozconfig', _import='MozconfigLoader') +def mozconfig(current_project, mozconfig, old_configure, build_env, help): + if not old_configure: + die('The OLD_CONFIGURE environment variable must be set') + + # Don't read the mozconfig for the js configure (yay backwards + # compatibility) + # While the long term goal is that js and top-level use the same configure + # and the same overall setup, including the possibility to use mozconfigs, + # figuring out what we want to do wrt mozconfig vs. command line and + # environment variable is not a clear-cut case, and it's more important to + # fix the immediate problem mozconfig causes to js developers by + # "temporarily" returning to the previous behavior of not loading the + # mozconfig for the js configure. + # Separately to the immediate problem for js developers, there is also the + # need to not load a mozconfig when running js configure as a subconfigure. + # Unfortunately, there is no direct way to tell whether the running + # configure is the js configure. The indirect way is to look at the + # OLD_CONFIGURE path, which points to js/src/old-configure. + # I expect we'll have figured things out for mozconfigs well before + # old-configure dies. + if os.path.dirname(os.path.abspath(old_configure[0])).endswith('/js/src'): + return {'path': None} + + loader = MozconfigLoader(build_env.topsrcdir) + current_project = current_project[0] if current_project else None + mozconfig = mozconfig[0] if mozconfig else None + mozconfig = loader.find_mozconfig(env={'MOZCONFIG': mozconfig}) + mozconfig = loader.read_mozconfig(mozconfig, moz_build_app=current_project) + + return mozconfig + +set_config('MOZCONFIG', depends(mozconfig)(lambda m: m['path'])) + + +option(env='PYTHON', nargs=1, help='Python interpreter') + +# Setup python virtualenv +# ============================================================== +@depends('PYTHON', check_build_environment, mozconfig, '--help') +@imports('os') +@imports('sys') +@imports('subprocess') +@imports(_from='mozbuild.configure.util', _import='LineIO') +@imports(_from='mozbuild.virtualenv', _import='VirtualenvManager') +@imports(_from='mozbuild.virtualenv', _import='verify_python_version') +@imports('distutils.sysconfig') +def virtualenv_python(env_python, build_env, mozconfig, help): + if help: + return + + python = env_python[0] if env_python else None + + # Ideally we'd rely on the mozconfig injection from mozconfig_options, + # but we'd rather avoid the verbosity when we need to reexecute with + # a different python. + if mozconfig['path']: + if 'PYTHON' in mozconfig['env']['added']: + python = mozconfig['env']['added']['PYTHON'] + elif 'PYTHON' in mozconfig['env']['modified']: + python = mozconfig['env']['modified']['PYTHON'][1] + elif 'PYTHON' in mozconfig['vars']['added']: + python = mozconfig['vars']['added']['PYTHON'] + elif 'PYTHON' in mozconfig['vars']['modified']: + python = mozconfig['vars']['modified']['PYTHON'][1] + + with LineIO(lambda l: log.error(l)) as out: + verify_python_version(out) + topsrcdir, topobjdir = build_env.topsrcdir, build_env.topobjdir + if topobjdir.endswith('/js/src'): + topobjdir = topobjdir[:-7] + + with LineIO(lambda l: log.info(l)) as out: + manager = VirtualenvManager( + topsrcdir, topobjdir, + os.path.join(topobjdir, '_virtualenv'), out, + os.path.join(topsrcdir, 'build', 'virtualenv_packages.txt')) + + if python: + # If we're not in the virtualenv, we need the which module for + # find_program. + if normsep(sys.executable) != normsep(manager.python_path): + sys.path.append(os.path.join(topsrcdir, 'python', 'which')) + found_python = find_program(python) + if not found_python: + die('The PYTHON environment variable does not contain ' + 'a valid path. Cannot find %s', python) + python = found_python + else: + python = sys.executable + + if not manager.up_to_date(python): + log.info('Creating Python environment') + manager.build(python) + + python = normsep(manager.python_path) + + if python != normsep(sys.executable): + log.info('Reexecuting in the virtualenv') + if env_python: + del os.environ['PYTHON'] + # One would prefer to use os.execl, but that's completely borked on + # Windows. + sys.exit(subprocess.call([python] + sys.argv)) + + # We are now in the virtualenv + if not distutils.sysconfig.get_python_lib(): + die('Could not determine python site packages directory') + + return python + +set_config('PYTHON', virtualenv_python) +add_old_configure_assignment('PYTHON', virtualenv_python) + +# Inject mozconfig options +# ============================================================== +# All options defined above this point can't be injected in mozconfig_options +# below, so collect them. +@template +def early_options(): + @dependable + @imports('__sandbox__') + def early_options(): + return set( + option.env + for option in __sandbox__._options.itervalues() + if option.env + ) + return early_options + +early_options = early_options() + +@depends(mozconfig, '--help') +# This gives access to the sandbox. Don't copy this blindly. +@imports('__sandbox__') +@imports('os') +def mozconfig_options(mozconfig, help): + if mozconfig['path']: + helper = __sandbox__._helper + log.info('Adding configure options from %s' % mozconfig['path']) + for arg in mozconfig['configure_args']: + log.info(' %s' % arg) + # We could be using imply_option() here, but it has other + # contraints that don't really apply to the command-line + # emulation that mozconfig provides. + helper.add(arg, origin='mozconfig', args=helper._args) + + def add(key, value): + if key.isupper(): + arg = '%s=%s' % (key, value) + log.info(' %s' % arg) + helper.add(arg, origin='mozconfig', args=helper._args) + + for key, value in mozconfig['env']['added'].iteritems(): + add(key, value) + os.environ[key] = value + for key, (_, value) in mozconfig['env']['modified'].iteritems(): + add(key, value) + os.environ[key] = value + for key, value in mozconfig['vars']['added'].iteritems(): + # mozconfig_loader adds _IS_SET variables that are irrelevant + if not key.endswith('_IS_SET'): + add(key, value) + for key, (_, value) in mozconfig['vars']['modified'].iteritems(): + add(key, value) + + +# Mozilla-Build +# ============================================================== +option(env='MOZILLABUILD', nargs=1, + help='Path to Mozilla Build (Windows-only)') + +option(env='CONFIG_SHELL', nargs=1, help='Path to a POSIX shell') + +# It feels dirty replicating this from python/mozbuild/mozbuild/mozconfig.py, +# but the end goal being that the configure script would go away... +@depends('CONFIG_SHELL', 'MOZILLABUILD') +@checking('for a shell') +@imports('sys') +def shell(value, mozillabuild): + if value: + return find_program(value[0]) + shell = 'sh' + if mozillabuild: + shell = mozillabuild[0] + '/msys/bin/sh' + if sys.platform == 'win32': + shell = shell + '.exe' + return find_program(shell) + + +# Host and target systems +# ============================================================== +option('--host', nargs=1, help='Define the system type performing the build') + +option('--target', nargs=1, + help='Define the system type where the resulting executables will be ' + 'used') + +@imports(_from='mozbuild.configure.constants', _import='CPU') +@imports(_from='mozbuild.configure.constants', _import='CPU_bitness') +@imports(_from='mozbuild.configure.constants', _import='Endianness') +@imports(_from='mozbuild.configure.constants', _import='Kernel') +@imports(_from='mozbuild.configure.constants', _import='OS') +def split_triplet(triplet): + # The standard triplet is defined as + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM + # There is also a quartet form: + # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM + # But we can consider the "KERNEL-OPERATING_SYSTEM" as one. + cpu, manufacturer, os = triplet.split('-', 2) + + # Autoconf uses config.sub to validate and canonicalize those triplets, + # but the granularity of its results has never been satisfying to our + # use, so we've had our own, different, canonicalization. We've also + # historically not been very consistent with how we use the canonicalized + # values. Hopefully, this will help us make things better. + # The tests are inherited from our decades-old autoconf-based configure, + # which can probably be improved/cleaned up because they are based on a + # mix of uname and config.guess output, while we now only use the latter, + # which presumably has a cleaner and leaner output. Let's refine later. + os = os.replace('/', '_') + if 'android' in os: + canonical_os = 'Android' + canonical_kernel = 'Linux' + elif os.startswith('linux'): + canonical_os = 'GNU' + canonical_kernel = 'Linux' + elif os.startswith('kfreebsd') and os.endswith('-gnu'): + canonical_os = 'GNU' + canonical_kernel = 'kFreeBSD' + elif os.startswith('gnu'): + canonical_os = canonical_kernel = 'GNU' + elif os.startswith('mingw'): + canonical_os = canonical_kernel = 'WINNT' + elif os.startswith('darwin'): + canonical_kernel = 'Darwin' + canonical_os = 'OSX' + elif os.startswith('ios'): + canonical_kernel = 'Darwin' + canonical_os = 'iOS' + elif os.startswith('dragonfly'): + canonical_os = canonical_kernel = 'DragonFly' + elif os.startswith('freebsd'): + canonical_os = canonical_kernel = 'FreeBSD' + elif os.startswith('netbsd'): + canonical_os = canonical_kernel = 'NetBSD' + elif os.startswith('openbsd'): + canonical_os = canonical_kernel = 'OpenBSD' + else: + die('Unknown OS: %s' % os) + + # The CPU granularity is probably not enough. Moving more things from + # old-configure will tell us if we need more + if cpu.endswith('86') or (cpu.startswith('i') and '86' in cpu): + canonical_cpu = 'x86' + endianness = 'little' + elif cpu in ('x86_64', 'ia64'): + canonical_cpu = cpu + endianness = 'little' + elif cpu in ('s390', 's390x'): + canonical_cpu = cpu + endianness = 'big' + elif cpu in ('powerpc64', 'ppc64', 'powerpc64le', 'ppc64le'): + canonical_cpu = 'ppc64' + endianness = 'little' if 'le' in cpu else 'big' + elif cpu in ('powerpc', 'ppc', 'rs6000') or cpu.startswith('powerpc'): + canonical_cpu = 'ppc' + endianness = 'big' + elif cpu in ('Alpha', 'alpha', 'ALPHA'): + canonical_cpu = 'Alpha' + endianness = 'little' + elif cpu.startswith('hppa') or cpu == 'parisc': + canonical_cpu = 'hppa' + endianness = 'big' + elif cpu.startswith('sparc64'): + canonical_cpu = 'sparc64' + endianness = 'big' + elif cpu.startswith('sparc') or cpu == 'sun4u': + canonical_cpu = 'sparc' + endianness = 'big' + elif cpu.startswith('arm'): + canonical_cpu = 'arm' + endianness = 'big' if cpu.startswith(('armeb', 'armbe')) else 'little' + elif cpu in ('mips', 'mipsel'): + canonical_cpu = 'mips32' + endianness = 'little' if 'el' in cpu else 'big' + elif cpu in ('mips64', 'mips64el'): + canonical_cpu = 'mips64' + endianness = 'little' if 'el' in cpu else 'big' + elif cpu.startswith('aarch64'): + canonical_cpu = 'aarch64' + endianness = 'little' + else: + die('Unknown CPU type: %s' % cpu) + + return namespace( + alias=triplet, + cpu=CPU(canonical_cpu), + bitness=CPU_bitness[canonical_cpu], + kernel=Kernel(canonical_kernel), + os=OS(canonical_os), + endianness=Endianness(endianness), + raw_cpu=cpu, + raw_os=os, + # Toolchains, most notably for cross compilation may use cpu-os + # prefixes. + toolchain='%s-%s' % (cpu, os), + ) + + +@imports('subprocess') +def config_sub(shell, triplet): + config_sub = os.path.join(os.path.dirname(__file__), '..', + 'autoconf', 'config.sub') + return subprocess.check_output([shell, config_sub, triplet]).strip() + + +@depends('--host', shell) +@checking('for host system type', lambda h: h.alias) +@imports('subprocess') +def host(value, shell): + if not value: + config_guess = os.path.join(os.path.dirname(__file__), '..', + 'autoconf', 'config.guess') + host = subprocess.check_output([shell, config_guess]).strip() + else: + host = value[0] + + return split_triplet(config_sub(shell, host)) + + +@depends('--target', host, shell) +@checking('for target system type', lambda t: t.alias) +def target(value, host, shell): + if not value: + return host + return split_triplet(config_sub(shell, value[0])) + + +@depends(host, target) +@checking('whether cross compiling') +def cross_compiling(host, target): + return host != target + +set_config('CROSS_COMPILE', cross_compiling) +set_define('CROSS_COMPILE', cross_compiling) +add_old_configure_assignment('CROSS_COMPILE', cross_compiling) + + +@depends(target) +def have_64_bit(target): + if target.bitness == 64: + return True + +set_config('HAVE_64BIT_BUILD', have_64_bit) +set_define('HAVE_64BIT_BUILD', have_64_bit) +add_old_configure_assignment('HAVE_64BIT_BUILD', have_64_bit) + + +# Autoconf needs these set +@depends(host) +def host_for_old_configure(host): + return '--host=%s' % host.alias + +add_old_configure_arg(host_for_old_configure) + +@depends(host, target) +def target_for_old_configure(host, target): + target_alias = target.alias + # old-configure does plenty of tests against $target and $target_os + # and expects darwin for iOS, so make it happy. + if target.os == 'iOS': + target_alias = target_alias.replace('-ios', '-darwin') + return '--target=%s' % target_alias + +add_old_configure_arg(target_for_old_configure) + + +# These variables are for compatibility with the current moz.builds and +# old-configure. Eventually, we'll want to canonicalize better. +@depends(target) +def target_variables(target): + if target.kernel == 'kFreeBSD': + os_target = 'GNU/kFreeBSD' + os_arch = 'GNU_kFreeBSD' + elif target.kernel == 'Darwin' or (target.kernel == 'Linux' and + target.os == 'GNU'): + os_target = target.kernel + os_arch = target.kernel + else: + os_target = target.os + os_arch = target.kernel + + if target.kernel == 'Darwin' and target.cpu == 'x86': + os_test = 'i386' + else: + os_test = target.raw_cpu + + return namespace( + OS_TARGET=os_target, + OS_ARCH=os_arch, + OS_TEST=os_test, + INTEL_ARCHITECTURE=target.cpu in ('x86', 'x86_64') or None, + ) + +set_config('OS_TARGET', delayed_getattr(target_variables, 'OS_TARGET')) +add_old_configure_assignment('OS_TARGET', + delayed_getattr(target_variables, 'OS_TARGET')) +set_config('OS_ARCH', delayed_getattr(target_variables, 'OS_ARCH')) +add_old_configure_assignment('OS_ARCH', + delayed_getattr(target_variables, 'OS_ARCH')) +set_config('OS_TEST', delayed_getattr(target_variables, 'OS_TEST')) +add_old_configure_assignment('OS_TEST', + delayed_getattr(target_variables, 'OS_TEST')) +set_config('CPU_ARCH', delayed_getattr(target, 'cpu')) +add_old_configure_assignment('CPU_ARCH', delayed_getattr(target, 'cpu')) +set_config('INTEL_ARCHITECTURE', delayed_getattr(target_variables, + 'INTEL_ARCHITECTURE')) +set_config('TARGET_CPU', delayed_getattr(target, 'raw_cpu')) +set_config('TARGET_OS', delayed_getattr(target, 'raw_os')) + + +@depends(host) +def host_variables(host): + if host.kernel == 'kFreeBSD': + os_arch = 'GNU_kFreeBSD' + else: + os_arch = host.kernel + return namespace( + HOST_OS_ARCH=os_arch, + ) + +set_config('HOST_OS_ARCH', delayed_getattr(host_variables, 'HOST_OS_ARCH')) +add_old_configure_assignment('HOST_OS_ARCH', + delayed_getattr(host_variables, 'HOST_OS_ARCH')) + +@depends(target) +def target_is_windows(target): + if target.kernel == 'WINNT': + return True + +set_define('_WINDOWS', target_is_windows) +set_define('WIN32', target_is_windows) +set_define('XP_WIN', target_is_windows) +set_define('XP_WIN32', target_is_windows) + +@depends(target) +def target_is_unix(target): + if target.kernel != 'WINNT': + return True + +set_define('XP_UNIX', target_is_unix) + +@depends(target) +def target_is_darwin(target): + if target.kernel == 'Darwin': + return True + +set_define('XP_DARWIN', target_is_darwin) + +@depends(target) +def target_is_ios(target): + if target.kernel == 'Darwin' and target.os == 'iOS': + return True + +set_define('XP_IOS', target_is_ios) + +@depends(target) +def target_is_osx(target): + if target.kernel == 'Darwin' and target.os == 'OSX': + return True + +set_define('XP_MACOSX', target_is_osx) + +@depends(target) +def target_is_linux(target): + if target.kernel == 'Linux': + return True + +set_define('XP_LINUX', target_is_linux) + +# The application/project to build +# ============================================================== +option('--enable-application', nargs=1, env='MOZ_BUILD_APP', + help='Application to build. Same as --enable-project.') + +@depends('--enable-application', '--help') +def application(app, help): + if app: + return app + +imply_option('--enable-project', application) + + +@depends(check_build_environment, '--help') +def default_project(build_env, help): + if build_env.topobjdir.endswith('/js/src'): + return 'js' + return 'browser' + +option('--enable-project', nargs=1, default=default_project, + help='Project to build') + +option('--with-external-source-dir', env='EXTERNAL_SOURCE_DIR', nargs=1, + help='External directory containing additional build files') + +@depends('--enable-project', '--with-external-source-dir', + check_build_environment, '--help') +@imports(_from='os.path', _import='exists') +def include_project_configure(project, external_source_dir, build_env, help): + if not project: + die('--enable-project is required.') + + base_dir = build_env.topsrcdir + if external_source_dir: + base_dir = os.path.join(base_dir, external_source_dir[0]) + + path = os.path.join(base_dir, project[0], 'moz.configure') + if not exists(path): + die('Cannot find project %s', project[0]) + return path + +@depends('--with-external-source-dir') +def external_source_dir(value): + if value: + return value[0] + +set_config('EXTERNAL_SOURCE_DIR', external_source_dir) +add_old_configure_assignment('EXTERNAL_SOURCE_DIR', external_source_dir) + + +@depends(include_project_configure, check_build_environment, '--help') +def build_project(include_project_configure, build_env, help): + ret = os.path.dirname(os.path.relpath(include_project_configure, + build_env.topsrcdir)) + return ret + +set_config('MOZ_BUILD_APP', build_project) +set_define('MOZ_BUILD_APP', build_project) +add_old_configure_assignment('MOZ_BUILD_APP', build_project) + + +# set RELEASE_OR_BETA and NIGHTLY_BUILD variables depending on the cycle we're in +# The logic works like this: +# - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD) +# - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora +# - otherwise, we're building Release/Beta (define RELEASE_OR_BETA) +@depends(check_build_environment, '--help') +@imports(_from='__builtin__', _import='open') +def milestone(build_env, _): + milestone_path = os.path.join(build_env.topsrcdir, + 'config', + 'milestone.txt') + with open(milestone_path, 'r') as fh: + milestone = fh.read().splitlines()[-1] + + is_nightly = is_release_or_beta = None + + if 'a1' in milestone: + is_nightly = True + elif 'a' not in milestone: + is_release_or_beta = True + + return namespace(version=milestone, + is_nightly=is_nightly, + is_release_or_beta=is_release_or_beta) + +set_config('GRE_MILESTONE', delayed_getattr(milestone, 'version')) +set_config('NIGHTLY_BUILD', delayed_getattr(milestone, 'is_nightly')) +set_define('NIGHTLY_BUILD', delayed_getattr(milestone, 'is_nightly')) +add_old_configure_assignment('NIGHTLY_BUILD', + delayed_getattr(milestone, 'is_nightly')) +set_config('RELEASE_OR_BETA', delayed_getattr(milestone, 'is_release_or_beta')) +set_define('RELEASE_OR_BETA', delayed_getattr(milestone, 'is_release_or_beta')) +add_old_configure_assignment('RELEASE_OR_BETA', + delayed_getattr(milestone, 'is_release_or_beta')) + +# The app update channel is 'default' when not supplied. The value is used in +# the application's confvars.sh (and is made available to a project specific +# moz.configure). +option('--enable-update-channel', + nargs=1, + help='Select application update channel', + default='default') + +@depends('--enable-update-channel') +def update_channel(channel): + if channel[0] == '': + return 'default' + return channel[0].lower() + +set_config('MOZ_UPDATE_CHANNEL', update_channel) +set_define('MOZ_UPDATE_CHANNEL', update_channel) +add_old_configure_assignment('MOZ_UPDATE_CHANNEL', update_channel) + + +# A template providing a shorthand for setting a variable. The created +# option will only be settable with imply_option. +# It is expected that a project-specific moz.configure will call imply_option +# to set a value other than the default. +# If required, the set_as_define and set_for_old_configure arguments +# will additionally cause the variable to be set using set_define and +# add_old_configure_assignment. util.configure would be an appropriate place for +# this, but it uses add_old_configure_assignment, which is defined in this file. +@template +def project_flag(env=None, set_for_old_configure=False, + set_as_define=False, **kwargs): + + if not env: + configure_error("A project_flag must be passed a variable name to set.") + + opt = option(env=env, possible_origins=('implied',), **kwargs) + + @depends(opt.option) + def option_implementation(value): + if value: + if len(value): + return value + return bool(value) + + set_config(env, option_implementation) + if set_as_define: + set_define(env, option_implementation) + if set_for_old_configure: + add_old_configure_assignment(env, option_implementation) + +# milestone.is_nightly corresponds to cases NIGHTLY_BUILD is set. +@depends(milestone, '--help') +def enabled_in_nightly(milestone, _): + return milestone.is_nightly + +# Set the MOZ_CONFIGURE_OPTIONS variable with all the options that +# were passed somehow (environment, command line, mozconfig) +@depends(mozconfig_options) +@imports(_from='mozbuild.shellutil', _import='quote') +@imports('__sandbox__') +def all_configure_options(_): + result = [] + previous = None + for option in __sandbox__._options.itervalues(): + # __sandbox__._options contains items for both option.name and + # option.env. But it's also an OrderedDict, meaning both are + # consecutive. + # Also ignore OLD_CONFIGURE and MOZCONFIG because they're not + # interesting. + if option == previous or option.env in ('OLD_CONFIGURE', 'MOZCONFIG'): + continue + previous = option + value = __sandbox__._value_for(option) + # We only want options that were explicitly given on the command + # line, the environment, or mozconfig, and that differ from the + # defaults. + if (value is not None and value.origin not in ('default', 'implied') and + value != option.default): + result.append(__sandbox__._raw_options[option]) + # We however always include options that are sent to old configure + # because we don't know their actual defaults. (Keep the conditions + # separate for ease of understanding and ease of removal) + elif (option.help == 'Help missing for old configure options' and + option in __sandbox__._raw_options): + result.append(__sandbox__._raw_options[option]) + + return quote(*result) + +set_config('MOZ_CONFIGURE_OPTIONS', all_configure_options) + + +# This is temporary until js/src/configure and configure are merged. +# Use instead of option() in js/moz.configure and more generally, for +# options that are shared between configure and js/src/configure. +@template +def js_option(*args, **kwargs): + opt = option(*args, **kwargs) + + @depends(opt.option, build_project) + def js_option(value, build_project): + if build_project != 'js': + return value.format(opt.option) + + add_old_configure_arg(js_option) + + +# Bug 1278542: This function is a workaround to resolve +# |android_ndk_include|'s dependency on 'gonkdir.' The +# actual implementation is located in b2g/moz.configure. +# Remove this function as soon as 'android_ndk_include' +# depends on 'target.' +@depends('--help') +def gonkdir(_): + return None diff --git a/build/moz.configure/java.configure b/build/moz.configure/java.configure new file mode 100644 index 000000000..459bd44cf --- /dev/null +++ b/build/moz.configure/java.configure @@ -0,0 +1,62 @@ +# -*- 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/. + + +# Java detection +# ======================================================== +option('--with-java-bin-path', nargs=1, + help='Location of Java binaries (java, javac, jar)') + +@depends('--with-java-bin-path') +@imports(_from='os', _import='environ') +def java_search_paths(path): + if path: + # Look for javac and jar in the specified path. + return path + # With no path specified, look for javac and jar in $JAVA_HOME (if set) + # and $PATH. + if 'JAVA_HOME' in environ: + return [os.path.join(environ['JAVA_HOME'], 'bin'), + environ.get('PATH', '')] + return [environ.get('PATH')] + +# Finds the given java tool, failing with a custom error message if we can't +# find it. +@template +def check_java_tool(tool): + check = check_prog(tool.upper(), (tool,), paths=java_search_paths, + allow_missing=True) + + @depends(check) + def require_tool(result): + if result is None: + die("The program %s was not found. Set $JAVA_HOME to your Java " + "SDK directory or use '--with-java-bin-path={java-bin-dir}'" + % tool) + return result + + return require_tool + +check_java_tool('java') +check_java_tool('javah') +check_java_tool('jar') +check_java_tool('jarsigner') +check_java_tool('keytool') +javac = check_java_tool('javac') + +@depends(javac) +@checking('for javac version') +@imports('subprocess') +def javac_version(javac): + try: + output = subprocess.check_output([javac, '-version'], + stderr=subprocess.STDOUT).rstrip() + version = Version(output.split(' ')[-1]) + if version < '1.7': + die('javac 1.7 or higher is required (found %s)' % version) + return version + except subprocess.CalledProcessError as e: + die('Failed to get javac version: %s', e.output) diff --git a/build/moz.configure/keyfiles.configure b/build/moz.configure/keyfiles.configure new file mode 100644 index 000000000..5a71fd4b2 --- /dev/null +++ b/build/moz.configure/keyfiles.configure @@ -0,0 +1,68 @@ +# -*- 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/. + + +@template +def keyfile(desc, help=None, callback=lambda x: x): + help = help or ('Use the secret key contained in the given keyfile ' + 'for %s requests' % desc) + name = desc.lower().replace(' ', '-') + no_key = callback('no-%s-key' % name) + + option('--with-%s-keyfile' % name, nargs=1, help=help) + + @depends('--with-%s-keyfile' % name) + @checking('for the %s key' % desc, lambda x: x and x is not no_key) + @imports(_from='__builtin__', _import='open') + @imports(_from='__builtin__', _import='IOError') + def keyfile(value): + if value: + try: + with open(value[0]) as fh: + result = fh.read().strip() + if result: + return callback(result) + raise FatalCheckError("'%s' is empty." % value[0]) + except IOError as e: + raise FatalCheckError("'%s': %s." % (value[0], e.strerror)) + return no_key + + return keyfile + + +@template +def simple_keyfile(desc): + value = keyfile(desc) + set_config('MOZ_%s_KEY' % desc.upper().replace(' ', '_'), value) + # Only really required for MOZ_ADJUST_SDK_KEY currently still used in + # old-configure. + add_old_configure_assignment('MOZ_%s_KEY' % desc.upper().replace(' ', '_'), + value) + + +@template +def id_and_secret_keyfile(desc): + def id_and_secret(value): + if value.startswith('no-') and value.endswith('-key'): + id = value[:-3] + 'clientid' + secret = value + elif ' ' in value: + id, secret = value.split(' ', 1) + else: + raise FatalCheckError('%s key file has an invalid format.' % desc) + return namespace( + id=id, + secret=secret, + ) + + content = keyfile(desc, help='Use the client id and secret key contained ' + 'in the given keyfile for %s requests' % desc, + callback=id_and_secret) + + + name = desc.upper().replace(' ', '_') + set_config('MOZ_%s_CLIENTID' % name, delayed_getattr(content, 'id')) + set_config('MOZ_%s_KEY' % name, delayed_getattr(content, 'secret')) diff --git a/build/moz.configure/memory.configure b/build/moz.configure/memory.configure new file mode 100644 index 000000000..3beed2fb2 --- /dev/null +++ b/build/moz.configure/memory.configure @@ -0,0 +1,98 @@ +# -*- 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/. + + +option(env='MOZ_JEMALLOC4', help='Enable jemalloc 4') +imply_option('--enable-jemalloc', depends_if('MOZ_JEMALLOC4')(lambda x: '4')) + + +option('--enable-jemalloc', nargs='?', choices=('4', 'moz'), env='MOZ_MEMORY', + help='Replace memory allocator with jemalloc') + +@depends('--enable-jemalloc', target, build_project, c_compiler) +def jemalloc(value, target, build_project, c_compiler): + if value.origin != 'default': + return bool(value) or None + + if build_project == 'js': + return True + + if target.kernel == 'Darwin' and target.cpu == 'x86_64': + # Don't enable by default on 32-bits OSX. See bug 702250. + return True + + if target.kernel == 'WINNT' and c_compiler.type in ('msvc', 'clang-cl'): + return True + + if target.kernel == 'Linux': + return True + +@depends('--enable-jemalloc') +def jemalloc4(jemalloc): + if len(jemalloc) and jemalloc[0] == '4': + return True + + +set_config('MOZ_MEMORY', jemalloc) +set_define('MOZ_MEMORY', jemalloc) +add_old_configure_assignment('MOZ_MEMORY', jemalloc) + +set_config('MOZ_JEMALLOC4', jemalloc4) +set_define('MOZ_JEMALLOC4', jemalloc4) +add_old_configure_assignment('MOZ_JEMALLOC4', jemalloc4) + + +# Because --enable-jemalloc doesn't use a default because of the dependency +# on the target, we can't use a js_option for it to propagate to js/src +# through the old-configure. +@depends(jemalloc, jemalloc4) +def jemalloc_for_old_configure(jemalloc, jemalloc4): + if jemalloc: + return '--enable-jemalloc=4' if jemalloc4 else '--enable-jemalloc' + return '--disable-jemalloc' + +add_old_configure_arg(jemalloc_for_old_configure) + + +@depends(jemalloc, jemalloc4, target) +def jemalloc_os_define(jemalloc, jemalloc4, target): + if jemalloc and not jemalloc4: + if target.kernel == 'WINNT': + return 'MOZ_MEMORY_WINDOWS' + if target.kernel == 'Linux': + return 'MOZ_MEMORY_LINUX' + if target.kernel == 'Darwin': + return 'MOZ_MEMORY_DARWIN' + if target.kernel in ('kFreeBSD', 'FreeBSD', 'NetBSD'): + return 'MOZ_MEMORY_BSD' + die('--enable-jemalloc is not supported on %s', target.kernel) + +set_define(jemalloc_os_define, '1') + +@depends(jemalloc, jemalloc4, target) +def jemalloc_os_define_android(jemalloc, jemalloc4, target): + if jemalloc and not jemalloc4 and target.os == 'Android': + return 'MOZ_MEMORY_ANDROID' + +set_define(jemalloc_os_define_android, '1') + + +option('--enable-replace-malloc', + help='Enable ability to dynamically replace the malloc implementation') + +@depends('--enable-replace-malloc', jemalloc, milestone, build_project) +def replace_malloc(value, jemalloc, milestone, build_project): + # Enable on central for the debugging opportunities it adds. + if value and not jemalloc: + die('--enable-replace-malloc requires --enable-jemalloc') + if value.origin != 'default': + return bool(value) or None + if milestone.is_nightly and jemalloc and build_project != 'js': + return True + +set_config('MOZ_REPLACE_MALLOC', replace_malloc) +set_define('MOZ_REPLACE_MALLOC', replace_malloc) +add_old_configure_assignment('MOZ_REPLACE_MALLOC', replace_malloc) diff --git a/build/moz.configure/old.configure b/build/moz.configure/old.configure new file mode 100644 index 000000000..b32c3f7b7 --- /dev/null +++ b/build/moz.configure/old.configure @@ -0,0 +1,425 @@ +# -*- 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/. + +@imports('codecs') +@imports('sys') +def encoded_open(path, mode): + encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8' + return codecs.open(path, mode, encoding) + + +option(env='AUTOCONF', nargs=1, help='Path to autoconf 2.13') + +@depends(mozconfig, 'AUTOCONF') +@checking('for autoconf') +@imports(_from='os.path', _import='exists') +@imports('re') +def autoconf(mozconfig, autoconf): + mozconfig_autoconf = None + if mozconfig['path']: + make_extra = mozconfig['make_extra'] + if make_extra: + for assignment in make_extra: + m = re.match('(?:export\s+)?AUTOCONF\s*:?=\s*(.+)$', + assignment) + if m: + mozconfig_autoconf = m.group(1) + + autoconf = autoconf[0] if autoconf else None + + for ac in (mozconfig_autoconf, autoconf, 'autoconf-2.13', 'autoconf2.13', + 'autoconf213'): + if ac: + autoconf = find_program(ac) + if autoconf: + break + else: + fink = find_program('fink') + if fink: + autoconf = os.path.normpath(os.path.join( + fink, '..', '..', 'lib', 'autoconf2.13', 'bin', 'autoconf')) + else: + brew = find_program('brew') + if brew: + autoconf = os.path.normpath(os.path.join( + brew, '..', '..', 'Cellar', 'autoconf213', '2.13', 'bin', + 'autoconf213')) + + if not autoconf: + die('Could not find autoconf 2.13') + + if not exists(autoconf): + die('Could not find autoconf 2.13 at %s', autoconf) + + return autoconf + +set_config('AUTOCONF', autoconf) + + +@depends('OLD_CONFIGURE', mozconfig, autoconf, check_build_environment, shell, + old_configure_assignments, build_project) +@imports(_from='__builtin__', _import='open') +@imports(_from='__builtin__', _import='print') +@imports('glob') +@imports('itertools') +@imports('subprocess') +# Import getmtime without overwriting the sandbox os.path. +@imports(_from='os.path', _import='getmtime') +@imports(_from='os.path', _import='exists') +@imports(_from='mozbuild.shellutil', _import='quote') +def prepare_configure(old_configure, mozconfig, autoconf, build_env, shell, + old_configure_assignments, build_project): + # os.path.abspath in the sandbox will ensure forward slashes on Windows, + # which is actually necessary because this path actually ends up literally + # as $0, and backslashes there breaks autoconf's detection of the source + # directory. + old_configure = os.path.abspath(old_configure[0]) + if build_project == 'js': + old_configure_dir = os.path.dirname(old_configure) + if not old_configure_dir.endswith('/js/src'): + old_configure = os.path.join(old_configure_dir, 'js', 'src', + os.path.basename(old_configure)) + + refresh = True + if exists(old_configure): + mtime = getmtime(old_configure) + aclocal = os.path.join(build_env.topsrcdir, 'build', 'autoconf', + '*.m4') + for input in itertools.chain( + (old_configure + '.in', + os.path.join(os.path.dirname(old_configure), 'aclocal.m4')), + glob.iglob(aclocal), + ): + if getmtime(input) > mtime: + break + else: + refresh = False + + if refresh: + log.info('Refreshing %s with %s', old_configure, autoconf) + script = subprocess.check_output([ + shell, autoconf, + '--localdir=%s' % os.path.dirname(old_configure), + old_configure + '.in']) + + # Make old-configure append to config.log, where we put our own log. + # This could be done with a m4 macro, but it's way easier this way + script = script.replace('>./config.log', '>>./config.log') + + with open(old_configure, 'wb') as fh: + fh.write(script) + + cmd = [shell, old_configure] + with encoded_open('old-configure.vars', 'w') as out: + log.debug('Injecting the following to old-configure:') + def inject(command): + print(command, file=out) + log.debug('| %s', command) + + if mozconfig['path']: + for key, value in mozconfig['vars']['added'].items(): + inject("%s=%s" % (key, quote(value))) + for key, (old, value) in mozconfig['vars']['modified'].items(): + inject("%s=%s" % (key, quote(value))) + for t in ('env', 'vars'): + for key in mozconfig[t]['removed'].keys(): + inject("unset %s" % key) + + # Autoconf is special, because it might be passed from + # mozconfig['make_extra'], which we don't pass automatically above. + inject('export AUTOCONF=%s' % quote(autoconf)) + + for assignment in old_configure_assignments: + inject(assignment) + + return cmd + + +@template +def old_configure_options(*options): + for opt in options: + option(opt, nargs='*', help='Help missing for old configure options') + + @dependable + def all_options(): + return list(options) + + return depends(prepare_configure, extra_old_configure_args, all_options, + *options) + + +@old_configure_options( + '--cache-file', + '--datadir', + '--enable-accessibility', + '--enable-address-sanitizer', + '--enable-alsa', + '--enable-android-omx', + '--enable-b2g-bt', + '--enable-b2g-camera', + '--enable-b2g-ril', + '--enable-bundled-fonts', + '--enable-clang-plugin', + '--enable-content-sandbox', + '--enable-cookies', + '--enable-cpp-rtti', + '--enable-crashreporter', + '--enable-dbus', + '--enable-debug-js-modules', + '--enable-directshow', + '--enable-dtrace', + '--enable-dump-painting', + '--enable-elf-hack', + '--enable-extensions', + '--enable-faststripe', + '--enable-feeds', + '--enable-gamepad', + '--enable-gconf', + '--enable-gczeal', + '--enable-gio', + '--enable-gnomeui', + '--enable-gold', + '--enable-hardware-aec-ns', + '--enable-icf', + '--enable-install-strip', + '--enable-ion', + '--enable-ios-target', + '--enable-jitspew', + '--enable-libjpeg-turbo', + '--enable-libproxy', + '--enable-llvm-hacks', + '--enable-logrefcnt', + '--enable-maintenance-service', + '--enable-memory-sanitizer', + '--enable-mobile-optimize', + '--enable-mozril-geoloc', + '--enable-necko-wifi', + '--enable-negotiateauth', + '--enable-nfc', + '--enable-nspr-build', + '--enable-official-branding', + '--enable-omx-plugin', + '--enable-oom-breakpoint', + '--enable-optimize', + '--enable-parental-controls', + '--enable-pie', + '--enable-png-arm-neon-support', + '--enable-posix-nspr-emulation', + '--enable-pref-extensions', + '--enable-pulseaudio', + '--enable-raw', + '--enable-readline', + '--enable-reflow-perf', + '--enable-release', + '--enable-require-all-d3dc-versions', + '--enable-safe-browsing', + '--enable-sandbox', + '--enable-signmar', + '--enable-simulator', + '--enable-small-chunk-size', + '--enable-startup-notification', + '--enable-startupcache', + '--enable-stdcxx-compat', + '--enable-strip', + '--enable-synth-pico', + '--enable-system-cairo', + '--enable-system-extension-dirs', + '--enable-system-pixman', + '--enable-system-sqlite', + '--enable-tasktracer', + '--enable-thread-sanitizer', + '--enable-trace-logging', + '--enable-ui-locale', + '--enable-universalchardet', + '--enable-updater', + '--enable-url-classifier', + '--enable-valgrind', + '--enable-verify-mar', + '--enable-webrtc', + '--enable-xul', + '--enable-zipwriter', + '--includedir', + '--libdir', + '--no-create', + '--prefix', + '--with-android-cxx-stl', + '--with-android-distribution-directory', + '--with-android-max-sdk', + '--with-android-min-sdk', + '--with-android-sdk', + '--with-app-basename', + '--with-app-name', + '--with-arch', + '--with-branding', + '--with-crashreporter-enable-percent', + '--with-cross-lib', + '--with-debug-label', + '--with-default-mozilla-five-home', + '--with-distribution-id', + '--with-doc-include-dirs', + '--with-doc-input-dirs', + '--with-doc-output-dir', + '--with-float-abi', + '--with-fpu', + '--with-intl-api', + '--with-ios-sdk', + '--with-jitreport-granularity', + '--with-macbundlename-prefix', + '--with-macos-private-frameworks', + '--with-macos-sdk', + '--with-nspr-cflags', + '--with-nspr-exec-prefix', + '--with-nspr-libs', + '--with-nspr-prefix', + '--with-nss-exec-prefix', + '--with-nss-prefix', + '--with-pthreads', + '--with-qemu-exe', + '--with-sixgill', + '--with-soft-float', + '--with-system-bz2', + '--with-system-icu', + '--with-system-jpeg', + '--with-system-libevent', + '--with-system-libvpx', + '--with-system-nspr', + '--with-system-nss', + '--with-system-png', + '--with-system-zlib', + '--with-thumb', + '--with-thumb-interwork', + '--with-unify-dist', + '--with-user-appdir', + '--x-includes', + '--x-libraries', + + # Below are the configure flags used by comm-central. + '--enable-ldap', + '--enable-mapi', + '--enable-calendar', + '--enable-incomplete-external-linkage', +) +@imports(_from='__builtin__', _import='compile') +@imports(_from='__builtin__', _import='open') +@imports('logging') +@imports('os') +@imports('subprocess') +@imports('sys') +@imports(_from='mozbuild.shellutil', _import='quote') +def old_configure(prepare_configure, extra_old_configure_args, all_options, + *options): + cmd = prepare_configure + + # old-configure only supports the options listed in @old_configure_options + # so we don't need to pass it every single option we've been passed. Only + # the ones that are not supported by python configure need to. + cmd += [ + value.format(name) + for name, value in zip(all_options, options) + if value.origin != 'default' + ] + + # We also pass it the options from js/moz.configure so that it can pass + # them down to js/src/configure. Note this list is empty when running + # js/src/configure, in which case we don't need to pass those options + # to old-configure since old-configure doesn't handle them anyways. + if extra_old_configure_args: + cmd += extra_old_configure_args + + # For debugging purpose, in case it's not what we'd expect. + log.debug('Running %s', quote(*cmd)) + + # Our logging goes to config.log, the same file old.configure uses. + # We can't share the handle on the file, so close it. We assume nothing + # beyond this point is going to be interesting to log to config.log from + # our end, so we don't make the effort to recreate a logging.FileHandler. + logger = logging.getLogger('moz.configure') + for handler in logger.handlers: + if isinstance(handler, logging.FileHandler): + handler.close() + logger.removeHandler(handler) + + log_size = os.path.getsize('config.log') + + ret = subprocess.call(cmd) + if ret: + with log.queue_debug(): + with encoded_open('config.log', 'r') as fh: + fh.seek(log_size) + for line in fh: + log.debug(line.rstrip()) + log.error('old-configure failed') + sys.exit(ret) + + raw_config = {} + with encoded_open('config.data', 'r') as fh: + code = compile(fh.read(), 'config.data', 'exec') + # Every variation of the exec() function I tried led to: + # SyntaxError: unqualified exec is not allowed in function 'main' it + # contains a nested function with free variables + exec code in raw_config + + # Ensure all the flags known to old-configure appear in the + # @old_configure_options above. + all_options = set(all_options) + for flag in raw_config['flags']: + if flag not in all_options: + die('Missing option in `@old_configure_options` in %s: %s', + __file__, flag) + + # If the code execution above fails, we want to keep the file around for + # debugging. + os.remove('config.data') + return raw_config + + +# set_config is only available in the global namespace, not directly in +# @depends functions, but we do need to enumerate the result of +# old_configure, so we cheat. +@imports('__sandbox__') +def set_old_configure_config(name, value): + __sandbox__.set_config_impl(name, value) + +# Same as set_old_configure_config, but for set_define. +@imports('__sandbox__') +def set_old_configure_define(name, value): + __sandbox__.set_define_impl(name, value) + + +@depends(old_configure) +@imports('types') +def post_old_configure(raw_config): + for k, v in raw_config['substs']: + set_old_configure_config( + k[1:-1], v[1:-1] if isinstance(v, types.StringTypes) else v) + + for k, v in dict(raw_config['defines']).iteritems(): + set_old_configure_define(k[1:-1], v[1:-1]) + + set_old_configure_config('non_global_defines', + raw_config['non_global_defines']) + + +# Assuming no other option is declared after this function, handle the +# env options that were injected by mozconfig_options by creating dummy +# Option instances and having the sandbox's CommandLineHelper handle +# them. We only do so for options that haven't been declared so far, +# which should be a proxy for the options that old-configure handles +# and that we don't know anything about. +@depends('--help') +@imports('__sandbox__') +@imports(_from='mozbuild.configure.options', _import='Option') +def remaining_mozconfig_options(_): + helper = __sandbox__._helper + for arg in helper: + if helper._origins[arg] != 'mozconfig': + continue + name = arg.split('=', 1)[0] + if name.isupper() and name not in __sandbox__._options: + option = Option(env=name, nargs='*', help=name) + helper.handle(option) + +# Please do not add anything after remaining_mozconfig_options() diff --git a/build/moz.configure/pkg.configure b/build/moz.configure/pkg.configure new file mode 100644 index 000000000..991f401ae --- /dev/null +++ b/build/moz.configure/pkg.configure @@ -0,0 +1,97 @@ +# -*- 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/. + +@depends('--enable-compile-environment') +def pkg_config(compile_env): + if compile_env: + return ('pkg-config',) + +pkg_config = check_prog('PKG_CONFIG', pkg_config, allow_missing=True) + +@depends_if(pkg_config) +@checking('for pkg-config version') +@imports('subprocess') +def pkg_config_version(pkg_config): + return Version(check_cmd_output(pkg_config, '--version').rstrip()) + +# Locates the given module using pkg-config. +# - `var` determines the name of variables to set when the package is found. +# <var>_CFLAGS and <var>_LIBS are set with corresponding values. +# - `package_desc` package name and version requirement string, list of +# strings describing packages to locate, or depends function that will +# resolve to such a string or list of strings. +# - `when` a depends function that will determine whether to perform +# any checks (default is to always perform checks). +# - `allow_missing` If set, failure to fulfill the package description +# will not result in an error or logged message, and any error message +# will be returned to the caller. +# Returns `True` when the package description is fulfilled. +@template +def pkg_check_modules(var, package_desc, when=always, + allow_missing=False): + if isinstance(package_desc, (tuple, list)): + package_desc = ' '.join(package_desc) + package_desc = dependable(package_desc) + + @depends(when, '--enable-compile-environment') + def when_and_compile_environment(when, compile_environment): + return when and compile_environment + + @depends_when(pkg_config, pkg_config_version, + when=when_and_compile_environment) + def check_pkg_config(pkg_config, version): + min_version = '0.9.0' + if pkg_config is None: + die("*** The pkg-config script could not be found. Make sure it is\n" + "*** in your path, or set the PKG_CONFIG environment variable\n" + "*** to the full path to pkg-config.") + if version < min_version: + die("*** Your version of pkg-config is too old. You need version %s or newer.", + min_version) + + @depends_when(pkg_config, package_desc, when=when_and_compile_environment) + @imports('subprocess') + @imports('sys') + @imports(_from='mozbuild.configure.util', _import='LineIO') + def package(pkg_config, package_desc): + # package_desc may start as a depends function, so we can't use + # @checking here. + log.info("checking for %s... " % package_desc) + with log.queue_debug(): + try: + subprocess.check_output([pkg_config, '--errors-to-stdout', + '--print-errors', package_desc]) + log.info("yes") + return True + except subprocess.CalledProcessError as e: + log.info("no") + log_writer = log.warning if allow_missing else log.error + with LineIO(lambda l: log_writer(l)) as o: + o.write(e.output) + if not allow_missing: + sys.exit(1) + + @depends_when(pkg_config, package_desc, when=package) + @checking('%s_CFLAGS' % var, callback=lambda t: ' '.join(t)) + def pkg_cflags(pkg_config, package_desc): + flags = check_cmd_output(pkg_config, '--cflags', package_desc) + return tuple(flags.split()) + + @depends_when(pkg_config, package_desc, when=package) + @checking('%s_LIBS' % var, callback=lambda t: ' '.join(t)) + def pkg_libs(pkg_config, package_desc): + libs = check_cmd_output(pkg_config, '--libs', package_desc) + # Remove evil flags like -Wl,--export-dynamic + return tuple(libs.replace('-Wl,--export-dynamic', '').split()) + + @depends_when(pkg_cflags, pkg_libs, when=package) + def pkg_info(cflags, libs): + return namespace(cflags=cflags, libs=libs) + + set_config('%s_CFLAGS' % var, pkg_cflags) + set_config('%s_LIBS' % var, pkg_libs) + + return pkg_info diff --git a/build/moz.configure/rust.configure b/build/moz.configure/rust.configure new file mode 100644 index 000000000..261768f64 --- /dev/null +++ b/build/moz.configure/rust.configure @@ -0,0 +1,166 @@ +# -*- 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/. + +option('--enable-rust', help='Include Rust language sources') + +@depends('--enable-rust') +def rust_compiler_names(value): + if value: + return ['rustc'] + +@depends('--enable-rust') +def cargo_binary_names(value): + if value: + return ['cargo'] + +rustc = check_prog('RUSTC', rust_compiler_names, allow_missing=True) +cargo = check_prog('CARGO', cargo_binary_names, allow_missing=True) + +@depends_if(rustc) +@checking('rustc version', lambda info: info.version) +def rustc_info(rustc): + out = check_cmd_output(rustc, '--version', '--verbose').splitlines() + info = dict((s.strip() for s in line.split(':', 1)) for line in out[1:]) + return namespace( + version=Version(info.get('release', '0')), + commit=info.get('commit-hash', 'unknown'), + ) + +@depends_if(cargo) +@checking('cargo support for --frozen') +@imports('subprocess') +@imports('os') +def cargo_supports_frozen(cargo): + try: + lines = subprocess.check_output( + [cargo, 'help', 'build'] + ).splitlines() + supported = any(' --frozen' in l for l in lines) + if 'MOZ_AUTOMATION' in os.environ and not supported: + die('cargo in automation must support --frozen') + return supported + except subprocess.CalledProcessError as e: + die('Failed to call cargo: %s', e.message) + +set_config('MOZ_CARGO_SUPPORTS_FROZEN', cargo_supports_frozen) + +@depends('--enable-rust', rustc, rustc_info) +@imports(_from='textwrap', _import='dedent') +def rust_compiler(value, rustc, rustc_info): + if value: + if not rustc: + die(dedent('''\ + Rust compiler not found. + To compile rust language sources, you must have 'rustc' in your path. + See https//www.rust-lang.org/ for more information. + ''')) + version = rustc_info.version + min_version = Version('1.10') + if version < min_version: + die(dedent('''\ + Rust compiler {} is too old. + To compile Rust language sources please install at least + version {} of the 'rustc' toolchain and make sure it is + first in your path. + You can verify this by typing 'rustc --version'. + '''.format(version, min_version))) + return True + +set_config('MOZ_RUST', rust_compiler) + +@depends(rust_compiler, rustc, target, cross_compiling) +@imports('os') +@imports('subprocess') +@imports(_from='mozbuild.configure.util', _import='LineIO') +@imports(_from='mozbuild.shellutil', _import='quote') +@imports(_from='tempfile', _import='mkstemp') +def rust_target(rust_compiler, rustc, target, cross_compiling): + if rust_compiler: + # Rust's --target options are similar to, but not exactly the same + # as, the autoconf-derived targets we use. An example would be that + # Rust uses distinct target triples for targetting the GNU C++ ABI + # and the MSVC C++ ABI on Win32, whereas autoconf has a single + # triple and relies on the user to ensure that everything is + # compiled for the appropriate ABI. We need to perform appropriate + # munging to get the correct option to rustc. + # + # The canonical list of targets supported can be derived from: + # + # https://github.com/rust-lang/rust/tree/master/mk/cfg + + # Avoid having to write out os+kernel for all the platforms where + # they don't differ. + os_or_kernel = target.kernel if target.kernel == 'Linux' and target.os != 'Android' else target.os + rustc_target = { + # DragonFly + ('x86_64', 'DragonFly'): 'x86_64-unknown-dragonfly', + # FreeBSD + ('x86', 'FreeBSD'): 'i686-unknown-freebsd', + ('x86_64', 'FreeBSD'): 'x86_64-unknown-freebsd', + # NetBSD + ('x86_64', 'NetBSD'): 'x86_64-unknown-netbsd', + # OpenBSD + ('x86_64', 'OpenBSD'): 'x86_64-unknown-openbsd', + # Linux + ('x86', 'Linux'): 'i586-unknown-linux-gnu', + # Linux + ('x86_64', 'Linux'): 'x86_64-unknown-linux-gnu', + # OS X and iOS + ('x86', 'OSX'): 'i686-apple-darwin', + ('x86', 'iOS'): 'i386-apple-ios', + ('x86_64', 'OSX'): 'x86_64-apple-darwin', + # Android + ('x86', 'Android'): 'i686-linux-android', + ('arm', 'Android'): 'armv7-linux-androideabi', + # Windows + # XXX better detection of CXX needed here, to figure out whether + # we need i686-pc-windows-gnu instead, since mingw32 builds work. + ('x86', 'WINNT'): 'i686-pc-windows-msvc', + ('x86_64', 'WINNT'): 'x86_64-pc-windows-msvc', + }.get((target.cpu, os_or_kernel), None) + + if rustc_target is None: + die("Don't know how to translate {} for rustc".format(target.alias)) + + # Check to see whether our rustc has a reasonably functional stdlib + # for our chosen target. + target_arg = '--target=' + rustc_target + in_fd, in_path = mkstemp(prefix='conftest', suffix='.rs') + out_fd, out_path = mkstemp(prefix='conftest', suffix='.rlib') + os.close(out_fd) + try: + source = 'pub extern fn hello() { println!("Hello world"); }' + log.debug('Creating `%s` with content:', in_path) + with LineIO(lambda l: log.debug('| %s', l)) as out: + out.write(source) + + os.write(in_fd, source) + os.close(in_fd) + + cmd = [ + rustc, + '--crate-type', 'staticlib', + target_arg, + '-o', out_path, + in_path, + ] + def failed(): + die('Cannot compile for {} with {}'.format(target.alias, rustc)) + check_cmd_output(*cmd, onerror=failed) + if not os.path.exists(out_path) or os.path.getsize(out_path) == 0: + failed() + finally: + os.remove(in_path) + os.remove(out_path) + # This target is usable. + return rustc_target + +set_config('RUST_TARGET', rust_target) + +# Until we remove all the other Rust checks in old-configure. +add_old_configure_assignment('MOZ_RUST', rust_compiler) +add_old_configure_assignment('RUSTC', rustc) +add_old_configure_assignment('RUST_TARGET', rust_target) diff --git a/build/moz.configure/toolchain.configure b/build/moz.configure/toolchain.configure new file mode 100644 index 000000000..8b2416152 --- /dev/null +++ b/build/moz.configure/toolchain.configure @@ -0,0 +1,910 @@ +# -*- 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', + delayed_getattr(yasm_version, 'major')) +add_old_configure_assignment('_YASM_MINOR_VERSION', + delayed_getattr(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, gonkdir, '--help') +def compiling_android(compile_env, build_project, gonkdir, _): + return compile_env and (gonkdir or 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) + +@depends_if(ccache) +def using_ccache(ccache): + return True + +set_config('MOZ_USING_CCACHE', using_ccache) + +@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) + + +# 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, host, cross_compiling) +def toolchain_prefix(value, target, host, 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 with fallback to + # cl.exe. + if info.type == 'clang-cl' and info.version != '19.00.24213': + # Those flags are direct clang-cl flags that don't need -Xclang, add + # them directly. + flags.append('-fms-compatibility-version=19.00.24213') + flags.append('-fallback') + + # 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='collections', _import='defaultdict') +@imports(_from='__builtin__', _import='sorted') +def get_vc_paths(base): + vc = defaultdict(lambda: defaultdict(dict)) + subkey = r'Microsoft\VisualStudio\VC\*\*\*\Compiler' + for v, h, t, p in get_registry_values(base + '\\' + subkey): + vc[v][h][t] = p + if not vc: + return + version, data = sorted(vc.iteritems(), key=lambda x: Version(x[0]))[-1] + return data + + +@depends(host) +@imports('platform') +def vc_compiler_path(host): + if host.kernel != 'WINNT': + return + vc_host = { + 'x86': 'x86', + 'AMD64': 'x64', + }.get(platform.machine()) + if vc_host is None: + return + vc_target = { + 'x86': 'x86', + 'x86_64': 'x64', + 'arm': 'arm', + }.get(host.cpu) + if vc_target is None: + return + + base_key = r'HKEY_LOCAL_MACHINE\SOFTWARE' + data = get_vc_paths(base_key) + if not data: + data = get_vc_paths(base_key + r'\Wow6432Node') + if not data: + return + + path = data.get(vc_host, {}).get(vc_target) + if not path and vc_host == 'x64': + vc_host = 'x86' + path = data.get(vc_host, {}).get(vc_target) + if not path: + return + path = os.path.dirname(path) + if vc_host != vc_target: + other_path = data.get(vc_host, {}).get(vc_host) + if other_path: + return (path, os.path.dirname(other_path)) + return (path,) + + +@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 + assert host_or_target == target or other_compiler + assert language == 'C' or host_or_target == target or other_c_compiler + + 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: + args = (c_compiler, other_c_compiler) if other_c_compiler else () + + @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=delayed_getattr(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.8.0': + raise FatalCheckError( + 'Only GCC 4.8 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, delayed_getattr(valid_compiler, 'type')) + add_old_configure_assignment( + '%s_TYPE' % var, delayed_getattr(valid_compiler, 'type')) + add_old_configure_assignment( + '%s_VERSION' % var, delayed_getattr(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) + + 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') + +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.') + + +@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) + +# 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. +@depends(c_compiler, target) +def libcxx_inline_visibility(c_compiler, target): + if c_compiler.type == 'clang' and target.os == 'Android': + return '' + +set_define('_LIBCPP_INLINE_VISIBILITY', libcxx_inline_visibility) +set_define('_LIBCPP_INLINE_VISIBILITY_EXCEPT_GCC49', libcxx_inline_visibility) + +@depends(c_compiler, target, check_build_environment) +def visibility_flags(c_compiler, 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) + +include('windows.configure') +include('rust.configure') diff --git a/build/moz.configure/util.configure b/build/moz.configure/util.configure new file mode 100644 index 000000000..33ed17de6 --- /dev/null +++ b/build/moz.configure/util.configure @@ -0,0 +1,440 @@ +# -*- 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/. + +@imports('sys') +def die(*args): + 'Print an error and terminate configure.' + log.error(*args) + sys.exit(1) + + +@imports(_from='mozbuild.configure', _import='ConfigureError') +def configure_error(message): + '''Raise a programming error and terminate configure. + Primarily for use in moz.configure templates to sanity check + their inputs from moz.configure usage.''' + raise ConfigureError(message) + +# A wrapper to obtain a process' output that returns the output generated +# by running the given command if it exits normally, and streams that +# output to log.debug and calls die or the given error callback if it +# does not. +@imports('subprocess') +@imports('sys') +@imports(_from='mozbuild.configure.util', _import='LineIO') +@imports(_from='mozbuild.shellutil', _import='quote') +def check_cmd_output(*args, **kwargs): + onerror = kwargs.pop('onerror', None) + + with log.queue_debug(): + log.debug('Executing: `%s`', quote(*args)) + proc = subprocess.Popen(args, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = proc.communicate() + retcode = proc.wait() + if retcode == 0: + return stdout + + log.debug('The command returned non-zero exit status %d.', + retcode) + for out, desc in ((stdout, 'output'), (stderr, 'error output')): + if out: + log.debug('Its %s was:', desc) + with LineIO(lambda l: log.debug('| %s', l)) as o: + o.write(out) + if onerror: + return onerror() + die('Command `%s` failed with exit status %d.' % + (quote(*args), retcode)) + +@imports('os') +def is_absolute_or_relative(path): + if os.altsep and os.altsep in path: + return True + return os.sep in path + + +@imports(_import='mozpack.path', _as='mozpath') +def normsep(path): + return mozpath.normsep(path) + + +@imports('ctypes') +@imports(_from='ctypes', _import='wintypes') +@imports(_from='mozbuild.configure.constants', _import='WindowsBinaryType') +def windows_binary_type(path): + """Obtain the type of a binary on Windows. + + Returns WindowsBinaryType constant. + """ + GetBinaryTypeW = ctypes.windll.kernel32.GetBinaryTypeW + GetBinaryTypeW.argtypes = [wintypes.LPWSTR, wintypes.POINTER(wintypes.DWORD)] + GetBinaryTypeW.restype = wintypes.BOOL + + bin_type = wintypes.DWORD() + res = GetBinaryTypeW(path, ctypes.byref(bin_type)) + if not res: + die('could not obtain binary type of %s' % path) + + if bin_type.value == 0: + return WindowsBinaryType('win32') + elif bin_type.value == 6: + return WindowsBinaryType('win64') + # If we see another binary type, something is likely horribly wrong. + else: + die('unsupported binary type on %s: %s' % (path, bin_type)) + + +@imports('ctypes') +@imports(_from='ctypes', _import='wintypes') +def get_GetShortPathNameW(): + GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW + GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, + wintypes.DWORD] + GetShortPathNameW.restype = wintypes.DWORD + return GetShortPathNameW + + +@template +@imports('ctypes') +@imports('platform') +@imports(_from='mozbuild.shellutil', _import='quote') +def normalize_path(): + # Until the build system can properly handle programs that need quoting, + # transform those paths into their short version on Windows (e.g. + # c:\PROGRA~1...). + if platform.system() == 'Windows': + GetShortPathNameW = get_GetShortPathNameW() + + def normalize_path(path): + path = normsep(path) + if quote(path) == path: + return path + size = 0 + while True: + out = ctypes.create_unicode_buffer(size) + needed = GetShortPathNameW(path, out, size) + if size >= needed: + if ' ' in out.value: + die("GetShortPathName returned a long path name. " + "Are 8dot3 filenames disabled?") + return normsep(out.value) + size = needed + + else: + def normalize_path(path): + return normsep(path) + + return normalize_path + +normalize_path = normalize_path() + + +# Locates the given program using which, or returns the given path if it +# exists. +# The `paths` parameter may be passed to search the given paths instead of +# $PATH. +@imports(_from='which', _import='which') +@imports(_from='which', _import='WhichError') +@imports('itertools') +@imports(_from='os', _import='pathsep') +def find_program(file, paths=None): + try: + if is_absolute_or_relative(file): + return normalize_path(which(os.path.basename(file), + [os.path.dirname(file)])) + if paths: + if not isinstance(paths, (list, tuple)): + die("Paths provided to find_program must be a list of strings, " + "not %r", paths) + paths = list(itertools.chain( + *(p.split(pathsep) for p in paths if p))) + return normalize_path(which(file, path=paths)) + except WhichError: + return None + + +@imports('os') +@imports('subprocess') +@imports(_from='mozbuild.configure.util', _import='LineIO') +@imports(_from='tempfile', _import='mkstemp') +def try_invoke_compiler(compiler, language, source, flags=None, onerror=None): + flags = flags or [] + + if not isinstance(flags, (list, tuple)): + die("Flags provided to try_compile must be a list of strings, " + "not %r", paths) + + suffix = { + 'C': '.c', + 'C++': '.cpp', + }[language] + + fd, path = mkstemp(prefix='conftest.', suffix=suffix) + try: + source = source.encode('ascii', 'replace') + + log.debug('Creating `%s` with content:', path) + with LineIO(lambda l: log.debug('| %s', l)) as out: + out.write(source) + + os.write(fd, source) + os.close(fd) + cmd = compiler + list(flags) + [path] + kwargs = {'onerror': onerror} + return check_cmd_output(*cmd, **kwargs) + finally: + os.remove(path) + + +def unique_list(l): + result = [] + for i in l: + if l not in result: + result.append(i) + return result + + +# Get values out of the Windows registry. This function can only be called on +# Windows. +# The `pattern` argument is a string starting with HKEY_ and giving the full +# "path" of the registry key to get the value for, with backslash separators. +# The string can contains wildcards ('*'). +# The result of this functions is an enumerator yielding tuples for each +# match. Each of these tuples contains the key name matching wildcards +# followed by the value. +# +# Examples: +# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' +# r'Windows Kits\Installed Roots\KitsRoot*') +# yields e.g.: +# ('KitsRoot81', r'C:\Program Files (x86)\Windows Kits\8.1\') +# ('KitsRoot10', r'C:\Program Files (x86)\Windows Kits\10\') +# +# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' +# r'Windows Kits\Installed Roots\KitsRoot8.1') +# yields e.g.: +# (r'C:\Program Files (x86)\Windows Kits\8.1\',) +# +# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' +# r'Windows Kits\*\KitsRoot*') +# yields e.g.: +# ('Installed Roots', 'KitsRoot81', +# r'C:\Program Files (x86)\Windows Kits\8.1\') +# ('Installed Roots', 'KitsRoot10', +# r'C:\Program Files (x86)\Windows Kits\10\') +# +# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' +# r'VisualStudio\VC\*\x86\*\Compiler') +# yields e.g.: +# ('19.0', 'arm', r'C:\...\amd64_arm\cl.exe') +# ('19.0', 'x64', r'C:\...\amd64\cl.exe') +# ('19.0', 'x86', r'C:\...\amd64_x86\cl.exe') +@imports(_import='_winreg', _as='winreg') +@imports(_from='__builtin__', _import='WindowsError') +@imports(_from='fnmatch', _import='fnmatch') +def get_registry_values(pattern): + def enum_helper(func, key): + i = 0 + while True: + try: + yield func(key, i) + except WindowsError: + break + i += 1 + + def get_keys(key, pattern): + try: + s = winreg.OpenKey(key, '\\'.join(pattern[:-1])) + except WindowsError: + return + for k in enum_helper(winreg.EnumKey, s): + if fnmatch(k, pattern[-1]): + try: + yield k, winreg.OpenKey(s, k) + except WindowsError: + pass + + def get_values(key, pattern): + try: + s = winreg.OpenKey(key, '\\'.join(pattern[:-1])) + except WindowsError: + return + for k, v, t in enum_helper(winreg.EnumValue, s): + if fnmatch(k, pattern[-1]): + yield k, v + + def split_pattern(pattern): + subpattern = [] + for p in pattern: + subpattern.append(p) + if '*' in p: + yield subpattern + subpattern = [] + if subpattern: + yield subpattern + + pattern = pattern.split('\\') + assert pattern[0].startswith('HKEY_') + keys = [(getattr(winreg, pattern[0]),)] + pattern = list(split_pattern(pattern[1:])) + for i, p in enumerate(pattern): + next_keys = [] + for base_key in keys: + matches = base_key[:-1] + base_key = base_key[-1] + if i == len(pattern) - 1: + want_name = '*' in p[-1] + for name, value in get_values(base_key, p): + yield matches + ((name, value) if want_name else (value,)) + else: + for name, k in get_keys(base_key, p): + next_keys.append(matches + (name, k)) + keys = next_keys + + +@imports(_from='mozbuild.configure.util', _import='Version', _as='_Version') +def Version(v): + 'A version number that can be compared usefully.' + return _Version(v) + +# Denotes a deprecated option. Combines option() and @depends: +# @deprecated_option('--option') +# def option(value): +# ... +# @deprecated_option() takes the same arguments as option(), except `help`. +# The function may handle the option like a typical @depends function would, +# but it is recommended it emits a deprecation error message suggesting an +# alternative option to use if there is one. +@template +def deprecated_option(*args, **kwargs): + assert 'help' not in kwargs + kwargs['help'] = 'Deprecated' + opt = option(*args, **kwargs) + + def decorator(func): + @depends(opt.option) + def deprecated(value): + if value.origin != 'default': + return func(value) + return deprecated + + return decorator + + +# from mozbuild.util import ReadOnlyNamespace as namespace +@imports(_from='mozbuild.util', _import='ReadOnlyNamespace') +def namespace(**kwargs): + return ReadOnlyNamespace(**kwargs) + + +# Turn an object into an object that can be used as an argument to @depends. +# The given object can be a literal value, a function that takes no argument, +# or, for convenience, a @depends function. +@template +@imports(_from='inspect', _import='isfunction') +@imports(_from='mozbuild.configure', _import='SandboxDependsFunction') +def dependable(obj): + if isinstance(obj, SandboxDependsFunction): + return obj + if isfunction(obj): + return depends(when=True)(obj) + return depends(when=True)(lambda: obj) + + +always = dependable(True) +never = dependable(False) + + +# Some @depends function return namespaces, and one could want to use one +# specific attribute from such a namespace as a "value" given to functions +# such as `set_config`. But those functions do not take immediate values. +# The `delayed_getattr` function allows access to attributes from the result +# of a @depends function in a non-immediate manner. +# @depends('--option') +# def option(value) +# return namespace(foo=value) +# set_config('FOO', delayed_getattr(option, 'foo') +@template +def delayed_getattr(func, key): + @depends(func) + def result(value): + # The @depends function we're being passed may have returned + # None, or an object that simply doesn't have the wanted key. + # In that case, just return None. + return getattr(value, key, None) + + return result + + +# Like @depends, but the decorated function is only called if one of the +# arguments it would be called with has a positive value (bool(value) is True) +@template +def depends_if(*args): + def decorator(func): + @depends(*args) + def wrapper(*args): + if any(arg for arg in args): + return func(*args) + return wrapper + return decorator + +# Like @depends_if, but a distinguished value passed as a keyword argument +# "when" is truth tested instead of every argument. This value is not passed +# to the function if it is called. +@template +def depends_when(*args, **kwargs): + if not len(kwargs) == 1 and kwargs.get('when'): + die('depends_when requires a single keyword argument, "when"') + when = kwargs['when'] + if not when: + return depends(*args) + + def decorator(fn): + @depends(when, *args) + def wrapper(val, *args): + if val: + return fn(*args) + return wrapper + return decorator + + +# Hacks related to old-configure +# ============================== + +@dependable +def old_configure_assignments(): + return [] + +@dependable +def extra_old_configure_args(): + return [] + +@template +def add_old_configure_assignment(var, value): + var = dependable(var) + value = dependable(value) + + @depends(old_configure_assignments, var, value) + @imports(_from='mozbuild.shellutil', _import='quote') + def add_assignment(assignments, var, value): + if var is None or value is None: + return + if value is True: + assignments.append('%s=1' % var) + elif value is False: + assignments.append('%s=' % var) + else: + if isinstance(value, (list, tuple)): + value = quote(*value) + assignments.append('%s=%s' % (var, quote(str(value)))) + +@template +def add_old_configure_arg(arg): + @depends(extra_old_configure_args, arg) + def add_arg(args, arg): + if arg: + args.append(arg) diff --git a/build/moz.configure/warnings.configure b/build/moz.configure/warnings.configure new file mode 100644 index 000000000..273a41bd9 --- /dev/null +++ b/build/moz.configure/warnings.configure @@ -0,0 +1,111 @@ +# -*- Mode: python; c-basic-offset: 4; 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/. + +js_option('--enable-warnings-as-errors', env='MOZ_ENABLE_WARNINGS_AS_ERRORS', + default=depends('MOZ_AUTOMATION', '--help')(lambda x, _: bool(x)), + help='Enable treating warnings as errors') + +add_old_configure_assignment( + 'MOZ_ENABLE_WARNINGS_AS_ERRORS', + depends('--enable-warnings-as-errors')(lambda x: bool(x))) + + +# GCC/Clang warnings: +# https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Warning-Options.html + +# lots of useful warnings +add_gcc_warning('-Wall') + +# catches C++ version forward-compat issues +add_gcc_warning('-Wc++11-compat', cxx_compiler) + +# catches bugs, e.g. "if (c); foo();", few false positives +add_gcc_warning('-Wempty-body') + +# catches return types with qualifiers like const +add_gcc_warning('-Wignored-qualifiers') + +# function declaration hides virtual function from base class +add_gcc_warning('-Woverloaded-virtual', cxx_compiler) + +# catches pointer arithmetic using NULL or sizeof(void) +add_gcc_warning('-Wpointer-arith') + +# catches comparing signed/unsigned ints +add_gcc_warning('-Wsign-compare') + +# catches overflow bugs, few false positives +add_gcc_warning('-Wtype-limits') + +# catches some dead code +add_gcc_warning('-Wunreachable-code') + +# catches treating string literals as non-const +add_gcc_warning('-Wwrite-strings', cxx_compiler) + +# turned on by -Wall, but we use offsetof on non-POD types frequently +add_gcc_warning('-Wno-invalid-offsetof', cxx_compiler) + +# catches objects passed by value to variadic functions. +check_and_add_gcc_warning('-Wclass-varargs') + +# catches issues around loops +check_and_add_gcc_warning('-Wloop-analysis') + +# catches C++ version forward-compat issues +check_and_add_gcc_warning('-Wc++11-compat-pedantic', cxx_compiler) +check_and_add_gcc_warning('-Wc++14-compat', cxx_compiler) +check_and_add_gcc_warning('-Wc++14-compat-pedantic', cxx_compiler) +check_and_add_gcc_warning('-Wc++1z-compat', cxx_compiler) + +# catches unintentional switch case fallthroughs +check_and_add_gcc_warning('-Wimplicit-fallthrough', cxx_compiler) + +# catches expressions used as a null pointer constant +# XXX: at the time of writing, the version of clang used on the OS X test +# machines has a bug that causes it to reject some valid files if both +# -Wnon-literal-null-conversion and -Wsometimes-uninitialized are +# specified. We work around this by instead using +# -Werror=non-literal-null-conversion, but we only do that when +# --enable-warnings-as-errors is specified so that no unexpected fatal +# warnings are produced. +check_and_add_gcc_warning('-Werror=non-literal-null-conversion', + when='--enable-warnings-as-errors') + +# catches string literals used in boolean expressions +check_and_add_gcc_warning('-Wstring-conversion') + +# catches inconsistent use of mutexes +check_and_add_gcc_warning('-Wthread-safety') + +# we inline 'new' and 'delete' in mozalloc +check_and_add_gcc_warning('-Wno-inline-new-delete', cxx_compiler) + +# Prevent the following GCC warnings from being treated as errors: +# too many false positives +check_and_add_gcc_warning('-Wno-error=maybe-uninitialized') + +# we don't want our builds held hostage when a platform-specific API +# becomes deprecated. +check_and_add_gcc_warning('-Wno-error=deprecated-declarations') + +# false positives depending on optimization +check_and_add_gcc_warning('-Wno-error=array-bounds') + +# can't get rid of those PGO warnings +check_and_add_gcc_warning('-Wno-error=coverage-mismatch', when='MOZ_PGO') + +# false positives during PGO +check_and_add_gcc_warning('-Wno-error=free-nonheap-object', when='MOZ_PGO') + +# We use mix of both POSIX and Win32 printf format across the tree, so format +# warnings are useless on mingw. +check_and_add_gcc_warning('-Wno-format', + when=depends(target)(lambda t: t.kernel == 'WINNT')) + +# Please keep these last in this file +add_old_configure_assignment('_WARNINGS_CFLAGS', warnings_cflags) +add_old_configure_assignment('_WARNINGS_CXXFLAGS', warnings_cxxflags) diff --git a/build/moz.configure/windows.configure b/build/moz.configure/windows.configure new file mode 100644 index 000000000..b9a3898a1 --- /dev/null +++ b/build/moz.configure/windows.configure @@ -0,0 +1,431 @@ +# -*- Mode: python; c-basic-offset: 4; 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/. + +option('--with-windows-version', nargs=1, default='603', + help='Windows SDK version to target. Win 8.1 (603) is currently' + 'the minimum supported version.') + +@depends(target) +def is_windows(target): + return target.kernel == 'WINNT' + + +@template +def depends_win(*args): + return depends_when(*args, when=is_windows) + + +@depends_win('--with-windows-version') +@imports(_from='__builtin__', _import='ValueError') +def valid_windows_version(value): + if not value: + die('Cannot build with --without-windows-version') + try: + version = int(value[0], 16) + if version in (0x603,): + return version + except ValueError: + pass + + die('Invalid value for --with-windows-version (%s)', value[0]) + + +option(env='WINDOWSSDKDIR', nargs=1, + help='Directory containing the Windows SDK') + +@depends_win('WINDOWSSDKDIR', host) +def windows_sdk_dir(value, host): + if value: + return value + if host.kernel != 'WINNT': + return () + + return tuple(x[1] for x in get_registry_values( + r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots' + r'\KitsRoot*')) + +# The Windows SDK 8.1 and 10 have different layouts. The former has +# $SDK/include/$subdir, while the latter has $SDK/include/$version/$subdir. +# The vcvars* scripts don't actually care about the version, they just take +# the last alphanumerically. +# The $SDK/lib directories always have version subdirectories, but while the +# versions match the one in $SDK/include for SDK 10, it's "winv6.3" for SDK +# 8.1. +@imports('os') +@imports('re') +@imports(_from='__builtin__', _import='sorted') +@imports(_from='__builtin__', _import='WindowsError') +def get_sdk_dirs(sdk, subdir): + def get_dirs_containing(sdk, stem, subdir): + base = os.path.join(sdk, stem) + try: + subdirs = [d for d in os.listdir(base) + if os.path.isdir(os.path.join(base, d))] + except WindowsError: + subdirs = [] + if not subdirs: + return () + if subdir in subdirs: + return (base,) + # At this point, either we have an incomplete or invalid SDK directory, + # or we exclusively have version numbers in subdirs. + return tuple(os.path.join(base, s) for s in subdirs + if os.path.isdir(os.path.join(base, s, subdir))) + + def categorize(dirs): + return {os.path.basename(d): d for d in dirs} + + include_dirs = categorize(get_dirs_containing(sdk, 'include', subdir)) + lib_dirs = categorize(get_dirs_containing(sdk, 'lib', subdir)) + + if 'include' in include_dirs: + include_dirs['winv6.3'] = include_dirs['include'] + del include_dirs['include'] + + valid_versions = sorted(set(include_dirs) & set(lib_dirs), reverse=True) + if valid_versions: + return namespace( + path=sdk, + lib=lib_dirs[valid_versions[0]], + include=include_dirs[valid_versions[0]], + ) + + +@imports(_from='mozbuild.shellutil', _import='quote') +def valid_windows_sdk_dir_result(value): + if value: + return '0x%04x in %s' % (value.version, quote(value.path)) + +@depends_win(c_compiler, windows_sdk_dir, valid_windows_version, + 'WINDOWSSDKDIR') +@checking('for Windows SDK', valid_windows_sdk_dir_result) +@imports(_from='__builtin__', _import='sorted') +@imports(_from='textwrap', _import='dedent') +def valid_windows_sdk_dir(compiler, windows_sdk_dir, target_version, + windows_sdk_dir_env): + if windows_sdk_dir_env: + windows_sdk_dir_env = windows_sdk_dir_env[0] + sdks = {} + for d in windows_sdk_dir: + sdk = get_sdk_dirs(d, 'um') + if sdk: + check = dedent('''\ + #include <winsdkver.h> + WINVER_MAXVER + ''') + um_dir = os.path.join(sdk.include, 'um') + shared_dir = os.path.join(sdk.include, 'shared') + result = try_preprocess(compiler.wrapper + [compiler.compiler] + + compiler.flags + + ['-I', um_dir, '-I', shared_dir], 'C', + check) + if result: + maxver = result.splitlines()[-1] + try: + maxver = int(maxver, 0) + except: + pass + else: + sdks[d] = maxver, sdk + continue + if d == windows_sdk_dir_env: + raise FatalCheckError( + 'Error while checking the version of the SDK in ' + 'WINDOWSSDKDIR (%s). Please verify it contains a valid and ' + 'complete SDK installation.' % windows_sdk_dir_env) + + valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True) + if valid_sdks: + biggest_version, sdk = sdks[valid_sdks[0]] + if not valid_sdks or biggest_version < target_version: + if windows_sdk_dir_env: + raise FatalCheckError( + 'You are targeting Windows version 0x%04x, but your SDK only ' + 'supports up to version 0x%04x. Install and use an updated SDK, ' + 'or target a lower version using --with-windows-version. ' + 'Alternatively, try running the Windows SDK Configuration Tool ' + 'and selecting a newer SDK. See ' + 'https://developer.mozilla.org/En/Windows_SDK_versions for ' + 'details on fixing this.' % (target_version, biggest_version)) + + raise FatalCheckError( + 'Cannot find a Windows SDK for version >= 0x%04x.' % target_version) + + return namespace( + path=sdk.path, + include=sdk.include, + lib=sdk.lib, + version=biggest_version, + ) + + +add_old_configure_assignment( + 'WINDOWSSDKDIR', + delayed_getattr(valid_windows_sdk_dir, 'path')) +add_old_configure_assignment( + 'MOZ_WINSDK_MAXVER', + depends(valid_windows_sdk_dir)( + lambda x: '0x%04X0000' % x.version if x else None)) + + +@imports(_from='mozbuild.shellutil', _import='quote') +def valid_ucrt_sdk_dir_result(value): + if value: + return '%s in %s' % (value.version, quote(value.path)) + +@depends_win(windows_sdk_dir, 'WINDOWSSDKDIR') +@checking('for Universal CRT SDK', valid_ucrt_sdk_dir_result) +@imports('os') +@imports(_from='__builtin__', _import='sorted') +@imports(_import='mozpack.path', _as='mozpath') +def valid_ucrt_sdk_dir(windows_sdk_dir, windows_sdk_dir_env): + if windows_sdk_dir_env: + windows_sdk_dir_env = windows_sdk_dir_env[0] + sdks = {} + for d in windows_sdk_dir: + sdk = get_sdk_dirs(d, 'ucrt') + if sdk: + version = os.path.basename(sdk.include) + # We're supposed to always find a version in the directory, because + # the 8.1 SDK, which doesn't have a version in the directory, doesn't + # contain the Universal CRT SDK. When the main SDK is 8.1, there + # is, however, supposed to be a reduced install of the SDK 10 + # with the UCRT. + if version != 'include': + sdks[d] = Version(version), sdk + continue + if d == windows_sdk_dir_env: + # When WINDOWSSDKDIR is set in the environment and we can't find the + # Universal CRT SDK, chances are this is a start-shell-msvc*.bat + # setup, where INCLUDE and LIB already contain the UCRT paths. + ucrt_includes = [ + p for p in os.environ.get('INCLUDE', '').split(os.pathsep) + if os.path.basename(p).lower() == 'ucrt' + ] + ucrt_libs = [ + p for p in os.environ.get('LIB', '').split(os.pathsep) + if os.path.basename(os.path.dirname(p)).lower() == 'ucrt' + ] + if ucrt_includes and ucrt_libs: + # Pick the first of each, since they are the ones that the + # compiler would look first. Assume they contain the SDK files. + include = os.path.dirname(ucrt_includes[0]) + lib = os.path.dirname(os.path.dirname(ucrt_libs[0])) + path = os.path.dirname(os.path.dirname(include)) + version = os.path.basename(include) + if version != 'include' and mozpath.basedir(lib, [path]): + sdks[d] = Version(version), namespace( + path=path, + include=include, + lib=lib, + ) + continue + raise FatalCheckError( + 'The SDK in WINDOWSSDKDIR (%s) does not contain the Universal ' + 'CRT.' % windows_sdk_dir_env) + + valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True) + if not valid_sdks: + raise FatalCheckError('Cannot find the Universal CRT SDK. ' + 'Please install it.') + + version, sdk = sdks[valid_sdks[0]] + + return namespace( + path=sdk.path, + include=sdk.include, + lib=sdk.lib, + version=version, + ) + + +@depends_win(c_compiler) +@imports('os') +def vc_path(c_compiler): + if c_compiler.type != 'msvc': + return + # Normally, we'd start from c_compiler.compiler, but for now, it's not the + # ideal full path to the compiler. At least, we're guaranteed find_program + # will get us the one we found in toolchain.configure. + cl = find_program(c_compiler.compiler) + result = os.path.dirname(cl) + while True: + next, p = os.path.split(result) + if next == result: + die('Cannot determine the Visual C++ directory the compiler (%s) ' + 'is in' % cl) + result = next + if p.lower() == 'bin': + break + return result + + +@depends_win(vc_path) +@checking('for the Debug Interface Access SDK', lambda x: x or 'not found') +@imports(_from='os.path', _import='isdir') +def dia_sdk_dir(vc_path): + if vc_path: + path = os.path.join(os.path.dirname(vc_path), 'DIA SDK') + if isdir(path): + return path + + +@depends_win(vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_dir) +@imports('os') +def include_path(vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_dir): + if not vc_path: + return + atlmfc_dir = os.path.join(vc_path, 'atlmfc', 'include') + if not os.path.isdir(atlmfc_dir): + die('Cannot find the ATL/MFC headers in the Visual C++ directory (%s). ' + 'Please install them.' % vc_path) + + winrt_dir = os.path.join(windows_sdk_dir.include, 'winrt') + if not os.path.isdir(winrt_dir): + die('Cannot find the WinRT headers in the Windows SDK directory (%s). ' + 'Please install them.' % windows_sdk_dir.path) + + includes = [] + include_env = os.environ.get('INCLUDE') + if include_env: + includes.append(include_env) + includes.extend(( + os.path.join(vc_path, 'include'), + atlmfc_dir, + os.path.join(windows_sdk_dir.include, 'shared'), + os.path.join(windows_sdk_dir.include, 'um'), + winrt_dir, + os.path.join(ucrt_sdk_dir.include, 'ucrt'), + )) + if dia_sdk_dir: + includes.append(os.path.join(dia_sdk_dir, 'include')) + # Set in the environment for old-configure + includes = os.pathsep.join(includes) + os.environ['INCLUDE'] = includes + return includes + +set_config('INCLUDE', include_path) + + +@depends_win(target, vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_dir) +@imports('os') +def lib_path(target, vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_dir): + if not vc_path: + return + vc_target = { + 'x86': '', + 'x86_64': 'amd64', + 'arm': 'arm', + }.get(target.cpu) + if vc_target is None: + return + # As vc_target can be '', and os.path.join will happily use the empty + # string, leading to a string ending with a backslash, that Make will + # interpret as a "string continues on next line" indicator, use variable + # args. + vc_target = (vc_target,) if vc_target else () + sdk_target = { + 'x86': 'x86', + 'x86_64': 'x64', + 'arm': 'arm', + }.get(target.cpu) + + atlmfc_dir = os.path.join(vc_path, 'atlmfc', 'lib', *vc_target) + if not os.path.isdir(atlmfc_dir): + die('Cannot find the ATL/MFC libraries in the Visual C++ directory (%s). ' + 'Please install them.' % vc_path) + + + libs = [] + lib_env = os.environ.get('LIB') + if lib_env: + libs.append(lib_env) + libs.extend(( + os.path.join(vc_path, 'lib', *vc_target), + atlmfc_dir, + os.path.join(windows_sdk_dir.lib, 'um', sdk_target), + os.path.join(ucrt_sdk_dir.lib, 'ucrt', sdk_target), + )) + if dia_sdk_dir: + libs.append(os.path.join(dia_sdk_dir, 'lib', *vc_target)) + # Set in the environment for old-configure + libs = os.pathsep.join(libs) + os.environ['LIB'] = libs + return libs + +set_config('LIB', lib_path) + + +option(env='MT', nargs=1, help='Path to the Microsoft Manifest Tool') + +@depends_win(valid_windows_sdk_dir) +@imports(_from='os', _import='environ') +@imports('platform') +def sdk_bin_path(valid_windows_sdk_dir): + if not valid_windows_sdk_dir: + return + + vc_host = { + 'x86': 'x86', + 'AMD64': 'x64', + }.get(platform.machine()) + + result = [ + environ['PATH'], + os.path.join(valid_windows_sdk_dir.path, 'bin', vc_host) + ] + if vc_host == 'x64': + result.append( + os.path.join(valid_windows_sdk_dir.path, 'bin', 'x86')) + return result + + +mt = check_prog('MT', depends_win()(lambda: ('mt.exe',)), input='MT', + paths=sdk_bin_path) + + +# Check that MT is not something unexpected like "magnetic tape manipulation +# utility". +@depends_win(mt) +@checking('whether MT is really Microsoft Manifest Tool', lambda x: bool(x)) +@imports('subprocess') +def valid_mt(path): + try: + out = subprocess.check_output([path]).splitlines() + out = '\n'.join(l for l in out + if 'Microsoft (R) Manifest Tool' in l) + if out: + return path + except subprocess.CalledProcessError: + pass + raise FatalCheckError('%s is not Microsoft Manifest Tool') + + +set_config('MSMANIFEST_TOOL', depends(valid_mt)(lambda x: bool(x))) + + +# Ultimately, this will move to toolchain.configure and be turned into a +# cross-platform check. +option(env='LD', nargs=1, help='Path to the linker') + +link = check_prog('LINK', depends_win()(lambda: ('link.exe',)), input='LD', + paths=vc_compiler_path) + +add_old_configure_assignment('LD', depends_win(link)(lambda x: x)) + + +# Normally, we'd just have CC, etc. set to absolute paths, but the build system +# doesn't currently handle properly the case where the paths contain spaces. +# Additionally, there's the issue described in toolchain.configure, in +# valid_compiler(). +@depends_win(sdk_bin_path) +@imports('os') +def alter_path(sdk_bin_path): + path = os.pathsep.join(sdk_bin_path) + os.environ['PATH'] = path + return path + +set_config('PATH', alter_path) |