mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D186092
		
			
				
	
	
		
			290 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			290 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
#!/usr/bin/env 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/.
 | 
						|
 | 
						|
"""
 | 
						|
Setup mozbase packages for development.
 | 
						|
 | 
						|
Packages may be specified as command line arguments.
 | 
						|
If no arguments are given, install all packages.
 | 
						|
 | 
						|
See https://wiki.mozilla.org/Auto-tools/Projects/Mozbase
 | 
						|
"""
 | 
						|
 | 
						|
import os
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
from optparse import OptionParser
 | 
						|
from subprocess import PIPE
 | 
						|
 | 
						|
try:
 | 
						|
    from subprocess import check_call as call
 | 
						|
except ImportError:
 | 
						|
    from subprocess import call
 | 
						|
 | 
						|
 | 
						|
# directory containing this file
 | 
						|
here = os.path.dirname(os.path.abspath(__file__))
 | 
						|
 | 
						|
# all python packages
 | 
						|
mozbase_packages = [
 | 
						|
    i for i in os.listdir(here) if os.path.exists(os.path.join(here, i, "setup.py"))
 | 
						|
]
 | 
						|
 | 
						|
# testing: https://wiki.mozilla.org/Auto-tools/Projects/Mozbase#Tests
 | 
						|
test_packages = ["mock"]
 | 
						|
 | 
						|
# documentation: https://wiki.mozilla.org/Auto-tools/Projects/Mozbase#Documentation
 | 
						|
extra_packages = ["sphinx"]
 | 
						|
 | 
						|
 | 
						|
def cycle_check(order, dependencies):
 | 
						|
    """ensure no cyclic dependencies"""
 | 
						|
    order_dict = dict([(j, i) for i, j in enumerate(order)])
 | 
						|
    for package, deps in dependencies.items():
 | 
						|
        index = order_dict[package]
 | 
						|
        for d in deps:
 | 
						|
            assert index > order_dict[d], "Cyclic dependencies detected"
 | 
						|
 | 
						|
 | 
						|
def info(directory):
 | 
						|
    "get the package setup.py information"
 | 
						|
 | 
						|
    assert os.path.exists(os.path.join(directory, "setup.py"))
 | 
						|
 | 
						|
    # setup the egg info
 | 
						|
    try:
 | 
						|
        call([sys.executable, "setup.py", "egg_info"], cwd=directory, stdout=PIPE)
 | 
						|
    except subprocess.CalledProcessError:
 | 
						|
        print("Error running setup.py in %s" % directory)
 | 
						|
        raise
 | 
						|
 | 
						|
    # get the .egg-info directory
 | 
						|
    egg_info = [entry for entry in os.listdir(directory) if entry.endswith(".egg-info")]
 | 
						|
    assert len(egg_info) == 1, "Expected one .egg-info directory in %s, got: %s" % (
 | 
						|
        directory,
 | 
						|
        egg_info,
 | 
						|
    )
 | 
						|
    egg_info = os.path.join(directory, egg_info[0])
 | 
						|
    assert os.path.isdir(egg_info), "%s is not a directory" % egg_info
 | 
						|
 | 
						|
    # read the package information
 | 
						|
    pkg_info = os.path.join(egg_info, "PKG-INFO")
 | 
						|
    info_dict = {}
 | 
						|
    for line in open(pkg_info).readlines():
 | 
						|
        if not line or line[0].isspace():
 | 
						|
            continue  # XXX neglects description
 | 
						|
        assert ":" in line
 | 
						|
        key, value = [i.strip() for i in line.split(":", 1)]
 | 
						|
        info_dict[key] = value
 | 
						|
 | 
						|
    return info_dict
 | 
						|
 | 
						|
 | 
						|
def get_dependencies(directory):
 | 
						|
    "returns the package name and dependencies given a package directory"
 | 
						|
 | 
						|
    # get the package metadata
 | 
						|
    info_dict = info(directory)
 | 
						|
 | 
						|
    # get the .egg-info directory
 | 
						|
    egg_info = [
 | 
						|
        entry for entry in os.listdir(directory) if entry.endswith(".egg-info")
 | 
						|
    ][0]
 | 
						|
 | 
						|
    # read the dependencies
 | 
						|
    requires = os.path.join(directory, egg_info, "requires.txt")
 | 
						|
    dependencies = []
 | 
						|
    if os.path.exists(requires):
 | 
						|
        for line in open(requires):
 | 
						|
            line = line.strip()
 | 
						|
            # in requires.txt file, a dependency is a non empty line
 | 
						|
            # Also lines like [device] are sections to mark optional
 | 
						|
            # dependencies, we don't want those sections.
 | 
						|
            if line and not (line.startswith("[") and line.endswith("]")):
 | 
						|
                dependencies.append(line)
 | 
						|
 | 
						|
    # return the information
 | 
						|
    return info_dict["Name"], dependencies
 | 
						|
 | 
						|
 | 
						|
