author Nathan Froyd <>
Sat, 02 Dec 2017 13:43:41 -0500
changeset 706648 9e92a30a9d52cc455f15b16c1b872d151c592ea4
parent 706570 ed8eade7d0d1bf5f8648383e861da77f776c37f2
child 706649 799ed2f23d7d72c2b444ff67848a4cf03277d1d8
permissions -rw-r--r--
Bug 1421792 - move --enable-trace-logging to moz.configure; r=nalexander

# -*- 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

def encoded_open(path, mode):
    encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
    return, 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')
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*(.+)$',
                if m:
                    mozconfig_autoconf =

    autoconf = autoconf[0] if autoconf else None

    for ac in (mozconfig_autoconf, autoconf, 'autoconf-2.13', 'autoconf2.13',
        if ac:
            autoconf = find_program(ac)
            if autoconf:
        fink = find_program('fink')
        if fink:
            autoconf = os.path.normpath(os.path.join(
                fink, '..', '..', 'lib', 'autoconf2.13', 'bin', 'autoconf'))
            brew = find_program('brew')
            if brew:
                autoconf = os.path.normpath(os.path.join(
                    brew, '..', '..', 'Cellar', 'autoconf213', '2.13', 'bin',

    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(_from='__builtin__', _import='sorted')
# 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',

    refresh = True
    if exists(old_configure):
        mtime = getmtime(old_configure)
        aclocal = os.path.join(build_env.topsrcdir, 'build', 'autoconf',
        for input in itertools.chain(
            (old_configure + '.in',
             os.path.join(os.path.dirname(old_configure), 'aclocal.m4')),
            if getmtime(input) > mtime:
            refresh = False

    if refresh:'Refreshing %s with %s', old_configure, autoconf)
        script = subprocess.check_output([
            shell, autoconf,
            '--localdir=%s' % os.path.dirname(old_configure),
            old_configure + '.in'])
        if not script:
            die('Generated old-configure is empty! Check that your autoconf 2.13 program works!')

        # 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:

    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) # noqa Python 2vs3
            log.debug('| %s', command)

        if mozconfig['path']:
            inject('# start of mozconfig values')
            items = {}
            for key, value in mozconfig['vars']['added'].items():
                items[key] = (value, 'added')
            for key, (old, value) in mozconfig['vars']['modified'].items():
                items[key] = (value, 'modified')

            for key, (value, action) in sorted(items.items()):
                inject("%s=%s # %s" % (key, quote(value), action))

            for t in ('env', 'vars'):
                for key in sorted(mozconfig[t]['removed'].keys()):
                    inject("unset %s # from %s" % (key, t))

            inject('# end of mozconfig values')

        # 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:

    return cmd

def old_configure_options(*options):
    for opt in options:
        option(opt, nargs='*', help='Help missing for old configure options')

    def all_options():
        return list(options)

    return depends(prepare_configure, extra_old_configure_args, all_options,

@imports(_from='__builtin__', _import='compile')
@imports(_from='__builtin__', _import='open')
@imports(_from='mozbuild.shellutil', _import='quote')
@imports(_from='mozbuild.shellutil', _import='split')
@imports(_from='mozbuild.util', _import='encode')
def old_configure(prepare_configure, extra_old_configure_args, all_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 += [
        for name, value in zip(all_options, options)
        if value.origin != 'default'

    env = dict(os.environ)
    extra_env = {}

    # 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:
        for arg in extra_old_configure_args:
            if arg.startswith('-'):
                k, v = arg.split('=', 1)
                extra_env[k] = v

    if extra_env:

    # For debugging purpose, in case it's not what we'd expect.
    log.debug('Running %s', quote(*cmd))
    if extra_env:
        log.debug('with extra environment: %s',
                  ' '.join('%s=%s' % pair for pair in extra_env.iteritems()))

    # 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):

    log_size = os.path.getsize('config.log')

    ret =, env=encode(env))
    if ret:
        with log.queue_debug():
            with encoded_open('config.log', 'r') as fh:
                for line in fh:
            log.error('old-configure failed')

    raw_config = {
        'split': split,
        'unique_list': unique_list,
    with encoded_open('', 'r') as fh:
        code = compile(, '', '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 # noqa

    # 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.
    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.
def set_old_configure_config(name, value):
    __sandbox__.set_config_impl(name, value)

# Same as set_old_configure_config, but for set_define.

def set_old_configure_define(name, value):
    __sandbox__.set_define_impl(name, value)

def post_old_configure(raw_config):
    for k, v in raw_config['substs']:
            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])


# 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.
@imports(_from='mozbuild.configure.options', _import='Option')
def remaining_mozconfig_options(_):
    helper = __sandbox__._helper
    for arg in helper:
        if helper._origins[arg] != 'mozconfig':
        name = arg.split('=', 1)[0]
        if name.isupper() and name not in __sandbox__._options:
            option = Option(env=name, nargs='*', help=name)

# Please do not add anything after remaining_mozconfig_options()