mach
author Mike Hommey <mh+mozilla@glandium.org>
Thu, 30 Oct 2014 14:35:01 +0900
changeset 231090 a10056253b8c0e53addd916568a614eeca4d9e81
parent 198583 65bcc7b2a8a6f4e5475be2b8c7949f5bbae08369
permissions -rwxr-xr-x
Bug 1091118 - Part 2: Do not use the top-level cache file for freetype2 subconfigure. r=gps, a=lmandel We modify the environment before running freetype2 configure. When it uses the same cache file, it stores knowledge about that environment in the cache file. The cache file is then reused to configure in js/src, with yet again a different environment, which makes subconfigure.py clear the cache because of the differences. The configure in js/src is however invoked with the same environment as the main configure was invoked with (mostly), so without freetype2 on the way, reusing the cache for it works as expected. In fact, it works better with the cache because of things coming from mozconfig that are not exported. With freetype2 on the way, as mentioned above, the cache is cleared. Without the cache, js/src/configure does new detections with a possibly different environment, and stores that in the cache. Until the next build, which then uses that different cache for the top-level configure. This results in subtle differences in the HOST_CC/HOST_CXX variables on android builds because those variables are not exported from mozconfig, depending on PATH, what the builder was building before, and if the build is a clobber. Avoiding the freetype2 subconfigure writing its environment variables change to the top-level cache makes the cache never invalidate for js/src.

#!/bin/sh
# 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/.

# The beginning of this script is both valid shell and valid python,
# such that the script starts with the shell and is reexecuted with
# the right python.
'''which' python2.7 > /dev/null && exec python2.7 "$0" "$@" || exec python "$0" "$@"
'''

from __future__ import print_function, unicode_literals

import os
import sys

def ancestors(path):
    while path:
        yield path
        (path, child) = os.path.split(path)
        if child == "":
            break

def load_mach(topsrcdir):
    sys.path[0:0] = [os.path.join(topsrcdir, "build")]
    import mach_bootstrap
    return mach_bootstrap.bootstrap(topsrcdir)


def check_and_run_mach(dir_path, args):
    # If we find the mach bootstrap module, we are in the srcdir.
    mach_path = os.path.join(dir_path, 'build/mach_bootstrap.py')
    if os.path.isfile(mach_path):
        mach = load_mach(dir_path)
        sys.exit(mach.run(args))


def main(args):
    # Check whether the current directory is within a mach src or obj dir.
    for dir_path in ancestors(os.getcwd()):
        # If we find a "mozinfo.json" file, we are in the objdir.
        mozinfo_path = os.path.join(dir_path, 'mozinfo.json')
        if os.path.isfile(mozinfo_path):
            import json
            info = json.load(open(mozinfo_path))
            if 'mozconfig' in info and 'MOZCONFIG' not in os.environ:
                # If the MOZCONFIG environment variable is not already set, set it
                # to the value from mozinfo.json.  This will tell the build system
                # to look for a config file at the path in $MOZCONFIG rather than
                # its default locations.
                #
                # Note: subprocess requires native strings in os.environ on Windows
                os.environ[b'MOZCONFIG'] = str(info['mozconfig'])

            if 'topsrcdir' in info:
                # Continue searching for mach_bootstrap in the source directory.
                dir_path = info['topsrcdir']

        check_and_run_mach(dir_path, args)

    # If we didn't find a source path by scanning for a mozinfo.json, check
    # whether the directory containing this script is a source directory.
    check_and_run_mach(os.path.dirname(__file__), args)

    print('Could not run mach: No mach source directory found.')
    sys.exit(1)


if __name__ == '__main__':
    if sys.platform == 'win32':
        # This is a complete hack to work around the fact that Windows
        # multiprocessing needs to import the original module (ie: this
        # file), but only works if it has a .py extension.
        #
        # We do this by a sort of two-level function interposing. The first
        # level interposes forking.get_command_line() with our version defined
        # in my_get_command_line(). Our version of get_command_line will
        # replace the command string with the contents of the fork_interpose()
        # function to be used in the subprocess.
        #
        # The subprocess then gets an interposed imp.find_module(), which we
        # hack up to find 'mach' without the .py extension, since we already
        # know where it is (it's us!). If we're not looking for 'mach', then
        # the original find_module will suffice.
        #
        # See also: http://bugs.python.org/issue19946
        # And: https://bugzilla.mozilla.org/show_bug.cgi?id=914563
        import inspect
        from multiprocessing import forking
        global orig_command_line

        def fork_interpose():
            import imp
            import os
            import sys
            orig_find_module = imp.find_module
            def my_find_module(name, dirs):
                if name == 'mach':
                    path = os.path.join(dirs[0], 'mach')
                    f = open(path)
                    return (f, path, ('', 'r', imp.PY_SOURCE))
                return orig_find_module(name, dirs)

            # Don't allow writing bytecode file for mach module.
            orig_load_module = imp.load_module
            def my_load_module(name, file, path, description):
                # multiprocess.forking invokes imp.load_module manually and
                # hard-codes the name __parents_main__ as the module name.
                if name == '__parents_main__':
                    old_bytecode = sys.dont_write_bytecode
                    sys.dont_write_bytecode = True
                    try:
                        return orig_load_module(name, file, path, description)
                    finally:
                        sys.dont_write_bytecode = old_bytecode

                return orig_load_module(name, file, path, description)

            imp.find_module = my_find_module
            imp.load_module = my_load_module
            from multiprocessing.forking import main; main()

        def my_get_command_line():
            fork_code, lineno = inspect.getsourcelines(fork_interpose)
            # Remove the first line (for 'def fork_interpose():') and the three
            # levels of indentation (12 spaces).
            fork_string = ''.join(x[12:] for x in fork_code[1:])
            cmdline = orig_command_line()
            cmdline[2] = fork_string
            return cmdline
        orig_command_line = forking.get_command_line
        forking.get_command_line = my_get_command_line

    main(sys.argv[1:])