Bug 1256573 - Switch moz.configure to use @imports instead of @advanced. r=nalexander
authorMike Hommey <mh+mozilla@glandium.org>
Sun, 27 Mar 2016 11:40:13 +0900
changeset 328804 f4e24db79431e447e3f6b2758ec6dba4dca817c2
parent 328803 89b86fc355d7611a3a43872ee047bd32d651ddd1
child 328805 09c22809d1dccf9604f585fd57b07a797826fb68
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1256573 - Switch moz.configure to use @imports instead of @advanced. r=nalexander
--- a/build/moz.configure/checks.configure
+++ b/build/moz.configure/checks.configure
@@ -7,17 +7,18 @@
 # 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.
+@imports(_from='__builtin__', _import='Exception')
+@imports(_from='__builtin__', _import='__name__')
 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,)
@@ -84,21 +85,19 @@ def checking(what, callback=None):
 # - `allow_missing` indicates whether not finding the program is an error.
 # 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.
+@imports(_from='mozbuild.shellutil', _import='quote')
+@imports(_from='mozbuild.configure', _import='DependsFunction')
 def check_prog(var, progs, what=None, input=None, allow_missing=False):
-    from mozbuild.shellutil import quote
-    from mozbuild.configure import DependsFunction
     if input:
         # Wrap input with type checking and normalization.
         def input(value):
             if not value:
             if isinstance(value, str):
                 return (value,)
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -78,20 +78,18 @@ option(env='MOZCONFIG', nargs=1, help='M
 # 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.
          check_build_environment, '--help')
+@imports(_from='mozbuild.mozconfig', _import='MozconfigLoader')
 def mozconfig(current_project, mozconfig, old_configure, build_env, help):
-    from mozbuild.mozconfig import MozconfigLoader
     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
