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/.
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
19
from optparse import OptionParser
21
from subprocess import PIPE
23
from subprocess import check_call as call
25
from subprocess import call
28
# directory containing this file
29
here = os.path.dirname(os.path.abspath(__file__))
32
all_packages = [i for i in os.listdir(here)
33
if os.path.exists(os.path.join(here, i, 'setup.py'))]
35
def cycle_check(order, dependencies):
36
"""ensure no cyclic dependencies"""
37
order_dict = dict([(j, i) for i, j in enumerate(order)])
38
for package, deps in dependencies.items():
39
index = order_dict[package]
41
assert index > order_dict[d], "Cyclic dependencies detected"
44
"get the package setup.py information"
46
assert os.path.exists(os.path.join(directory, 'setup.py'))
49
call([sys.executable, 'setup.py', 'egg_info'], cwd=directory, stdout=PIPE)
51
# get the .egg-info directory
52
egg_info = [entry for entry in os.listdir(directory)
53
if entry.endswith('.egg-info')]
54
assert len(egg_info) == 1, 'Expected one .egg-info directory in %s, got: %s' % (directory, egg_info)
55
egg_info = os.path.join(directory, egg_info[0])
56
assert os.path.isdir(egg_info), "%s is not a directory" % egg_info
58
# read the package information
59
pkg_info = os.path.join(egg_info, 'PKG-INFO')
61
for line in file(pkg_info).readlines():
62
if not line or line[0].isspace():
63
continue # XXX neglects description
65
key, value = [i.strip() for i in line.split(':', 1)]
66
info_dict[key] = value
70
def get_dependencies(directory):
71
"returns the package name and dependencies given a package directory"
73
# get the package metadata
74
info_dict = info(directory)
76
# get the .egg-info directory
77
egg_info = [entry for entry in os.listdir(directory)
78
if entry.endswith('.egg-info')][0]
80
# read the dependencies
81
requires = os.path.join(directory, egg_info, 'requires.txt')
82
if os.path.exists(requires):
83
dependencies = [line.strip()
84
for line in file(requires).readlines()
89
# return the information
90
return info_dict['Name'], dependencies
92
def dependency_info(dep):
93
"return dictionary of dependency information from a dependency string"
94
retval = dict(Name=None, Type=None, Version=None)
95
for joiner in ('==', '<=', '>='):
97
retval['Type'] = joiner
98
name, version = [i.strip() for i in dep.split(joiner, 1)]
100
retval['Version'] = version
103
retval['name'] = dep.strip()
106
def unroll_dependencies(dependencies):
108
unroll a set of dependencies to a flat list
110
dependencies = {'packageA': set(['packageB', 'packageC', 'packageF']),
111
'packageB': set(['packageC', 'packageD', 'packageE', 'packageG']),
112
'packageC': set(['packageE']),
113
'packageE': set(['packageF', 'packageG']),
114
'packageF': set(['packageG']),
115
'packageX': set(['packageA', 'packageG'])}
121
packages = set(dependencies.keys())
122
for deps in dependencies.values():
123
packages.update(deps)
125
while len(order) != len(packages):
127
for package in packages.difference(order):
128
if set(dependencies.get(package, set())).issubset(order):
129
order.append(package)
132
raise AssertionError("Cyclic dependencies detected")
134
cycle_check(order, dependencies) # sanity check
139
def main(args=sys.argv[1:]):
141
# parse command line options
142
usage = '%prog [options] [package] [package] [...]'
143
parser = OptionParser(usage=usage, description=__doc__)
144
parser.add_option('-d', '--dependencies', dest='list_dependencies',
145
action='store_true', default=False,
146
help="list dependencies for the packages")
147
parser.add_option('--list', action='store_true', default=False,
148
help="list what will be installed")
149
options, packages = parser.parse_args(args)
152
# install all packages
153
packages = sorted(all_packages)
155
# ensure specified packages are in the list
156
assert set(packages).issubset(all_packages), "Packages should be in %s (You gave: %s)" % (all_packages, packages)
158
if options.list_dependencies:
159
# list the package dependencies
160
for package in packages:
161
print '%s: %s' % get_dependencies(os.path.join(here, package))
164
# gather dependencies
165
# TODO: version conflict checking
168
mapping = {} # mapping from subdir name to package name
170
for package in packages:
171
key, value = get_dependencies(os.path.join(here, package))
172
deps[key] = [dependency_info(dep)['Name'] for dep in value]
173
mapping[package] = key
175
# keep track of all dependencies for non-mozbase packages
177
alldeps[dependency_info(dep)['Name']] = ''.join(dep.split())
179
# indirect dependencies
183
for value in deps.values():
185
if dep in all_packages and dep not in deps:
186
key, value = get_dependencies(os.path.join(here, dep))
187
deps[key] = [sanitize_dependency(dep) for dep in value]
190
alldeps[sanitize_dependency(dep)] = ''.join(dep.split())
191
mapping[package] = key
197
# get the remaining names for the mapping
198
for package in all_packages:
199
if package in mapping:
201
key, value = get_dependencies(os.path.join(here, package))
202
mapping[package] = key
204
# unroll dependencies
205
unrolled = unroll_dependencies(deps)
207
# make a reverse mapping: package name -> subdirectory
208
reverse_mapping = dict([(j,i) for i, j in mapping.items()])
210
# we only care about dependencies in mozbase
211
unrolled = [package for package in unrolled if package in reverse_mapping]
214
# list what will be installed
215
for package in unrolled:
219
# install non-mozbase dependencies
220
# (currently none on modern python)
221
# these need to be installed separately and the --no-deps flag
222
# subsequently used due to a bug in setuptools; see
223
# https://bugzilla.mozilla.org/show_bug.cgi?id=759836
224
pypi_deps = dict([(i, j) for i,j in alldeps.items()
225
if i not in unrolled])
226
for package, version in pypi_deps.items():
227
# easy_install should be available since we rely on setuptools
228
call(['easy_install', version])
230
# set up the packages for development
231
for package in unrolled:
232
call([sys.executable, 'setup.py', 'develop', '--no-deps'],
233
cwd=os.path.join(here, reverse_mapping[package]))
235
if __name__ == '__main__':