1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """objects and functions used in dealing with packages
23 """
24
25 import ihooks
26 import glob
27 import os
28 import sys
29
30 from twisted.python import rebuild, reflect
31
32 from flumotion.common import log, common
33 from flumotion.configure import configure
34
35 __version__ = "$Rev: 6982 $"
36
37
39 """
40 I am an import Hooks object that makes sure that every package that gets
41 loaded has every necessary path in the module's __path__ list.
42
43 @type packager: L{Packager}
44 """
45 packager = None
46
67
69 """
70 I am an object through which package paths can be registered, to support
71 the partitioning of the module import namespace across bundles.
72 """
73
74 logCategory = 'packager'
75
77 self._paths = {}
78 self._packages = {}
79 self.install()
80
82 """
83 Install our custom importer that uses bundled packages.
84 """
85 self.debug('installing custom importer')
86 self._hooks = PackageHooks()
87 self._hooks.packager = self
88 self._importer = ihooks.ModuleImporter()
89 self._importer.set_hooks(self._hooks)
90 self._importer.install()
91
93 """
94 Return all absolute paths to the top level of a tree from which
95 (part of) the given package name can be imported.
96 """
97 if packageName not in self._packages:
98 return None
99
100 return [self._paths[key] for key in self._packages[packageName]]
101
103 """
104 Register a given path as a path that can be imported from.
105 Used to support partition of bundled code or import code from various
106 uninstalled location.
107
108 sys.path will also be changed to include this, and remove references
109 to older packagePath's for the same bundle.
110
111 @param packagePath: path to add under which the module namespaces live,
112 (ending in an md5sum, for flumotion purposes)
113 @type packagePath: string
114 @param key a unique id for the package being registered
115 @type key: string
116 @param prefix: prefix of the packages to be considered
117 @type prefix: string
118 """
119
120 new = True
121 packagePath = os.path.abspath(packagePath)
122 if not os.path.exists(packagePath):
123 log.warning('bundle',
124 'registering a non-existing package path %s' % packagePath)
125
126 self.log('registering packagePath %s' % packagePath)
127
128
129 if key in self._paths:
130 oldPath = self._paths[key]
131 if packagePath == oldPath:
132 self.log('already registered %s for key %s' % (
133 packagePath, key))
134 return
135 new = False
136
137
138
139
140
141 if not os.path.isdir(packagePath):
142 log.warning('bundle', 'package path not a dir: %s',
143 packagePath)
144 packageNames = []
145 else:
146 packageNames = _findPackageCandidates(packagePath, prefix)
147
148 if not packageNames:
149 log.log('bundle',
150 'packagePath %s does not have candidates starting with %s' %
151 (packagePath, prefix))
152 return
153 packageNames.sort()
154
155 self.log('package candidates %r' % packageNames)
156
157 if not new:
158
159 log.log('bundle',
160 'replacing old path %s with new path %s for key %s' % (
161 oldPath, packagePath, key))
162
163 if oldPath in sys.path:
164 log.log('bundle',
165 'removing old packagePath %s from sys.path' % oldPath)
166 sys.path.remove(oldPath)
167
168
169 for keys in self._packages.values():
170 if key in keys:
171 keys.remove(key)
172
173 self._paths[key] = packagePath
174
175
176 if not packagePath in sys.path:
177 self.log('adding packagePath %s to sys.path' % packagePath)
178 sys.path.insert(0, packagePath)
179
180
181 for name in packageNames:
182 if name not in self._packages:
183 self._packages[name] = [key]
184 else:
185 self._packages[name].insert(0, key)
186
187 self.log('packagePath %s has packageNames %r' % (
188 packagePath, packageNames))
189
190
191 packageNames.reverse()
192
193 for packageName in packageNames:
194 if packageName not in sys.modules:
195 continue
196 self.log('fixing up %s ...' % packageName)
197
198
199 package = sys.modules.get(packageName)
200 for path in package.__path__:
201 if not new and path.startswith(oldPath):
202 self.log('%s.__path__ before remove %r' % (
203 packageName, package.__path__))
204 self.log('removing old %s from %s.__path__' % (
205 path, name))
206 package.__path__.remove(path)
207 self.log('%s.__path__ after remove %r' % (
208 packageName, package.__path__))
209
210
211
212
213
214 newPath = os.path.join(packagePath,
215 packageName.replace('.', os.sep))
216
217
218
219
220 if len(package.__path__) == 0:
221
222
223
224
225 self.debug('WARN: package %s does not have __path__ values' % (
226 packageName))
227 elif package.__path__[0] == newPath:
228 self.log('path %s already at start of %s.__path__' % (
229 newPath, packageName))
230 continue
231
232 if newPath in package.__path__:
233 package.__path__.remove(newPath)
234 self.log('moving %s to front of %s.__path__' % (
235 newPath, packageName))
236 else:
237 self.log('inserting new %s into %s.__path__' % (
238 newPath, packageName))
239 package.__path__.insert(0, newPath)
240
241
242
243
244
245
246
247
248 self.log('fixed up %s, __path__ %s ...' % (packageName, package.__path__))
249
250
251
252 if not new:
253 self.log('finding end module candidates')
254 if not os.path.isdir(packagePath):
255 log.warning('bundle', 'package path not a dir: %s',
256 path)
257 moduleNames = []
258 else:
259 moduleNames = findEndModuleCandidates(packagePath, prefix)
260 self.log('end module candidates to rebuild: %r' % moduleNames)
261 for name in moduleNames:
262 if name in sys.modules:
263
264 self.log("rebuilding non-package module %s" % name)
265 try:
266 module = reflect.namedAny(name)
267 except AttributeError:
268 log.warning('bundle',
269 "could not reflect non-package module %s" % name)
270 continue
271
272 if hasattr(module, '__path__'):
273 self.log('rebuilding module %s with paths %r' % (name,
274 module.__path__))
275 rebuild.rebuild(module)
276
277
278
279 self.log('registered packagePath %s for key %s' % (packagePath, key))
280
282 """
283 Unregister all previously registered package paths, and uninstall
284 the custom importer.
285 """
286 for path in self._paths.values():
287 if path in sys.path:
288 self.log('removing packagePath %s from sys.path' % path)
289 sys.path.remove(path)
290 self._paths = {}
291 self._packages = {}
292 self.debug('uninstalling custom importer')
293 self._importer.uninstall()
294
296 """
297 I'm similar to os.listdir, but I work recursively and only return
298 directories containing python code.
299
300 @param path: the path
301 @type path: string
302 """
303 retval = []
304 try:
305 files = os.listdir(path)
306 except OSError:
307 pass
308 else:
309 for f in files:
310
311 p = os.path.join(path, f)
312 if os.path.isdir(p) and f != '.svn':
313 retval += _listDirRecursively(p)
314
315 if glob.glob(os.path.join(path, '*.py*')):
316 retval.append(path)
317
318 return retval
319
321 """
322 I'm similar to os.listdir, but I work recursively and only return
323 files representing python non-package modules.
324
325 @param path: the path
326 @type path: string
327
328 @rtype: list
329 @returns: list of files underneath the given path containing python code
330 """
331 retval = []
332
333
334 dirs = _listDirRecursively(path)
335
336 for directory in dirs:
337 pyfiles = glob.glob(os.path.join(directory, '*.py*'))
338 dontkeep = glob.glob(os.path.join(directory, '*__init__.py*'))
339 for f in dontkeep:
340 if f in pyfiles:
341 pyfiles.remove(f)
342
343 retval.extend(pyfiles)
344
345 return retval
346
348 """
349 I take a directory and return a list of candidate python packages
350 under that directory that start with the given prefix.
351 A package is a module containing modules; typically the directory
352 with the same name as the package contains __init__.py
353
354 @param path: the path
355 @type path: string
356 """
357
358
359 dirs = _listDirRecursively(os.path.join(path, prefix))
360
361
362 bundlePaths = [x[len(path) + 1:] for x in dirs]
363
364
365 bundlePaths = [path for path in bundlePaths if path.find('.svn') == -1]
366 bundlePaths = [path for path in bundlePaths if path.find('-') == -1]
367
368
369 bundlePackages = [".".join(x.split(os.path.sep)) for x in bundlePaths]
370
371
372
373 packages = {}
374 for name in bundlePackages:
375 packages[name] = 1
376 parts = name.split(".")
377 build = None
378 for p in parts:
379 if not build:
380 build = p
381 else:
382 build = build + "." + p
383 packages[build] = 1
384
385 bundlePackages = packages.keys()
386
387
388 bundlePackages.sort()
389
390 return bundlePackages
391
393 """
394 I take a directory and return a list of candidate python end modules
395 (i.e., non-package modules) for the given module prefix.
396
397 @param path: the path under which to search for end modules
398 @type path: string
399 @param prefix: module prefix to check candidates under
400 @type prefix: string
401 """
402 pathPrefix = "/".join(prefix.split("."))
403 files = _listPyFileRecursively(os.path.join(path, pathPrefix))
404
405
406 importPaths = [x[len(path) + 1:] for x in files]
407
408
409 importPaths = [path for path in importPaths if path.find('.svn') == -1]
410 importPaths = [path for path in importPaths if path.find('-') == -1]
411
412
413 endModules = [common.pathToModuleName(x) for x in importPaths]
414
415
416 endModules = [module for module in endModules
417 if module and module.startswith(prefix)]
418
419
420 endModules.sort()
421
422
423 res = {}
424 for b in endModules: res[b] = 1
425
426 return res.keys()
427
428
429 __packager = None
430
442