@@ -125,59 +123,54 @@ def mozconfig(current_project, mozconfig
 def old_configure_assignments(help):
     return []
 def extra_old_configure_args(help):
     return []
+@imports(_from='mozbuild.configure', _import='DependsFunction')
 def add_old_configure_assignment(var, value_func):
-    from mozbuild.configure import DependsFunction
     assert isinstance(value_func, DependsFunction)
     @depends(old_configure_assignments, value_func)
-    @advanced
+    @imports(_from='mozbuild.shellutil', _import='quote')
     def add_assignment(assignments, value):
         if value is None:
         if value is True:
             assignments.append('%s=1' % var)
         elif value is False:
             assignments.append('%s=' % var)
-            from mozbuild.shellutil import quote
             if isinstance(value, (list, tuple)):
                 value = ' '.join(quote(v) for v in value)
             assignments.append('%s=%s' % (var, quote(value)))
 def add_old_configure_arg(arg):
     def add_arg(args):
 option(env='PYTHON', nargs=1, help='Python interpreter')
 # Setup python virtualenv
 # ==============================================================
 @depends('PYTHON', check_build_environment, mozconfig)
+@imports(_from='mozbuild.configure.util', _import='LineIO')
+@imports(_from='mozbuild.virtualenv', _import='VirtualenvManager')
+@imports(_from='mozbuild.virtualenv', _import='verify_python_version')
 def virtualenv_python(env_python, build_env, mozconfig):
-    import os
-    import sys
-    import subprocess
-    from mozbuild.configure.util import LineIO
-    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']
@@ -223,46 +216,36 @@ def virtualenv_python(env_python, build_
         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
-    import distutils.sysconfig
     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
 # ==============================================================
-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.
 def early_options():
-    @advanced
+    @imports('__sandbox__')
     def early_options(help):
         return set(
-            for option in depends.__self__._options.itervalues()
+            for option in __sandbox__._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.
@@ -310,19 +293,21 @@ def wanted_mozconfig_variables(help):
 @depends(mozconfig, wanted_mozconfig_variables, '--help')
+# This gives access to the sandbox. Don't copy this blindly.
 def mozconfig_options(mozconfig, wanted_mozconfig_variables, help):
     if mozconfig['path']:
-        helper = command_line_helper()
+        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)
@@ -338,31 +323,26 @@ def mozconfig_options(mozconfig, wanted_
         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...
 def shell(mozillabuild):
-    import sys
     shell = 'sh'
     if mozillabuild:
         shell = mozillabuild[0] + '/msys/bin/sh'
     if sys.platform == 'win32':
         shell = shell + '.exe'
     return shell
@@ -456,30 +436,28 @@ def split_triplet(triplet):
 def config_sub(shell, triplet):
-    import subprocess
     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)
 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()
         host = value[0]
     return split_triplet(config_sub(shell, host))
@@ -669,17 +647,17 @@ add_old_configure_assignment('MOZ_BUILD_
 # set RELEASE_BUILD 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_BUILD)
+@imports(_from='__builtin__', _import='open')
 def milestone(build_env):
     milestone_path = os.path.join(build_env.topsrcdir,
     with open(milestone_path, 'r') as fh:
         milestone = fh.read().splitlines()[-1]
     is_nightly = is_release = None
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -1,30 +1,27 @@
 # -*- 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/.
 def encoded_open(path, mode):
-    import codecs
-    import sys
     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')
 def autoconf(mozconfig, autoconf):
-    import re
     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:
@@ -58,46 +55,46 @@ def autoconf(mozconfig, autoconf):
     return autoconf
 set_config('AUTOCONF', autoconf)
 # See comment in mozconfig_options() from build/moz.configure/init.configure
+# This gives access to the sandbox. Don't copy this blindly.
 def check_mozconfig_variables():
     # This escapes the sandbox. Don't copy this. This is only here because it
     # is a one off until old-configure is gone.
-    all_options = depends.__self__._options.itervalues()
+    all_options = __sandbox__._options.itervalues()
     @depends(early_options, wanted_mozconfig_variables)
     def check_mozconfig_variables(early_options, wanted_mozconfig_variables):
         for option in all_options:
             if (option.env and option.env not in early_options and
                     option.env not in wanted_mozconfig_variables):
                 die('You need to add `%s` to the `wanted_mozconfig_variables` '
                     'list in build/moz.configure/init.configure.', option.env)
 @depends('OLD_CONFIGURE', mozconfig, autoconf, check_build_environment, shell,
          old_configure_assignments, build_project)
+@imports(_from='__builtin__', _import='open')
+@imports(_from='__builtin__', _import='print')
+# Import getmtime without overwriting the sandbox os.path.
+@imports(_from='os.path', _import='getmtime')
+@imports(_from='mozbuild.shellutil', _import='quote')
 def prepare_configure(old_configure, mozconfig, autoconf, build_env, shell,
                       old_configure_assignments, build_project):
-    import glob
-    import itertools
-    import subprocess
-    # Import getmtime without overwriting the sandbox os.path.
-    from os.path import getmtime
-    from mozbuild.shellutil import quote
     # 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'):
@@ -364,25 +361,26 @@ def old_configure_options(*options):
     # Below are the configure flags used by comm-central.
+@imports(_from='__builtin__', _import='compile')
+@imports(_from='__builtin__', _import='open')
+@imports(_from='__builtin__', _import='zip')
+@imports(_from='mozbuild.shellutil', _import='quote')
 def old_configure(prepare_configure, extra_old_configure_args, all_options,
-    import logging
-    import os
-    import subprocess
-    import sys
-    from mozbuild.shellutil import quote
     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)
@@ -451,20 +449,18 @@ def set_old_configure_config(name, value
 # Same as set_old_configure_config, but for set_define.
 def set_old_configure_define(name, value):
     set_define(name, value)
 def post_old_configure(raw_config):
-    import types
     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])
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -5,19 +5,18 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 # yasm detection
 # ==============================================================
 yasm = check_prog('YASM', ['yasm'], allow_missing=True)
 @checking('yasm version')
 def yasm_version(yasm):
-    import subprocess
         version = Version(subprocess.check_output(
             [yasm, '--version']
         return version
     except subprocess.CalledProcessError as e:
         die('Failed to get yasm version: %s', e.message)
@@ -80,19 +79,18 @@ ccache = check_prog('CCACHE', progs=(), 
 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):
-    from mozbuild.shellutil import split as shell_split
     if ccache:
         if wrapper:
             return tuple([ccache] + shell_split(wrapper[0]))
             return (ccache,)
     elif wrapper:
         return tuple(shell_split(wrapper[0]))
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -1,74 +1,73 @@
 # -*- 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/.
 def die(*args):
     'Print an error and terminate configure.'
-    import sys
+@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.'''
-    from mozbuild.configure import ConfigureError
     raise ConfigureError(message)
 def is_absolute_or_relative(path):
-    import os
     if os.altsep and os.altsep in path:
         return True
     return os.sep in path
+@imports(_import='mozpack.path', _as='mozpath')
 def normsep(path):
-    import mozpack.path as mozpath
     return mozpath.normsep(path)
+# This unlocks the sandbox. Do not copy blindly.
+@imports(_import='__builtin__', _as='__builtins__')
 def find_program(file):
     if is_absolute_or_relative(file):
         return os.path.abspath(file) if os.path.isfile(file) else None
+    # We can't use @imports here because it imports at declaration time,
+    # and the declaration of find_program happens before we ensure the
+    # which module is available in sys.path somehow.
     from which import which, WhichError
         return normsep(which(file))
     except WhichError:
         return None
 def unique_list(l):
     result = []
     for i in l:
         if l not in result:
     return result
+@imports(_from='mozbuild.configure.util', _import='Version', _as='_Version')
 def Version(v):
     'A version number that can be compared usefully.'
-    from mozbuild.configure.util import Version as _Version
     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,
@@ -87,35 +86,34 @@ def deprecated_option(*args, **kwargs):
                 return func(value)
         return deprecated
     return decorator
 # from mozbuild.util import ReadOnlyNamespace as namespace
+@imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
 def namespace(**kwargs):
-    from mozbuild.util import ReadOnlyNamespace
     return ReadOnlyNamespace(**kwargs)
 # 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')
 def delayed_getattr(func, key):
-    @advanced
+    @imports(_from='__builtin__', _import='getattr')
     def result(value):
             return getattr(value, key)
         except AttributeError:
             # 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 None
--- a/moz.configure
+++ b/moz.configure
@@ -57,19 +57,18 @@ add_old_configure_assignment('COMPILE_EN
 def toolchain_include(value, help):
     if value:
         return 'build/moz.configure/toolchain.configure'
+@imports(_from='mozbuild.backend', _import='backends')
 def build_backends_choices(help):
-    from mozbuild.backend import backends
     return tuple(backends)
 option('--enable-build-backend', nargs='+', choices=build_backends_choices,
        help='Enable additional build backends')
 @depends('--enable-build-backend', '--enable-artifact-builds')
 def build_backend(backends, artifact_builds):
@@ -105,34 +104,32 @@ def perl_for_old_configure(value):
     return value
 add_old_configure_assignment('PERL', perl_for_old_configure)
 def perl_version_check(min_version):
     @checking('for minimum required perl version >= %s' % min_version)
-    @advanced
+    @imports('subprocess')
     def get_perl_version(perl):
-        import subprocess
             return Version(subprocess.check_output([perl, '-e', 'print $]']))
         except subprocess.CalledProcessError as e:
             die('Failed to get perl version: %s', e.message)
     def check_perl_version(version):
         if version < min_version:
             die('Perl %s or higher is required.', min_version)
     @checking('for full perl installation')
-    @advanced
+    @imports('subprocess')
     def has_full_perl_installation(perl):
-        import subprocess
         ret = subprocess.call(
             [perl, '-e', 'use Config; exit(!-d $Config{archlib})'])
         return ret == 0
     def require_full_perl_installation(has_full_perl_installation):
         if not has_full_perl_installation:
             die('Cannot find Config.pm or $Config{archlib}. '