testing/mozbase/setup_development.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 #!/usr/bin/env python
     3 # This Source Code Form is subject to the terms of the Mozilla Public
     4 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
     5 # You can obtain one at http://mozilla.org/MPL/2.0/.
     7 """
     8 Setup mozbase packages for development.
    10 Packages may be specified as command line arguments.
    11 If no arguments are given, install all packages.
    13 See https://wiki.mozilla.org/Auto-tools/Projects/Mozbase
    14 """
    16 import os
    17 import subprocess
    18 import sys
    19 from optparse import OptionParser
    20 from subprocess import PIPE
    21 try:
    22     from subprocess import check_call as call
    23 except ImportError:
    24     from subprocess import call
    27 # directory containing this file
    28 here = os.path.dirname(os.path.abspath(__file__))
    30 # all python packages
    31 mozbase_packages = [i for i in os.listdir(here)
    32                     if os.path.exists(os.path.join(here, i, 'setup.py'))]
    33 test_packages = [ "mock" # testing: https://wiki.mozilla.org/Auto-tools/Projects/Mozbase#Tests
    34                   ]
    35 extra_packages = [ "sphinx" # documentation: https://wiki.mozilla.org/Auto-tools/Projects/Mozbase#Documentation
    36                   ]
    38 def cycle_check(order, dependencies):
    39     """ensure no cyclic dependencies"""
    40     order_dict = dict([(j, i) for i, j in enumerate(order)])
    41     for package, deps in dependencies.items():
    42         index = order_dict[package]
    43         for d in deps:
    44             assert index > order_dict[d], "Cyclic dependencies detected"
    46 def info(directory):
    47     "get the package setup.py information"
    49     assert os.path.exists(os.path.join(directory, 'setup.py'))
    51     # setup the egg info
    52     try:
    53         call([sys.executable, 'setup.py', 'egg_info'], cwd=directory, stdout=PIPE)
    54     except subprocess.CalledProcessError:
    55         print "Error running setup.py in %s" % directory
    56         raise
    58     # get the .egg-info directory
    59     egg_info = [entry for entry in os.listdir(directory)
    60                 if entry.endswith('.egg-info')]
    61     assert len(egg_info) == 1, 'Expected one .egg-info directory in %s, got: %s' % (directory, egg_info)
    62     egg_info = os.path.join(directory, egg_info[0])
    63     assert os.path.isdir(egg_info), "%s is not a directory" % egg_info
    65     # read the package information
    66     pkg_info = os.path.join(egg_info, 'PKG-INFO')
    67     info_dict = {}
    68     for line in file(pkg_info).readlines():
    69         if not line or line[0].isspace():
    70             continue # XXX neglects description
    71         assert ':' in line
    72         key, value = [i.strip() for i in line.split(':', 1)]
    73         info_dict[key] = value
    75     return info_dict
    77 def get_dependencies(directory):
    78     "returns the package name and dependencies given a package directory"
    80     # get the package metadata
    81     info_dict = info(directory)
    83     # get the .egg-info directory
    84     egg_info = [entry for entry in os.listdir(directory)
    85                 if entry.endswith('.egg-info')][0]
    87     # read the dependencies
    88     requires = os.path.join(directory, egg_info, 'requires.txt')
    89     if os.path.exists(requires):
    90         dependencies = [line.strip()
    91                         for line in file(requires).readlines()
    92                         if line.strip()]
    93     else:
    94         dependencies = []
    96     # return the information
    97     return info_dict['Name'], dependencies
    99 def dependency_info(dep):
   100     "return dictionary of dependency information from a dependency string"
   101     retval = dict(Name=None, Type=None, Version=None)
   102     for joiner in ('==', '<=', '>='):
   103         if joiner in dep:
   104             retval['Type'] = joiner
   105             name, version = [i.strip() for i in dep.split(joiner, 1)]
   106             retval['Name'] = name
   107             retval['Version'] = version
   108             break
   109     else:
   110         retval['Name'] = dep.strip()
   111     return retval
   113 def unroll_dependencies(dependencies):
   114     """
   115     unroll a set of dependencies to a flat list
   117     dependencies = {'packageA': set(['packageB', 'packageC', 'packageF']),
   118                     'packageB': set(['packageC', 'packageD', 'packageE', 'packageG']),
   119                     'packageC': set(['packageE']),
   120                     'packageE': set(['packageF', 'packageG']),
   121                     'packageF': set(['packageG']),
   122                     'packageX': set(['packageA', 'packageG'])}
   123     """
   125     order = []
   127     # flatten all
   128     packages = set(dependencies.keys())
   129     for deps in dependencies.values():
   130         packages.update(deps)
   132     while len(order) != len(packages):
   134         for package in packages.difference(order):
   135             if set(dependencies.get(package, set())).issubset(order):
   136                 order.append(package)
   137                 break
   138         else:
   139             raise AssertionError("Cyclic dependencies detected")
   141     cycle_check(order, dependencies) # sanity check
   143     return order
   146 def main(args=sys.argv[1:]):
   148     # parse command line options
   149     usage = '%prog [options] [package] [package] [...]'
   150     parser = OptionParser(usage=usage, description=__doc__)
   151     parser.add_option('-d', '--dependencies', dest='list_dependencies',
   152                       action='store_true', default=False,
   153                       help="list dependencies for the packages")
   154     parser.add_option('--list', action='store_true', default=False,
   155                       help="list what will be installed")
   156     parser.add_option('--extra', '--install-extra-packages', action='store_true', default=False,
   157                       help="installs extra supporting packages as well as core mozbase ones")
   158     options, packages = parser.parse_args(args)
   160     if not packages:
   161         # install all packages
   162         packages = sorted(mozbase_packages)
   164     # ensure specified packages are in the list
   165     assert set(packages).issubset(mozbase_packages), "Packages should be in %s (You gave: %s)" % (mozbase_packages, packages)
   167     if options.list_dependencies:
   168         # list the package dependencies
   169         for package in packages:
   170             print '%s: %s' % get_dependencies(os.path.join(here, package))
   171         parser.exit()
   173     # gather dependencies
   174     # TODO: version conflict checking
   175     deps = {}
   176     alldeps = {}
   177     mapping = {} # mapping from subdir name to package name
   178     # core dependencies
   179     for package in packages:
   180         key, value = get_dependencies(os.path.join(here, package))
   181         deps[key] = [dependency_info(dep)['Name'] for dep in value]
   182         mapping[package] = key
   184         # keep track of all dependencies for non-mozbase packages
   185         for dep in value:
   186             alldeps[dependency_info(dep)['Name']] = ''.join(dep.split())
   188     # indirect dependencies
   189     flag = True
   190     while flag:
   191         flag = False
   192         for value in deps.values():
   193             for dep in value:
   194                 if dep in mozbase_packages and dep not in deps:
   195                     key, value = get_dependencies(os.path.join(here, dep))
   196                     deps[key] = [sanitize_dependency(dep) for dep in value]
   198                     for dep in value:
   199                         alldeps[sanitize_dependency(dep)] = ''.join(dep.split())
   200                     mapping[package] = key
   201                     flag = True
   202                     break
   203             if flag:
   204                 break
   206     # get the remaining names for the mapping
   207     for package in mozbase_packages:
   208         if package in mapping:
   209             continue
   210         key, value = get_dependencies(os.path.join(here, package))
   211         mapping[package] = key
   213     # unroll dependencies
   214     unrolled = unroll_dependencies(deps)
   216     # make a reverse mapping: package name -> subdirectory
   217     reverse_mapping = dict([(j,i) for i, j in mapping.items()])
   219     # we only care about dependencies in mozbase
   220     unrolled = [package for package in unrolled if package in reverse_mapping]
   222     if options.list:
   223         # list what will be installed
   224         for package in unrolled:
   225             print package
   226         parser.exit()
   228     # set up the packages for development
   229     for package in unrolled:
   230         call([sys.executable, 'setup.py', 'develop', '--no-deps'],
   231              cwd=os.path.join(here, reverse_mapping[package]))
   233     # add the directory of sys.executable to path to aid the correct
   234     # `easy_install` getting called
   235     # https://bugzilla.mozilla.org/show_bug.cgi?id=893878
   236     os.environ['PATH'] = '%s%s%s' % (os.path.dirname(os.path.abspath(sys.executable)),
   237                                      os.path.pathsep,
   238                                      os.environ.get('PATH', '').strip(os.path.pathsep))
   240     # install non-mozbase dependencies
   241     # these need to be installed separately and the --no-deps flag
   242     # subsequently used due to a bug in setuptools; see
   243     # https://bugzilla.mozilla.org/show_bug.cgi?id=759836
   244     pypi_deps = dict([(i, j) for i,j in alldeps.items()
   245                       if i not in unrolled])
   246     for package, version in pypi_deps.items():
   247         # easy_install should be available since we rely on setuptools
   248         call(['easy_install', version])
   250     # install packages required for unit testing
   251     for package in test_packages:
   252         call(['easy_install', package])
   254     # install extra non-mozbase packages if desired
   255     if options.extra:
   256         for package in extra_packages:
   257             call(['easy_install', package])
   259 if __name__ == '__main__':
   260     main()

mercurial