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.

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

mercurial