def dependency_info(dep):
 | 
						|
    "return dictionary of dependency information from a dependency string"
 | 
						|
    retval = dict(Name=None, Type=None, Version=None)
 | 
						|
    for joiner in ("==", "<=", ">="):
 | 
						|
        if joiner in dep:
 | 
						|
            retval["Type"] = joiner
 | 
						|
            name, version = [i.strip() for i in dep.split(joiner, 1)]
 | 
						|
            retval["Name"] = name
 | 
						|
            retval["Version"] = version
 | 
						|
            break
 | 
						|
    else:
 | 
						|
        retval["Name"] = dep.strip()
 | 
						|
    return retval
 | 
						|
 | 
						|
 | 
						|
def unroll_dependencies(dependencies):
 | 
						|
    """
 | 
						|
    unroll a set of dependencies to a flat list
 | 
						|
 | 
						|
    dependencies = {'packageA': set(['packageB', 'packageC', 'packageF']),
 | 
						|
                    'packageB': set(['packageC', 'packageD', 'packageE', 'packageG']),
 | 
						|
                    'packageC': set(['packageE']),
 | 
						|
                    'packageE': set(['packageF', 'packageG']),
 | 
						|
                    'packageF': set(['packageG']),
 | 
						|
                    'packageX': set(['packageA', 'packageG'])}
 | 
						|
    """
 | 
						|
 | 
						|
    order = []
 | 
						|
 | 
						|
    # flatten all
 | 
						|
    packages = set(dependencies.keys())
 | 
						|
    for deps in dependencies.values():
 | 
						|
        packages.update(deps)
 | 
						|
 | 
						|
    while len(order) != len(packages):
 | 
						|
        for package in packages.difference(order):
 | 
						|
            if set(dependencies.get(package, set())).issubset(order):
 | 
						|
                order.append(package)
 | 
						|
                break
 | 
						|
        else:
 | 
						|
            raise AssertionError("Cyclic dependencies detected")
 | 
						|
 | 
						|
    cycle_check(order, dependencies)  # sanity check
 | 
						|
 | 
						|
    return order
 | 
						|
 | 
						|
 | 
						|
