Skip to content
Newer
Older
100755 187 lines (147 sloc) 5.79 KB
32e90d9 @k0s add a script to setup for development
k0s authored
1 #!/usr/bin/env python
2
3 """
4 Setup mozbase packages for development.
5
6 Packages may be specified as command line arguments.
7 If no arguments are given, install all packages.
8
9 See https://wiki.mozilla.org/Auto-tools/Projects/MozBase
10 """
11
12 # XXX note that currently directory names must equal package names
13
14 import pkg_resources
15 import os
16 import sys
17 from optparse import OptionParser
18
19 from subprocess import PIPE
20 try:
21 from subprocess import check_call as call
22 except ImportError:
23 from subprocess import call
24
25
26 # directory containing this file
27 here = os.path.dirname(os.path.abspath(__file__))
28
29 # all python packages
30 all_packages = [i for i in os.listdir(here)
31 if os.path.exists(os.path.join(here, i, 'setup.py'))]
32
33 def cycle_check(order, dependencies):
34 """ensure no cyclic dependencies"""
35 order_dict = dict([(j, i) for i, j in enumerate(order)])
36 for package, deps in dependencies.items():
37 index = order_dict[package]
38 for d in deps:
39 assert index > order_dict[d], "Cyclic dependencies detected"
40
41 def dependencies(directory):
42 """
43 get the dependencies of a package directory containing a setup.py
44 returns the package name and the list of dependencies
45 """
46 assert os.path.exists(os.path.join(directory, 'setup.py'))
47
48 # setup the egg info
49 call([sys.executable, 'setup.py', 'egg_info'], cwd=directory, stdout=PIPE)
50
51 # get the .egg-info directory
52 egg_info = [i for i in os.listdir(directory)
53 if i.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
57
58 # read the dependencies
59 requires = os.path.join(egg_info, 'requires.txt')
60 if os.path.exists(requires):
61 dependencies = [i.strip() for i in file(requires).readlines() if i.strip()]
62 else:
63 dependencies = []
64
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
74
75
76 # return the information
77 return info_dict['Name'], dependencies
78
79 def sanitize_dependency(dep):
80 """
81 remove version numbers from deps
82 """
83 for joiner in ('==', '<=', '>='):
84 if joiner in dep:
85 dep = dep.split(joiner, 1)[0].strip()
86 return dep # XXX only one joiner allowed right now
87 return dep
88
89
90 def unroll_dependencies(dependencies):
91 """
92 unroll a set of dependencies to a flat list
93
94 dependencies = {'packageA': set(['packageB', 'packageC', 'packageF']),
95 'packageB': set(['packageC', 'packageD', 'packageE', 'packageG']),
96 'packageC': set(['packageE']),
97 'packageE': set(['packageF', 'packageG']),
98 'packageF': set(['packageG']),
99 'packageX': set(['packageA', 'packageG'])}
100 """
101
102 order = []
103
104 # flatten all
105 packages = set(dependencies.keys())
106 for deps in dependencies.values():
107 packages.update(deps)
108
109 while len(order) != len(packages):
110
111 for package in packages.difference(order):
112 if set(dependencies.get(package, set())).issubset(order):
113 order.append(package)
114 break
115 else:
116 raise AssertionError("Cyclic dependencies detected")
117
118 cycle_check(order, dependencies) # sanity check
119
120 return order
121
122
123 def main(args=sys.argv[1:]):
124
125 # parse command line options
126 usage = '%prog [options] [package] [package] [...]'
127 parser = OptionParser(usage=usage, description=__doc__)
128 parser.add_option('-d', '--dependencies', dest='list_dependencies',
129 action='store_true', default=False,
130 help="list dependencies for the packages")
131 parser.add_option('--list', action='store_true', default=False,
132 help="list what will be installed")
133 options, packages = parser.parse_args(args)
134
135 if not packages:
136 # install all packages
137 packages = sorted(all_packages)
138
139 # ensure specified packages are in the list
140 assert set(packages).issubset(all_packages), "Packages should be in %s" % all_packages
141
142 if options.list_dependencies:
143 # list the package dependencies
144 for package in packages:
145 print '%s: %s' % dependencies(os.path.join(here, package))
146 parser.exit()
147
148 # gather dependencies
149 deps = {}
150 # core dependencies
151 for package in packages:
152 key, value = dependencies(os.path.join(here, package))
153 deps[key] = [sanitize_dependency(dep) for dep in value]
154 # indirect dependencies
155 flag = True
156 while flag:
157 flag = False
158 for value in deps.values():
159 for dep in value:
160 if dep in all_packages and dep not in deps:
161 key, value = dependencies(os.path.join(here, dep))
162 deps[key] = [sanitize_dependency(dep) for dep in value]
163 flag = True
164 break
165 if flag:
166 break
167
168 # unroll dependencies
169 unrolled = unroll_dependencies(deps)
170
171 # we only care about dependencies in mozbase
172 unrolled = [package for package in unrolled
173 if package in all_packages]
174
175 if options.list:
176 # list what will be installed
177 for package in unrolled:
178 print package
179 parser.exit()
180
181 # set up the packages for development
182 for package in unrolled:
183 call([sys.executable, 'setup.py', 'develop'], cwd=os.path.join(here, package))
184
185 if __name__ == '__main__':
186 main()
Something went wrong with that request. Please try again.