Bug 1256573 - Switch moz.configure to use @imports instead of @advanced. r?nalexander draft
authorMike Hommey <mh+mozilla@glandium.org>
Sun, 27 Mar 2016 11:40:13 +0900
changeset 345023 a0d6f3040da8e5a8783392dc6692a851631a3b97
parent 345022 7f71ffb3983e6c252cd00a519bb4a7d01789fb41
child 345024 67514b1a7f0f164ff3f152989583b8534570060a
push id13997
push userbmo:mh+mozilla@glandium.org
push dateSun, 27 Mar 2016 22:55:22 +0000
reviewersnalexander
bugs1256573
milestone48.0a1
Bug 1256573 - Switch moz.configure to use @imports instead of @advanced. r?nalexander
build/moz.configure/checks.configure
build/moz.configure/init.configure
build/moz.configure/old.configure
build/moz.configure/toolchain.configure
build/moz.configure/util.configure
moz.configure
--- 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.
 @template
-@advanced
+@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'))
 # 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
-@advanced
+@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.
         @depends(input)
         def input(value):
             if not value:
                 return
             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.
 @depends('MOZ_CURRENT_PROJECT', 'MOZCONFIG', 'OLD_CONFIGURE',
          check_build_environment, '--help')
-@advanced
+@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 []
 
 @depends('--help')
 def extra_old_configure_args(help):
     return []
 
 @template
-@advanced
+@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:
             return
         if value is True:
             assignments.append('%s=1' % var)
         elif value is False:
             assignments.append('%s=' % var)
         else:
-            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)))
 
 @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
+@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):
-    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
 # ==============================================================
-@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
 def early_options():
     @depends('--help')
-    @advanced
+    @imports('__sandbox__')
     def early_options(help):
         return set(
             option.env
-            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):
          'WITHOUT_X',
          'XARGS',
          'YASM',
          'ZIP',
      ])
 
 
 @depends(mozconfig, wanted_mozconfig_variables, '--help')
+# This gives access to the sandbox. Don't copy this blindly.
+@imports('__sandbox__')
 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...
 @depends('MOZILLABUILD')
-@advanced
+@imports('sys')
 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):
         kernel=canonical_kernel,
         os=canonical_os,
         raw_cpu=cpu,
         raw_os=os,
     )
 
 
 @template
-@advanced
+@imports('subprocess')
 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)
-@advanced
+@imports('subprocess')
 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(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)
 @depends(check_build_environment)
-@advanced
+@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 = 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/.
 
 @template
-@advanced
+@imports('codecs')
+@imports('sys')
 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')
-@advanced
+@imports('re')
 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*(.+)$',
                              assignment)
                 if m:
@@ -58,46 +55,45 @@ def autoconf(mozconfig, autoconf):
 
     return autoconf
 
 set_config('AUTOCONF', autoconf)
 
 
 # See comment in mozconfig_options() from build/moz.configure/init.configure
 @template
-@advanced
+# This gives access to the sandbox. Don't copy this blindly.
+@imports('__sandbox__')
 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)
 
 check_mozconfig_variables()
 
 
 @depends('OLD_CONFIGURE', mozconfig, autoconf, check_build_environment, shell,
          old_configure_assignments, build_project)
-@advanced
+@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='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 +360,25 @@ def old_configure_options(*options):
     '--x-libraries',
 
     # Below are the configure flags used by comm-central.
     '--enable-ldap',
     '--enable-mapi',
     '--enable-calendar',
     '--enable-incomplete-external-linkage',
 )
-@advanced
+@imports(_from='__builtin__', _import='compile')
+@imports(_from='__builtin__', _import='zip')
+@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):
-    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 += [
         value.format(name)
         for name, value in zip(all_options, options)
@@ -451,20 +447,18 @@ def set_old_configure_config(name, value
 
 # Same as set_old_configure_config, but for set_define.
 @template
 def set_old_configure_define(name, value):
     set_define(name, value)
 
 
 @depends(old_configure)
-@advanced
+@imports('types')
 def post_old_configure(raw_config):
-    import types
-
     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',
--- 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)
 
 @depends_if(yasm)
 @checking('yasm version')
-@advanced
+@imports('subprocess')
 def yasm_version(yasm):
-    import subprocess
     try:
         version = Version(subprocess.check_output(
             [yasm, '--version']
         ).splitlines()[0].split()[1])
         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=(), 
 
 @depends_if(ccache)
 def using_ccache(ccache):
     return True
 
 set_config('MOZ_USING_CCACHE', using_ccache)
 
 @depends('--with-compiler-wrapper', ccache)
-@advanced
+@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]))
         else:
             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/.
 
 @template
-@advanced
+@imports('sys')
 def die(*args):
     'Print an error and terminate configure.'
-    import sys
     log.error(*args)
     sys.exit(1)
 
 
 @template
-@advanced
+@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)
 
 
 @template
-@advanced
+@imports('os')
 def is_absolute_or_relative(path):
-    import os
     if os.altsep and os.altsep in path:
         return True
     return os.sep in path
 
 
 @template
-@advanced
+@imports(_import='mozpack.path', _as='mozpath')
 def normsep(path):
-    import mozpack.path as mozpath
     return mozpath.normsep(path)
 
 
 @template
-@advanced
+# 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
     try:
         return normsep(which(file))
     except WhichError:
         return None
 
 
 @template
 def unique_list(l):
     result = []
     for i in l:
         if l not in result:
             result.append(i)
     return result
 
 @template
-@advanced
+@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
 @template
-@advanced
+@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')
 @template
 def delayed_getattr(func, key):
     @depends(func)
-    @advanced
+    @imports(_from='__builtin__', _import='getattr')
     def result(value):
         try:
             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'
 
 include(toolchain_include)
 
 
 @depends('--help')
-@advanced
+@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)
 
 @template
 def perl_version_check(min_version):
     @depends(perl)
     @checking('for minimum required perl version >= %s' % min_version)
-    @advanced
+    @imports('subprocess')
     def get_perl_version(perl):
-        import subprocess
         try:
             return Version(subprocess.check_output([perl, '-e', 'print $]']))
         except subprocess.CalledProcessError as e:
             die('Failed to get perl version: %s', e.message)
 
     @depends(get_perl_version)
     def check_perl_version(version):
         if version < min_version:
             die('Perl %s or higher is required.', min_version)
 
     @depends(perl)
     @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
 
     @depends(has_full_perl_installation)
     def require_full_perl_installation(has_full_perl_installation):
         if not has_full_perl_installation:
             die('Cannot find Config.pm or $Config{archlib}. '