build/moz.configure/old.configure
author Mike Hommey <mh+mozilla@glandium.org>
Thu, 21 May 2020 10:11:21 +0000
changeset 531541 94877a07905449afb0dac9c6426c6c47bf870e49
parent 530225 2c4692d0b6cbdcfef0ae02bf1c0abb20d96d898d
child 531542 89475adf15b6407aca0d1061b09e92a6673196a9
permissions -rw-r--r--
Bug 1639815 - Move --with-qemu-exe and --with-cross-lib to python configure. r=froydnj Differential Revision: https://phabricator.services.mozilla.com/D76285

# -*- 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='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)
                # Check whether we've exported any known-broken variables.
                m = re.match('(?:export\s+)?(?:CC|CXX)\s*:?=\s*(?:.+)$',
                             assignment)
                if m:
                    die('Setting the value of CC or CXX with "mk_add_options" '
                        'can cause the build to fail unexpectedly. Please '
                        'change your mozconfig to instead use '
                        '"ac_add_options", e.g. ac_add_options '
                        'CC=my-custom-cc".')

    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(mozconfig)
def prepare_mozconfig(mozconfig):
    if mozconfig['path']:
        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 t in ('env', 'vars'):
            for key in mozconfig[t]['removed'].keys():
                items[key] = (None, 'removed ' + t)
        return items