def main(args=sys.argv[1:]):
 | 
						|
    # parse command line options
 | 
						|
    usage = "%prog [options] [package] [package] [...]"
 | 
						|
    parser = OptionParser(usage=usage, description=__doc__)
 | 
						|
    parser.add_option(
 | 
						|
        "-d",
 | 
						|
        "--dependencies",
 | 
						|
        dest="list_dependencies",
 | 
						|
        action="store_true",
 | 
						|
        default=False,
 | 
						|
        help="list dependencies for the packages",
 | 
						|
    )
 | 
						|
    parser.add_option(
 | 
						|
        "--list", action="store_true", default=False, help="list what will be installed"
 | 
						|
    )
 | 
						|
    parser.add_option(
 | 
						|
        "--extra",
 | 
						|
        "--install-extra-packages",
 | 
						|
        action="store_true",
 | 
						|
        default=False,
 | 
						|
        help="installs extra supporting packages as well as core mozbase ones",
 | 
						|
    )
 | 
						|
    options, packages = parser.parse_args(args)
 | 
						|
 | 
						|
    if not packages:
 | 
						|
        # install all packages
 | 
						|
        packages = sorted(mozbase_packages)
 | 
						|
 | 
						|
    # ensure specified packages are in the list
 | 
						|
    assert set(packages).issubset(
 | 
						|
        mozbase_packages
 | 
						|
    ), "Packages should be in %s (You gave: %s)" % (mozbase_packages, packages)
 | 
						|
 | 
						|
    if options.list_dependencies:
 | 
						|
        # list the package dependencies
 | 
						|
        for package in packages:
 | 
						|
            print("%s: %s" % get_dependencies(os.path.join(here, package)))
 | 
						|
        parser.exit()
 | 
						|
 | 
						|
    # gather dependencies
 | 
						|
    # TODO: version conflict checking
 | 
						|
    deps = {}
 | 
						|
    alldeps = {}
 | 
						|
    mapping = {}  # mapping from subdir name to package name
 | 
						|
    # core dependencies
 | 
						|
    for package in packages:
 | 
						|
        key, value = get_dependencies(os.path.join(here, package))
 | 
						|
        deps[key] = [dependency_info(dep)["Name"] for dep in value]
 | 
						|
        mapping[package] = key
 | 
						|
 | 
						|
        # keep track of all dependencies for non-mozbase packages
 | 
						|
        for dep in value:
 | 
						|
            alldeps[dependency_info(dep)["Name"]] = "".join(dep.split())
 | 
						|
 | 
						|
    # indirect dependencies
 | 
						|
    flag = True
 | 
						|
    while flag:
 | 
						|
        flag = False
 | 
						|
        for value in deps.values():
 | 
						|
            for dep in value:
 | 
						|
                if dep in mozbase_packages and dep not in deps:
 | 
						|
                    key, value = get_dependencies(os.path.join(here, dep))
 | 
						|
                    deps[key] = [dep for dep in value]
 | 
						|
 | 
						|
                    for dep in value:
 | 
						|
                        alldeps[dep] = "".join(dep.split())
 | 
						|
                    mapping[package] = key
 | 
						|
                    flag = True
 | 
						|
                    break
 | 
						|
            if flag:
 | 
						|
                break
 | 
						|
 | 
						|
    # get the remaining names for the mapping
 | 
						|
    for package in mozbase_packages:
 | 
						|
        if package in mapping:
 | 
						|
            continue
 | 
						|
        key, value = get_dependencies(os.path.join(here, package))
 | 
						|
        mapping[package] = key
 | 
						|
 | 
						|
    # unroll dependencies
 | 
						|
    unrolled = unroll_dependencies(deps)
 | 
						|
 | 
						|
    # make a reverse mapping: package name -> subdirectory
 | 
						|
    reverse_mapping = dict([(j, i) for i, j in mapping.items()])
 | 
						|
 | 
						|
    # we only care about dependencies in mozbase
 | 
						|
    unrolled = [package for package in unrolled if package in reverse_mapping]
 | 
						|
 | 
						|
    if options.list:
 | 
						|
        # list what will be installed
 | 
						|
        for package in unrolled:
 | 
						|
            print(package)
 | 
						|
        parser.exit()
 | 
						|
 | 
						|
    # set up the packages for development
 | 
						|
    for package in unrolled:
 | 
						|
        call(
 | 
						|
            [sys.executable, "setup.py", "develop", "--no-deps"],
 | 
						|
            cwd=os.path.join(here, reverse_mapping[package]),
 | 
						|
        )
 | 
						|
 | 
						|
    # add the directory of sys.executable to path to aid the correct
 | 
						|
    # `easy_install` getting called
 | 
						|
    # https://bugzilla.mozilla.org/show_bug.cgi?id=893878
 | 
						|
    os.environ["PATH"] = "%s%s%s" % (
 | 
						|
        os.path.dirname(os.path.abspath(sys.executable)),
 | 
						|
        os.path.pathsep,
 | 
						|
        os.environ.get("PATH", "").strip(os.path.pathsep),
 | 
						|
    )
 | 
						|
 | 
						|
    # install non-mozbase dependencies
 | 
						|
    # these need to be installed separately and the --no-deps flag
 | 
						|
    # subsequently used due to a bug in setuptools; see
 | 
						|
    # https://bugzilla.mozilla.org/show_bug.cgi?id=759836
 | 
						|
    pypi_deps = dict([(i, j) for i, j in alldeps.items() if i not in unrolled])
 | 
						|
    for package, version in pypi_deps.items():
 | 
						|
        # easy_install should be available since we rely on setuptools
 | 
						|
        call(["easy_install", version])
 | 
						|
 | 
						|
    # install packages required for unit testing
 | 
						|
    for package in test_packages:
 | 
						|
        call(["easy_install", package])
 | 
						|
 | 
						|
    # install extra non-mozbase packages if desired
 | 
						|
    if options.extra:
 | 
						|
        for package in extra_packages:
 | 
						|
            call(["easy_install", package])
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 |