# -*- 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('>./config.log', '>>${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(extra_old_configure_args, all_options, *options) @old_configure_options( '--cache-file', '--datadir', '--enable-cookies', '--enable-cpp-rtti', '--enable-crashreporter', '--enable-dbus', '--enable-debug-js-modules', '--enable-directshow', '--enable-dtrace', '--enable-dump-painting', '--enable-extensions', '--enable-feeds', '--enable-gconf', '--enable-icf', '--enable-install-strip', '--enable-ios-target', '--enable-libjpeg-turbo', '--enable-libproxy', '--enable-llvm-hacks', '--enable-logrefcnt', '--enable-mobile-optimize', '--enable-necko-wifi', '--enable-negotiateauth', '--enable-nfc', '--enable-nspr-build', '--enable-official-branding', '--enable-parental-controls', '--enable-pref-extensions', '--enable-readline', '--enable-sandbox', '--enable-startup-notification', '--enable-startupcache', '--enable-strip', '--enable-synth-pico', '--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-cross-lib', '--with-debug-label', '--with-distribution-id', '--with-doc-include-dirs', '--with-doc-input-dirs', '--with-doc-output-dir', '--with-intl-api', '--with-ios-sdk', '--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-qemu-exe', '--with-sixgill', '--with-system-bz2', '--with-system-icu', '--with-system-libevent', '--with-system-nspr', '--with-system-nss', '--with-system-png', '--with-system-zlib', '--with-unify-dist', '--with-user-appdir', '--x-includes', '--x-libraries', ) def prepare_configure_options(extra_old_configure_args, 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' ] 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('-'): options.append(arg) else: k, v = arg.split('=', 1) extra_env[k] = v return namespace(options=options, extra_env=extra_env, 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('types') @imports(_from='mozbuild.shellutil', _import='quote') @imports(_from='mozbuild.shellutil', _import='split') @imports(_from='six', _import='exec_') def old_configure(prepare_configure, prepare_configure_options, altered_path): cmd = prepare_configure + prepare_configure_options.options extra_env = prepare_configure_options.extra_env env = dict(os.environ) if extra_env: env.update(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. 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, types.StringTypes) 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) def post_old_configure(raw_config): for k, v in raw_config['substs']: set_old_configure_config(k, v) for k, v in dict(raw_config['defines']).iteritems(): 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 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()