@depends('OLD_CONFIGURE', 'MOZILLABUILD', prepare_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')
@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')
@imports(_from='tempfile', _import='NamedTemporaryFile')
@imports(_from='os', _import='environ')
@imports(_from='os', _import='remove')
@imports(_from='os', _import='rename')
@imports(_from='subprocess', _import='CalledProcessError')
@imports(_from='__builtin__', _import='OSError')
def prepare_configure(old_configure, mozillabuild, 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)

        try:
            script = subprocess.check_output([
                shell, autoconf,
                '--localdir=%s' % os.path.dirname(old_configure),
                old_configure + '.in'])
        except CalledProcessError as exc:
            # Autoconf on win32 may break due to a bad $PATH.  Let the user know
            # their $PATH is suspect.
            if mozillabuild:
                mozillabuild_path = normsep(mozillabuild[0])
                sh_path = normsep(find_program('sh'))
                if mozillabuild_path not in sh_path:
                    log.warning("The '{}msys/bin' directory is not first in $PATH. "
                                "This may cause autoconf to fail. ($PATH is currently "
                                "set to: {})".format(mozillabuild_path, environ[
                        'PATH']))
            die('autoconf exited with return code {}'.format(exc.returncode))

        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(b'>./config.log', b'>>${CONFIG_LOG=./config.log}')

        with NamedTemporaryFile(mode='wb', prefix=os.path.basename(old_configure),
                                dir=os.path.dirname(old_configure), delete=False) as fh:
            fh.write(script)

        try:
            rename(fh.name, old_configure)
        except OSError:
            try:
                # Likely the file already existed (on Windows). Retry after removing it.
                remove(old_configure)
                rename(fh.name, old_configure)
            except OSError as e:
                die('Failed re-creating old-configure: %s' % e.message)

    cmd = [shell, old_configure]
    with 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:
            inject('# start of mozconfig values')
            for key, (value, action) in sorted(mozconfig.items()):
                if action.startswith('removed '):
                    inject("unset %s # from %s" % (
                        key, action[len('removed '):]))
                else:
                    inject("%s=%s # %s" % (key, quote(value), action))

            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 k, v in old_configure_assignments:
            inject('%s=%s' % (k, quote(v)))

    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(host_for_sub_configure, target_for_sub_configure, all_options, *options)


@old_configure_options(
    '--cache-file',
    '--datadir',
    '--enable-cpp-rtti',
    '--enable-crashreporter',
    '--enable-dbus',
    '--enable-debug-js-modules',
    '--enable-dtrace',
    '--enable-dump-painting',
    '--enable-extensions',
    '--enable-icf',
    '--enable-install-strip',
    '--enable-libproxy',
    '--enable-logrefcnt',
    '--enable-mobile-optimize',
    '--enable-necko-wifi',
    '--enable-negotiateauth',
    '--enable-nspr-build',
    '--enable-official-branding',
    '--enable-parental-controls',
    '--enable-pref-extensions',
    '--enable-readline',
    '--enable-sandbox',
    '--enable-startupcache',
    '--enable-strip',
    '--enable-system-cairo',
    '--enable-system-extension-dirs',
    '--enable-system-pixman',
    '--enable-universalchardet',
    '--enable-updater',
    '--enable-xul',
    '--enable-zipwriter',
    '--includedir',
    '--libdir',
    '--prefix',
    '--with-android-distribution-directory',
    '--with-android-max-sdk',
    '--with-android-min-sdk',
    '--with-app-basename',
    '--with-app-name',
    '--with-branding',
    '--with-debug-label',
    '--with-distribution-id',
    '--with-intl-api',
    '--with-jitreport-granularity',
    '--with-macbundlename-prefix',
    '--with-nspr-cflags',
    '--with-nspr-exec-prefix',
    '--with-nspr-libs',
    '--with-nspr-prefix',
    '--with-nss-exec-prefix',
    '--with-nss-prefix',
    '--with-sixgill',
    '--with-system-icu',
    '--with-system-libevent',
    '--with-system-nspr',
    '--with-system-nss',
    '--with-system-png',
    '--with-system-zlib',
    '--with-user-appdir',
    '--x-includes',
    '--x-libraries',
)
def prepare_configure_options(host, target, all_options, *options):
    # 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.
    options = [
        value.format(name)
        for name, value in zip(all_options, options)
        if value.origin != 'default'
    ] + [host, target]

    return namespace(options=options, all_options=all_options)


@depends(prepare_configure, prepare_configure_options, altered_path)
@imports(_from='__builtin__', _import='compile')
@imports(_from='__builtin__', _import='open')
@imports('logging')
@imports('os')
@imports('subprocess')
@imports('sys')
@imports(_from='mozbuild.shellutil', _import='quote')
@imports(_from='mozbuild.shellutil', _import='split')
@imports(_from='six', _import='exec_')
@imports(_from='six', _import='iteritems')
@imports(_from='six', _import='string_types')
def old_configure(prepare_configure, prepare_configure_options, altered_path):
    cmd = prepare_configure + prepare_configure_options.options

    env = dict(os.environ)

    # 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.
    logger = logging.getLogger('moz.configure')
    config_log = None
    for handler in logger.handlers:
        if isinstance(handler, logging.FileHandler):
            config_log = handler
            config_log.close()
            logger.removeHandler(config_log)
            env['CONFIG_LOG'] = config_log.baseFilename
            log_size = os.path.getsize(config_log.baseFilename)
            break

    if altered_path:
        env['PATH'] = altered_path

    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                            env=env)
    while True:
        line = proc.stdout.readline()
        if not line:
            break
        log.info(line.rstrip())

    ret = proc.wait()
    if ret:
        with log.queue_debug():
            if config_log:
                with open(config_log.baseFilename, 'r') as fh:
                    fh.seek(log_size)
                    for line in fh:
                        log.debug(line.rstrip())
            log.error('old-configure failed')
        sys.exit(ret)

    if config_log:
        # Create a new handler in append mode
        handler = logging.FileHandler(config_log.baseFilename, mode='a', delay=True)
        handler.setFormatter(config_log.formatter)
        logger.addHandler(handler)

    raw_config = {
        'split': split,
        'unique_list': unique_list,
    }
    with open('config.data', 'r') as fh:
        code = compile(fh.read(), 'config.data', 'exec')
        exec_(code, raw_config)

    # Ensure all the flags known to old-configure appear in the
    # @old_configure_options above.
    all_options = set(prepare_configure_options.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')

    for c in ('substs', 'defines'):
        raw_config[c] = [
            (k[1:-1], v[1:-1] if isinstance(v, string_types) else v)
            for k, v in raw_config[c]
        ]

    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(_from='six', _import='iteritems')
def post_old_configure(raw_config):
    for k, v in raw_config['substs']:
        set_old_configure_config(k, v)

    for k, v in iteritems(dict(raw_config['defines'])):
        set_old_configure_define(k, v)

    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 list(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()