build/moz.configure/init.configure
author Chris Pearce <cpearce@mozilla.com>
Wed, 16 Mar 2016 13:49:26 +0800
changeset 288911 49aa9281700cdacc521a7fa851abfc019831f1eb
parent 288832 1e381e3428d1b46233bb649d572ceddcc7cc0f91
child 289032 bea1d2b7531368d5b3a5228b430c3caa685a43d1
permissions -rw-r--r--
Bug 1256065. r=gerald

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

include('util.configure')

option(env='DIST', nargs=1, help='DIST directory')

# Do not allow objdir == srcdir builds.
# ==============================================================
@depends('--help', 'DIST')
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__), '..', '..')))

    set_config('TOPSRCDIR', topsrcdir)
    set_config('TOPOBJDIR', topobjdir)
    set_config('MOZ_BUILD_ROOT', topobjdir)
    if dist:
        set_config('DIST', normsep(dist[0]))
    else:
        set_config('DIST', os.path.join(topobjdir, 'dist'))

    if help:
        return

    if topsrcdir == topobjdir:
        error(
            '  ***\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 os.path.exists(os.path.join(topsrcdir, f))
    ]
    if conflict_files:
        error(
            '  ***\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)
        )


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')
@advanced
def mozconfig(current_project, mozconfig, old_configure, build_env, help):
    from mozbuild.mozconfig import MozconfigLoader

    if not old_configure:
        error('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


# Hacks related to old-configure
# ==============================

@depends('--help')
def old_configure_assignments(help):
    return []

@depends('--help')
def extra_old_configure_args(help):
    return []

@template
def add_old_configure_assignment(var, value):
    @depends(old_configure_assignments)
    @advanced
    def add_assignment(assignments):
        from mozbuild.shellutil import quote
        assignments.append('%s=%s' % (var, quote(value)))

@template
def add_old_configure_arg(arg):
    @depends(extra_old_configure_args)
    def add_arg(args):
        args.append(arg)


option(env='PYTHON', nargs=1, help='Python interpreter')

# Setup python virtualenv
# ==============================================================
@depends('PYTHON', check_build_environment, mozconfig)
@advanced
def virtualenv_python(env_python, build_env, mozconfig):
    import os
    import sys
    import subprocess
    from mozbuild.virtualenv import (
        VirtualenvManager,
        verify_python_version,
    )

    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]

    verify_python_version(sys.stderr)
    topsrcdir, topobjdir = build_env['TOPSRCDIR'], build_env['TOPOBJDIR']
    if topobjdir.endswith('/js/src'):
        topobjdir = topobjdir[:-7]

    manager = VirtualenvManager(
        topsrcdir, topobjdir,
        os.path.join(topobjdir, '_virtualenv'), sys.stdout,
        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:
            error('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):
        warn('Creating Python environment')
        manager.build(python)

    python = normsep(manager.python_path)

    if python != normsep(sys.executable):
        warn('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
    import distutils.sysconfig
    if not distutils.sysconfig.get_python_lib():
        error('Could not determine python site packages directory')

    set_config('PYTHON', python)
    add_old_configure_assignment('PYTHON', python)
    return python


# Inject mozconfig options
# ==============================================================
@template
@advanced
def command_line_helper():
    # This escapes the sandbox. Don't copy this. This is only here because
    # it is a one off and because the required functionality doesn't need
    # to be exposed for other usecases.
    return depends.__self__._helper


# All options defined above this point can't be injected in mozconfig_options
# below, so collect them.
@template
@advanced
def early_options():
    @depends('--help')
    def early_options(help):
        return set(
            option.env
            for option in depends.__self__._options.itervalues()
            if option.env
        )
    return early_options

early_options = early_options()

# At the moment, moz.configure doesn't have complete knowledge of all the
# supported options in mozconfig because of all that is still in old.configure.
# But we don't know all the options that moz.configure knows about until all
# moz.configure files are executed, so we keep a manual list here, that is
# checked in old.configure (we'll assume it's the last moz.configure file
# processed). This is tedious but necessary for now.
@depends('--help')
def wanted_mozconfig_variables(help):
     return set([
         'AUTOCONF',
         'AWK',
         'DISABLE_EXPORT_JS',
         'DISABLE_SHARED_JS',
         'DOXYGEN',
         'DSYMUTIL',
         'EXTERNAL_SOURCE_DIR',
         'GENISOIMAGE',
         'MOZILLABUILD',
         'MOZ_ARTIFACT_BUILDS',
         'MOZ_BUILD_APP',
         'PERL',
         'RPMBUILD',
         'TAR',
         'UNZIP',
         'XARGS',
         'ZIP',
     ])


@depends(mozconfig, wanted_mozconfig_variables)
def mozconfig_options(mozconfig, wanted_mozconfig_variables):
    if mozconfig['path']:
        helper = command_line_helper()
        warn('Adding configure options from %s' % mozconfig['path'])
        for arg in mozconfig['configure_args']:
            warn('  %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):
            # See comment above wanted_mozconfig_variables
            if key in wanted_mozconfig_variables:
                arg = '%s=%s' % (key, value)
                warn('  %s' % arg)
                helper.add(arg, origin='mozconfig', args=helper._args)

        for key, value in mozconfig['env']['added'].iteritems():
            add(key, value)
        for key, (_, value) in mozconfig['env']['modified'].iteritems():
            add(key, value)
        for key, value in mozconfig['vars']['added'].iteritems():
            add(key, value)
        for key, (_, value) in mozconfig['vars']['modified'].iteritems():
            add(key, value)


del command_line_helper


# Mozilla-Build
# ==============================================================
option(env='MOZILLABUILD', nargs=1,
       help='Path to Mozilla Build (Windows-only)')

# It feels dirty replicating this from python/mozbuild/mozbuild/mozconfig.py,
# but the end goal being that the configure script would go away...
@depends('MOZILLABUILD')
@advanced
def shell(mozillabuild):
    import sys

    shell = 'sh'
    if mozillabuild:
        shell = mozillabuild[0] + '/msys/bin/sh'
    if sys.platform == 'win32':
        shell = shell + '.exe'
    return 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')

@template
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_os = canonical_kernel = 'Darwin'
    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:
        error('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'
    elif cpu in ('s390', 's390x', 'x86_64', 'ia64'):
        canonical_cpu = cpu
    elif cpu in ('powerpc64', 'ppc64', 'powerpc64le', 'ppc64le'):
        canonical_cpu = 'ppc64'
    elif cpu in ('powerpc', 'ppc', 'rs6000') or cpu.startswith('powerpc'):
        canonical_cpu = 'ppc'
    elif cpu in ('Alpha', 'alpha', 'ALPHA'):
        canonical_cpu = 'Alpha'
    elif cpu.startswith('hppa') or cpu == 'parisc':
        canonical_cpu = 'hppa'
    elif cpu.startswith('sparc') or cpu == 'sun4u':
        canonical_cpu = 'sparc'
    elif cpu.startswith('arm'):
        canonical_cpu = 'arm'
    elif cpu in ('mips', 'mipsel'):
        canonical_cpu = 'mips32'
    elif cpu in ('mips64', 'mips64el'):
        canonical_cpu = 'mips64'
    elif cpu.startswith('aarch64'):
        canonical_cpu = 'aarch64'
    else:
        canonical_cpu = cpu

    return namespace(
        alias=triplet,
        cpu=canonical_cpu,
        kernel=canonical_kernel,
        os=canonical_os,
        raw_cpu=cpu,
        raw_os=os,
    )


@depends('--host', shell)
@advanced
def host(value, shell):
    if not value:
        import subprocess
        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(host)


@depends('--target', host)
def target(value, host):
    if not value:
        return host
    return split_triplet(value[0])


@depends(host, target)
def host_and_target_for_old_configure(host, target):
    # Autoconf needs these set
    add_old_configure_arg('--host=%s' % host.alias)
    add_old_configure_arg('--target=%s' % target.alias)


# 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 == 'Linux' and target.os == 'GNU':
        os_target = target.kernel
        os_arch = target.kernel
    else:
        os_target = target.os
        os_arch = target.kernel
    add_old_configure_assignment('OS_TARGET', os_target)
    set_config('OS_TARGET', os_target)
    add_old_configure_assignment('OS_ARCH', os_arch)
    set_config('OS_ARCH', os_arch)

    if target.os == 'Darwin' and target.cpu == 'x86':
        os_test = 'i386'
    else:
        os_test = target.raw_cpu
    add_old_configure_assignment('OS_TEST', os_test)
    set_config('OS_TEST', os_test)

    add_old_configure_assignment('CPU_ARCH', target.cpu)
    set_config('CPU_ARCH', target.cpu)

    if target.cpu in ('x86', 'x86_64'):
        set_config('INTEL_ARCHITECTURE', '1')

    set_config('TARGET_CPU', target.raw_cpu)
    set_config('TARGET_OS', target.raw_os)

@depends(host)
def host_variables(host):
    if host.kernel == 'kFreeBSD':
        os_arch = 'GNU_kFreeBSD'
    else:
        os_arch = host.kernel
    add_old_configure_assignment('HOST_OS_ARCH', os_arch)
    set_config('HOST_OS_ARCH', os_arch)


# 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:
        imply_option(app.format('--enable-project'))


@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')
def include_project_configure(project, external_source_dir, build_env, help):
    if not project:
        error('--enable-project is required.')

    base_dir = build_env['TOPSRCDIR']
    if external_source_dir:
        set_config('EXTERNAL_SOURCE_DIR', external_source_dir[0])
        add_old_configure_assignment('EXTERNAL_SOURCE_DIR',
                                     external_source_dir[0])
        base_dir = os.path.join(base_dir, external_source_dir[0])

    path = os.path.join(base_dir, project[0], 'moz.configure')
    if not os.path.exists(path):
        error('Cannot find project %s' % project[0])
    return path

@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']))
    add_old_configure_assignment('MOZ_BUILD_APP', ret)
    return ret


# This is temporary until js/src/configure and configure are merged.
# Use instead of option() in js/moz.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':
            add_old_configure_arg(value.format(opt.option))


include(include_project_configure)