tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

configure.py (11964B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 import itertools
      6 import logging
      7 import os
      8 import pprint
      9 import sys
     10 import textwrap
     11 from collections.abc import Iterable
     12 
     13 base_dir = os.path.abspath(os.path.dirname(__file__))
     14 sys.path.insert(0, os.path.join(base_dir, "python", "mach"))
     15 sys.path.insert(0, os.path.join(base_dir, "python", "mozboot"))
     16 sys.path.insert(0, os.path.join(base_dir, "python", "mozbuild"))
     17 sys.path.insert(0, os.path.join(base_dir, "third_party", "python", "packaging"))
     18 sys.path.insert(0, os.path.join(base_dir, "testing", "mozbase", "mozfile"))
     19 sys.path.insert(0, os.path.join(base_dir, "testing", "mozbase", "mozshellutil"))
     20 sys.path.insert(0, os.path.join(base_dir, "third_party", "python", "six"))
     21 sys.path.insert(0, os.path.join(base_dir, "third_party", "python", "looseversion"))
     22 sys.path.insert(0, os.path.join(base_dir, "third_party", "python", "filelock"))
     23 import mozpack.path as mozpath
     24 from mach.requirements import MachEnvRequirements
     25 from mach.site import (
     26    CommandSiteManager,
     27    ExternalPythonSite,
     28    MachSiteManager,
     29    MozSiteMetadata,
     30    SitePackagesSource,
     31 )
     32 from mozbuild.backend.configenvironment import PartialConfigEnvironment
     33 from mozbuild.configure import TRACE, ConfigureSandbox
     34 from mozbuild.pythonutil import iter_modules_in_path
     35 from mozbuild.util import FileAvoidWrite
     36 
     37 if "MOZ_CONFIGURE_BUILDSTATUS" in os.environ:
     38 
     39    def buildstatus(message):
     40        print("BUILDSTATUS", message)
     41 
     42 else:
     43 
     44    def buildstatus(message):
     45        return
     46 
     47 
     48 def main(argv):
     49    # Check for CRLF line endings.
     50    with open(__file__) as fh:
     51        data = fh.read()
     52        if "\r" in data:
     53            print(
     54                "\n ***\n"
     55                " * The source tree appears to have Windows-style line endings.\n"
     56                " *\n"
     57                " * If using Git, Git is likely configured to use Windows-style\n"
     58                " * line endings.\n"
     59                " *\n"
     60                " * To convert the working copy to UNIX-style line endings, run\n"
     61                " * the following:\n"
     62                " *\n"
     63                " * $ git config core.autocrlf false\n"
     64                " * $ git config core.eof lf\n"
     65                " * $ git rm --cached -r .\n"
     66                " * $ git reset --hard\n"
     67                " *\n"
     68                " * If not using Git, the tool you used to obtain the source\n"
     69                " * code likely converted files to Windows line endings. See\n"
     70                " * usage information for that tool for more.\n"
     71                " ***",
     72                file=sys.stderr,
     73            )
     74            return 1
     75 
     76    config = {}
     77 
     78    sandbox = ConfigureSandbox(config, os.environ, argv)
     79 
     80    if not sandbox._help:
     81        # This limitation has mostly to do with GNU make. Since make can't represent
     82        # variables with spaces without correct quoting and many paths are used
     83        # without proper quoting, using paths with spaces commonly results in
     84        # targets or dependencies being treated as multiple paths. This, of course,
     85        # undermines the ability for make to perform up-to-date checks and makes
     86        # the build system not work very efficiently. In theory, a non-make build
     87        # backend will make this limitation go away. But there is likely a long tail
     88        # of things that will need fixing due to e.g. lack of proper path quoting.
     89        topsrcdir = os.path.realpath(os.path.dirname(__file__))
     90        if len(topsrcdir.split()) > 1:
     91            print(
     92                "Source directory cannot be located in a path with spaces: %s"
     93                % topsrcdir,
     94                file=sys.stderr,
     95            )
     96            return 1
     97        topobjdir = os.path.realpath(os.curdir)
     98        if len(topobjdir.split()) > 1:
     99            print(
    100                "Object directory cannot be located in a path with spaces: %s"
    101                % topobjdir,
    102                file=sys.stderr,
    103            )
    104            return 1
    105 
    106        # Do not allow topobjdir == topsrcdir
    107        if os.path.samefile(topsrcdir, topobjdir):
    108            print(
    109                "  ***\n"
    110                "  * Building directly in the main source directory is not allowed.\n"
    111                "  *\n"
    112                "  * To build, you must run configure from a separate directory\n"
    113                "  * (referred to as an object directory).\n"
    114                "  *\n"
    115                "  * If you are building with a mozconfig, you will need to change your\n"
    116                "  * mozconfig to point to a different object directory.\n"
    117                "  ***",
    118                file=sys.stderr,
    119            )
    120            return 1
    121        buildstatus("START_configure activate virtualenv")
    122        _activate_build_virtualenv()
    123        buildstatus("END_configure activate virtualenv")
    124 
    125    clobber_file = "CLOBBER"
    126    if not os.path.exists(clobber_file):
    127        # Simply touch the file.
    128        with open(clobber_file, "a"):
    129            pass
    130 
    131    if os.environ.get("MOZ_CONFIGURE_TRACE"):
    132        sandbox._logger.setLevel(TRACE)
    133 
    134    buildstatus("START_configure read moz.configure")
    135    sandbox.include_file(os.path.join(os.path.dirname(__file__), "moz.configure"))
    136    buildstatus("END_configure read moz.configure")
    137    buildstatus("START_configure run moz.configure")
    138    sandbox.run()
    139    buildstatus("END_configure run moz.configure")
    140 
    141    if sandbox._help:
    142        return 0
    143 
    144    buildstatus("START_configure config.status")
    145    logging.getLogger("moz.configure").info("Creating config.status")
    146    try:
    147        js_config = config.copy()
    148        pwd = os.getcwd()
    149        try:
    150            os.makedirs("js/src", exist_ok=True)
    151            os.chdir("js/src")
    152            # The build system frontend expects $objdir/js/src/config.status
    153            # to have $objdir/js/src as topobjdir.
    154            # We want forward slashes on all platforms.
    155            js_config["TOPOBJDIR"] += "/js/src"
    156            ret = config_status(js_config, execute=False)
    157            if ret:
    158                return ret
    159        finally:
    160            os.chdir(pwd)
    161        return config_status(config)
    162    finally:
    163        buildstatus("END_configure config.status")
    164 
    165 
    166 def check_unicode(obj):
    167    """Recursively check that all strings in the object are unicode strings."""
    168    if isinstance(obj, dict):
    169        result = True
    170        for k, v in obj.items():
    171            if not check_unicode(k):
    172                print("%s key is not unicode." % k, file=sys.stderr)
    173                result = False
    174            elif not check_unicode(v):
    175                print("%s value is not unicode." % k, file=sys.stderr)
    176                result = False
    177        return result
    178    if isinstance(obj, bytes):
    179        return False
    180    if isinstance(obj, str):
    181        return True
    182    if isinstance(obj, Iterable):
    183        return all(check_unicode(o) for o in obj)
    184    return True
    185 
    186 
    187 def config_status(config, execute=True):
    188    # Sanitize config data to feed config.status
    189    # Ideally, all the backend and frontend code would handle the booleans, but
    190    # there are so many things involved, that it's easier to keep config.status
    191    # untouched for now.
    192    def sanitize_config(v):
    193        if v is True:
    194            return "1"
    195        if v is False:
    196            return ""
    197        # Serialize types that look like lists and tuples as lists.
    198        if not isinstance(v, (bytes, str, dict)) and isinstance(v, Iterable):
    199            return list(v)
    200        return v
    201 
    202    sanitized_config = {}
    203    sanitized_config["substs"] = {
    204        k: sanitize_config(v)
    205        for k, v in config.items()
    206        if k
    207        not in (
    208            "DEFINES",
    209            "TOPSRCDIR",
    210            "TOPOBJDIR",
    211            "CONFIG_STATUS_DEPS",
    212        )
    213    }
    214    sanitized_config["defines"] = {
    215        k: sanitize_config(v) for k, v in config["DEFINES"].items()
    216    }
    217    sanitized_config["topsrcdir"] = config["TOPSRCDIR"]
    218    sanitized_config["topobjdir"] = config["TOPOBJDIR"]
    219    sanitized_config["mozconfig"] = config.get("MOZCONFIG")
    220 
    221    if not check_unicode(sanitized_config):
    222        print("Configuration should be all unicode.", file=sys.stderr)
    223        print("Please file a bug for the above.", file=sys.stderr)
    224        return 1
    225 
    226    # Create config.status. Eventually, we'll want to just do the work it does
    227    # here, when we're able to skip configure tests/use cached results/not rely
    228    # on autoconf.
    229    with open("config.status", "w", encoding="utf-8") as fh:
    230        fh.write(
    231            textwrap.dedent(
    232                """\
    233            #!%(python)s
    234            # coding=utf-8
    235            from mozbuild.configure.constants import *
    236        """
    237            )
    238            % {"python": config["PYTHON3"]}
    239        )
    240        for k, v in sorted(sanitized_config.items()):
    241            fh.write("%s = " % k)
    242            pprint.pprint(v, stream=fh, indent=4)
    243        fh.write(
    244            "__all__ = ['topobjdir', 'topsrcdir', 'defines', 'substs', 'mozconfig']"
    245        )
    246 
    247        if execute:
    248            fh.write(
    249                textwrap.dedent(
    250                    """
    251                if __name__ == '__main__':
    252                    from mozbuild.config_status import config_status
    253                    args = dict([(name, globals()[name]) for name in __all__])
    254                    config_status(**args)
    255            """
    256                )
    257            )
    258 
    259    partial_config = PartialConfigEnvironment(config["TOPOBJDIR"])
    260    partial_config.write_vars(sanitized_config)
    261 
    262    # Write out a file so the build backend knows to re-run configure when
    263    # relevant Python changes. Use FileAvoidWrite to only write if the
    264    # deps_content has changed to avoid invalidating Gradle's configuration cache
    265    with FileAvoidWrite("config_status_deps.in") as fh:
    266        for f in sorted(
    267            itertools.chain(
    268                config["CONFIG_STATUS_DEPS"],
    269                iter_modules_in_path(config["TOPOBJDIR"], config["TOPSRCDIR"]),
    270            )
    271        ):
    272            fh.write("%s\n" % mozpath.normpath(f))
    273 
    274    # Other things than us are going to run this file, so we need to give it
    275    # executable permissions.
    276    os.chmod("config.status", 0o755)
    277    if execute:
    278        from mozbuild.config_status import config_status
    279 
    280        return config_status(args=[], **sanitized_config)
    281    return 0
    282 
    283 
    284 def _activate_build_virtualenv():
    285    """Ensure that the build virtualenv is activated
    286 
    287    configure.py may be executed through Mach, or via "./configure, make".
    288    In the first case, the build virtualenv should already be activated.
    289    In the second case, we're likely being executed with the system Python, and must
    290    prepare the virtualenv and activate it ourselves.
    291    """
    292 
    293    version = ".".join(str(i) for i in sys.version_info[0:3])
    294    print(f"Using Python {version} from {sys.executable}")
    295 
    296    active_site = MozSiteMetadata.from_runtime()
    297    if active_site and active_site.site_name == "build":
    298        # We're already running within the "build" virtualenv, no additional work is
    299        # needed.
    300        return
    301 
    302    # We're using the system python (or are nested within a non-build mach-managed
    303    # virtualenv), so we should activate the build virtualenv as expected by the rest of
    304    # configure.
    305 
    306    topsrcdir = os.path.realpath(os.path.dirname(__file__))
    307 
    308    mach_site = MachSiteManager(
    309        topsrcdir,
    310        None,
    311        MachEnvRequirements(),
    312        ExternalPythonSite(sys.executable),
    313        SitePackagesSource.NONE,
    314    )
    315    mach_site.activate()
    316 
    317    from mach.util import get_virtualenv_base_dir
    318 
    319    build_site = CommandSiteManager.from_environment(
    320        topsrcdir,
    321        None,
    322        "build",
    323        get_virtualenv_base_dir(topsrcdir),
    324    )
    325    if not build_site.ensure():
    326        print("Created Python 3 virtualenv")
    327    build_site.activate()
    328 
    329 
    330 if __name__ == "__main__":
    331    sys.exit(main(sys.argv))