mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-01 01:07:51 -04:00
VFSImporter.sharedPackages
This commit is contained in:
parent
1c55bb68cc
commit
8c9032dca3
@ -294,16 +294,12 @@ class AppRunner(DirectObject):
|
||||
if mainName:
|
||||
moduleName = mainName
|
||||
|
||||
root = self.multifileRoot
|
||||
if '.' in moduleName:
|
||||
root += '/' + '/'.join(moduleName.split('.')[:-1])
|
||||
v = VFSImporter.VFSImporter(root)
|
||||
loader = v.find_module(moduleName)
|
||||
if not loader:
|
||||
try:
|
||||
__import__(moduleName)
|
||||
except ImportError:
|
||||
message = "No %s found in application." % (moduleName)
|
||||
raise StandardError, message
|
||||
|
||||
main = loader.load_module(moduleName)
|
||||
main = sys.modules[moduleName]
|
||||
if hasattr(main, 'main') and callable(main.main):
|
||||
main.main(self)
|
||||
|
||||
@ -418,6 +414,7 @@ class AppRunner(DirectObject):
|
||||
|
||||
# Mount the Multifile under /mf, by convention.
|
||||
vfs.mount(mf, self.multifileRoot, vfs.MFReadOnly)
|
||||
VFSImporter.reloadSharedPackages()
|
||||
|
||||
self.loadMultifilePrcFiles(mf, self.multifileRoot)
|
||||
self.gotP3DFilename = True
|
||||
|
@ -363,3 +363,19 @@ class PackageInfo:
|
||||
if not foundOnPath:
|
||||
# Not already here; add it.
|
||||
sys.path.append(root)
|
||||
|
||||
# Also, find any toplevel Python packages, and add these as
|
||||
# shared packages. This will allow different packages
|
||||
# installed in different directories to share Python files as
|
||||
# if they were all in the same directory.
|
||||
for filename in mf.getSubfileNames():
|
||||
if filename.endswith('/__init__.pyc') or \
|
||||
filename.endswith('/__init__.pyo') or \
|
||||
filename.endswith('/__init__.py'):
|
||||
components = filename.split('/')[:-1]
|
||||
moduleName = '.'.join(components)
|
||||
VFSImporter.sharedPackages[moduleName] = True
|
||||
|
||||
# Fix up any shared directories so we can load packages from
|
||||
# disparate locations.
|
||||
VFSImporter.reloadSharedPackages()
|
||||
|
@ -101,58 +101,58 @@ default-model-extension .bam
|
||||
""" + auxDisplays)
|
||||
|
||||
|
||||
## class egg(package):
|
||||
## # This package contains the code for reading and operating on egg
|
||||
## # files. Since the Packager automatically converts egg files to bam
|
||||
## # files, this is not needed for most Panda3D applications.
|
||||
class egg(package):
|
||||
# This package contains the code for reading and operating on egg
|
||||
# files. Since the Packager automatically converts egg files to bam
|
||||
# files, this is not needed for most Panda3D applications.
|
||||
|
||||
## config(display_name = "Panda3D egg loader")
|
||||
## require('panda3d')
|
||||
config(display_name = "Panda3D egg loader")
|
||||
require('panda3d')
|
||||
|
||||
## file('libpandaegg.dll')
|
||||
file('libpandaegg.dll')
|
||||
|
||||
## file('egg.prc', extract = True, text = """
|
||||
## plugin-path $EGG_ROOT
|
||||
## load-file-type egg pandaegg
|
||||
## """)
|
||||
file('egg.prc', extract = True, text = """
|
||||
plugin-path $EGG_ROOT
|
||||
load-file-type egg pandaegg
|
||||
""")
|
||||
|
||||
## class wx(package):
|
||||
## config(display_name = "wxPython GUI Toolkit")
|
||||
## require('panda3d')
|
||||
class wx(package):
|
||||
config(display_name = "wxPython GUI Toolkit")
|
||||
require('panda3d')
|
||||
|
||||
## module('direct.showbase.WxGlobal', 'wx', 'wx.*')
|
||||
module('direct.showbase.WxGlobal', 'wx', 'wx.*')
|
||||
|
||||
|
||||
## class tk(package):
|
||||
## config(display_name = "Tk GUI Toolkit")
|
||||
## require('panda3d')
|
||||
class tk(package):
|
||||
config(display_name = "Tk GUI Toolkit")
|
||||
require('panda3d')
|
||||
|
||||
## module('Tkinter',
|
||||
## 'direct.showbase.TkGlobal',
|
||||
## 'direct.tkpanels',
|
||||
## 'direct.tkwidgets')
|
||||
module('Tkinter',
|
||||
'direct.showbase.TkGlobal',
|
||||
'direct.tkpanels',
|
||||
'direct.tkwidgets')
|
||||
|
||||
## class packp3d(p3d):
|
||||
## # This application is a command-line convenience for building a p3d
|
||||
## # application out of a directory hierarchy on disk. We build it here
|
||||
## # into its own p3d application, to allow end-users to easily build p3d
|
||||
## # applications using the appropriate version of Python and Panda for
|
||||
## # the targeted runtime.
|
||||
class packp3d(p3d):
|
||||
# This application is a command-line convenience for building a p3d
|
||||
# application out of a directory hierarchy on disk. We build it here
|
||||
# into its own p3d application, to allow end-users to easily build p3d
|
||||
# applications using the appropriate version of Python and Panda for
|
||||
# the targeted runtime.
|
||||
|
||||
## config(display_name = "Panda3D Application Packer",
|
||||
## hidden = True, platform_specific = False)
|
||||
## require('panda3d', 'egg')
|
||||
config(display_name = "Panda3D Application Packer",
|
||||
hidden = True, platform_specific = False)
|
||||
require('panda3d', 'egg')
|
||||
|
||||
## mainModule('direct.p3d.packp3d')
|
||||
mainModule('direct.p3d.packp3d')
|
||||
|
||||
|
||||
## class ppackage(p3d):
|
||||
## # As above, a packaging utility. This is the fully-general ppackage
|
||||
## # utility, which reads pdef files (like this one!) and creates one or
|
||||
## # more packages or p3d applications.
|
||||
class ppackage(p3d):
|
||||
# As above, a packaging utility. This is the fully-general ppackage
|
||||
# utility, which reads pdef files (like this one!) and creates one or
|
||||
# more packages or p3d applications.
|
||||
|
||||
## config(display_name = "Panda3D General Package Utility",
|
||||
## hidden = True, platform_specific = False)
|
||||
## require('panda3d', 'egg')
|
||||
config(display_name = "Panda3D General Package Utility",
|
||||
hidden = True, platform_specific = False)
|
||||
require('panda3d', 'egg')
|
||||
|
||||
## mainModule('direct.p3d.ppackage')
|
||||
mainModule('direct.p3d.ppackage')
|
||||
|
@ -664,13 +664,15 @@ start_p3dpython(P3DInstance *inst) {
|
||||
}
|
||||
|
||||
// Build up a search path that includes all of the required packages
|
||||
// that have already been installed.
|
||||
// that have already been installed. We build this in reverse
|
||||
// order, so that the higher-order packages come first in the list;
|
||||
// that allows them to shadow settings in the lower-order packages.
|
||||
assert(!inst->_packages.empty());
|
||||
string search_path;
|
||||
size_t pi = 0;
|
||||
assert(pi < inst->_packages.size());
|
||||
size_t pi = inst->_packages.size() - 1;
|
||||
search_path = inst->_packages[pi]->get_package_dir();
|
||||
++pi;
|
||||
while (pi < inst->_packages.size()) {
|
||||
while (pi > 0) {
|
||||
--pi;
|
||||
#ifdef _WIN32
|
||||
search_path += ';';
|
||||
#else
|
||||
@ -678,7 +680,6 @@ start_p3dpython(P3DInstance *inst) {
|
||||
#endif // _WIN32
|
||||
|
||||
search_path += inst->_packages[pi]->get_package_dir();
|
||||
++pi;
|
||||
}
|
||||
|
||||
nout << "Search path is " << search_path << "\n";
|
||||
|
@ -5,9 +5,26 @@ import os
|
||||
import marshal
|
||||
import imp
|
||||
import struct
|
||||
import types
|
||||
import __builtin__
|
||||
|
||||
__all__ = ['register', 'freeze_new_modules']
|
||||
__all__ = ['register', 'sharedPackages',
|
||||
'reloadSharedPackage', 'reloadSharedPackages']
|
||||
|
||||
# The sharedPackages dictionary lists all of the "shared packages",
|
||||
# special Python packages that automatically span multiple directories
|
||||
# via magic in the VFSImporter. You can make a package "shared"
|
||||
# simply by adding its name into this dictionary (and then calling
|
||||
# reloadSharedPackages() if it's already been imported).
|
||||
|
||||
# When a package name is in this dictionary at import time, *all*
|
||||
# instances of the package are located along sys.path, and merged into
|
||||
# a single Python module with a __path__ setting that represents the
|
||||
# union. Thus, you can have a direct.showbase.foo in your own
|
||||
# application, and loading it won't shadow the system
|
||||
# direct.showbase.ShowBase which is in a different directory on disk.
|
||||
|
||||
sharedPackages = {}
|
||||
|
||||
vfs = VirtualFileSystem.getGlobalPtr()
|
||||
|
||||
@ -102,22 +119,36 @@ class VFSLoader:
|
||||
self.desc = desc
|
||||
self.packagePath = packagePath
|
||||
|
||||
def load_module(self, fullname):
|
||||
def load_module(self, fullname, loadingShared = False):
|
||||
#print >>sys.stderr, "load_module(%s), dir_path = %s, filename = %s" % (fullname, self.dir_path, self.filename)
|
||||
if self.fileType == FTFrozenModule:
|
||||
return self._import_frozen_module(fullname)
|
||||
if self.fileType == FTExtensionModule:
|
||||
return self._import_extension_module(fullname)
|
||||
|
||||
# Check if this is a child of a shared package.
|
||||
if not loadingShared and self.packagePath and '.' in fullname:
|
||||
parentname = fullname.rsplit('.', 1)[0]
|
||||
if parentname in sharedPackages:
|
||||
# It is. That means it's a shared package too.
|
||||
parent = sys.modules[parentname]
|
||||
path = getattr(parent, '__path__', None)
|
||||
importer = VFSSharedImporter()
|
||||
sharedPackages[fullname] = True
|
||||
loader = importer.find_module(fullname, path = path)
|
||||
assert loader
|
||||
return loader.load_module(fullname)
|
||||
|
||||
code = self._read_code()
|
||||
if not code:
|
||||
raise ImportError, 'No Python code in %s' % (fullname)
|
||||
|
||||
mod = sys.modules.setdefault(fullname, new.module(fullname))
|
||||
mod.__file__ = self.filename.cStr()
|
||||
mod.__file__ = self.filename.toOsSpecific()
|
||||
mod.__loader__ = self
|
||||
if self.packagePath:
|
||||
mod.__path__ = [self.packagePath.cStr()]
|
||||
mod.__path__ = [self.packagePath.toOsSpecific()]
|
||||
#print >> sys.stderr, "loaded %s, path = %s" % (fullname, mod.__path__)
|
||||
|
||||
exec code in mod.__dict__
|
||||
return mod
|
||||
@ -137,6 +168,9 @@ class VFSLoader:
|
||||
|
||||
def get_source(self, fullname):
|
||||
return self._read_source()
|
||||
|
||||
def get_filename(self, fullname):
|
||||
return self.filename.toOsSpecific()
|
||||
|
||||
def _read_source(self):
|
||||
""" Returns the Python source for this file, if it is
|
||||
@ -190,7 +224,7 @@ class VFSLoader:
|
||||
|
||||
module = imp.load_module(fullname, None, filename.toOsSpecific(),
|
||||
self.desc)
|
||||
module.__file__ = self.filename.cStr()
|
||||
module.__file__ = self.filename.toOsSpecific()
|
||||
return module
|
||||
|
||||
def _import_frozen_module(self, fullname):
|
||||
@ -199,7 +233,6 @@ class VFSLoader:
|
||||
#print >>sys.stderr, "importing frozen %s" % (fullname)
|
||||
module = imp.load_module(fullname, None, fullname,
|
||||
('', '', imp.PY_FROZEN))
|
||||
#print >>sys.stderr, "got frozen %s" % (module)
|
||||
return module
|
||||
|
||||
def _read_code(self):
|
||||
@ -269,7 +302,7 @@ class VFSLoader:
|
||||
|
||||
if source and source[-1] != '\n':
|
||||
source = source + '\n'
|
||||
code = __builtin__.compile(source, filename.cStr(), 'exec')
|
||||
code = __builtin__.compile(source, filename.toOsSpecific(), 'exec')
|
||||
|
||||
# try to cache the compiled code
|
||||
pycFilename = Filename(filename)
|
||||
@ -289,6 +322,138 @@ class VFSLoader:
|
||||
|
||||
return code
|
||||
|
||||
class VFSSharedImporter:
|
||||
""" This is a special importer that is added onto the meta_path
|
||||
list, so that it is called before sys.path is traversed. It uses
|
||||
special logic to load one of the "shared" packages, by searching
|
||||
the entire sys.path for all instances of this shared package, and
|
||||
merging them. """
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def find_module(self, fullname, path = None, reload = False):
|
||||
#print >>sys.stderr, "shared find_module(%s), path = %s" % (fullname, path)
|
||||
|
||||
if fullname not in sharedPackages:
|
||||
# Not a shared package; fall back to normal import.
|
||||
return None
|
||||
|
||||
if path is None:
|
||||
path = sys.path
|
||||
|
||||
excludePaths = []
|
||||
if reload:
|
||||
# If reload is true, we are simply reloading the module,
|
||||
# looking for new paths to add.
|
||||
mod = sys.modules[fullname]
|
||||
excludePaths = getattr(mod, '_vfs_shared_path', None)
|
||||
if excludePaths is None:
|
||||
# If there isn't a _vfs_shared_path symbol already,
|
||||
# the module must have been loaded through
|
||||
# conventional means. Try to guess which path it was
|
||||
# found on.
|
||||
d = self.getLoadedDirname(mod)
|
||||
excludePaths = [d]
|
||||
|
||||
loaders = []
|
||||
for dir in path:
|
||||
if dir in excludePaths:
|
||||
continue
|
||||
|
||||
importer = sys.path_importer_cache.get(dir, None)
|
||||
if importer is None:
|
||||
try:
|
||||
importer = VFSImporter(dir)
|
||||
except ImportError:
|
||||
continue
|
||||
|
||||
sys.path_importer_cache[dir] = importer
|
||||
|
||||
try:
|
||||
loader = importer.find_module(fullname)
|
||||
if not loader:
|
||||
continue
|
||||
except ImportError:
|
||||
continue
|
||||
|
||||
loaders.append(loader)
|
||||
|
||||
if not loaders:
|
||||
return None
|
||||
return VFSSharedLoader(loaders, reload = reload)
|
||||
|
||||
def getLoadedDirname(self, mod):
|
||||
""" Returns the directory name that the indicated
|
||||
conventionally-loaded module must have been loaded from. """
|
||||
|
||||
fullname = mod.__name__
|
||||
dirname = Filename.fromOsSpecific(mod.__file__).getDirname()
|
||||
|
||||
parentname = None
|
||||
basename = fullname
|
||||
if '.' in fullname:
|
||||
parentname, basename = fullname.rsplit('.', 1)
|
||||
|
||||
path = None
|
||||
if parentname:
|
||||
parent = sys.modules[parentname]
|
||||
path = parent.__path__
|
||||
if path is None:
|
||||
path = sys.path
|
||||
|
||||
for dir in path:
|
||||
pdir = Filename.fromOsSpecific(dir).cStr()
|
||||
if pdir + '/' + basename == dirname:
|
||||
# We found it!
|
||||
return dir
|
||||
|
||||
# Couldn't figure it out.
|
||||
return None
|
||||
|
||||
class VFSSharedLoader:
|
||||
""" The second part of VFSSharedImporter, this imports a list of
|
||||
packages and combines them. """
|
||||
|
||||
def __init__(self, loaders, reload):
|
||||
self.loaders = loaders
|
||||
self.reload = reload
|
||||
|
||||
def load_module(self, fullname):
|
||||
#print >>sys.stderr, "shared load_module(%s), loaders = %s" % (fullname, map(lambda l: l.dir_path, self.loaders))
|
||||
|
||||
mod = None
|
||||
path = []
|
||||
vfs_shared_path = []
|
||||
if self.reload:
|
||||
mod = sys.modules[fullname]
|
||||
path = mod.__path__ or []
|
||||
vfs_shared_path = getattr(mod, '_vfs_shared_path', [])
|
||||
|
||||
for loader in self.loaders:
|
||||
try:
|
||||
mod = loader.load_module(fullname, loadingShared = True)
|
||||
except ImportError:
|
||||
continue
|
||||
for dir in getattr(mod, '__path__', []):
|
||||
if dir not in path:
|
||||
path.append(dir)
|
||||
|
||||
if mod is None:
|
||||
# If all of them failed to load, raise ImportError.
|
||||
raise ImportError
|
||||
|
||||
# If at least one of them loaded successfully, return the
|
||||
# union of loaded modules.
|
||||
mod.__path__ = path
|
||||
|
||||
# Also set this special symbol, which records that this is a
|
||||
# shared package, and also lists the paths we have already
|
||||
# loaded.
|
||||
mod._vfs_shared_path = vfs_shared_path + map(lambda l: l.dir_path, self.loaders)
|
||||
|
||||
return mod
|
||||
|
||||
_registered = False
|
||||
def register():
|
||||
""" Register the VFSImporter on the path_hooks, if it has not
|
||||
@ -300,8 +465,52 @@ def register():
|
||||
if not _registered:
|
||||
_registered = True
|
||||
sys.path_hooks.insert(0, VFSImporter)
|
||||
|
||||
sys.meta_path.insert(0, VFSSharedImporter())
|
||||
|
||||
# Blow away the importer cache, so we'll come back through the
|
||||
# VFSImporter for every folder in the future, even those
|
||||
# folders that previously were loaded directly.
|
||||
sys.path_importer_cache = {}
|
||||
|
||||
def reloadSharedPackage(mod):
|
||||
""" Reloads the specific module as a shared package, adding any
|
||||
new directories that might have appeared on the search path. """
|
||||
|
||||
fullname = mod.__name__
|
||||
path = None
|
||||
if '.' in fullname:
|
||||
parentname = fullname.rsplit('.', 1)[0]
|
||||
parent = sys.modules[parentname]
|
||||
path = parent.__path__
|
||||
|
||||
importer = VFSSharedImporter()
|
||||
loader = importer.find_module(fullname, path = path, reload = True)
|
||||
if loader:
|
||||
loader.load_module(fullname)
|
||||
|
||||
# Also force any child packages to become shared packages, if
|
||||
# they aren't already.
|
||||
for basename, child in mod.__dict__.items():
|
||||
if isinstance(child, types.ModuleType):
|
||||
childname = child.__name__
|
||||
if childname == fullname + '.' + basename and \
|
||||
hasattr(child, '__path__') and \
|
||||
childname not in sharedPackages:
|
||||
sharedPackages[childname] = True
|
||||
reloadSharedPackage(child)
|
||||
|
||||
def reloadSharedPackages():
|
||||
""" Walks through the sharedPackages list, and forces a reload of
|
||||
any modules on that list that have already been loaded. This
|
||||
allows new directories to be added to the search path. """
|
||||
|
||||
#print >> sys.stderr, "reloadSharedPackages, path = %s, sharedPackages = %s" % (sys.path, sharedPackages.keys())
|
||||
|
||||
for fullname in sharedPackages.keys():
|
||||
mod = sys.modules.get(fullname, None)
|
||||
if not mod:
|
||||
continue
|
||||
|
||||
reloadSharedPackage(mod)
|
||||
|
||||
|
||||
|
@ -1096,12 +1096,12 @@ class Freezer:
|
||||
moduleList.append(self.makeForbiddenModuleListEntry(moduleName))
|
||||
else:
|
||||
if origName in sourceTrees:
|
||||
# This is one of our Python source trees.
|
||||
# These are a special case: we don't compile
|
||||
# the __init__.py files within them, since
|
||||
# their only purpose is to munge the __path__
|
||||
# variable anyway. Instead, we pretend the
|
||||
# __init__.py files are empty.
|
||||
# This is one of Panda3D's own Python source
|
||||
# trees. These are a special case: we don't
|
||||
# compile the __init__.py files within them,
|
||||
# since their only purpose is to munge the
|
||||
# __path__ variable anyway. Instead, we
|
||||
# pretend the __init__.py files are empty.
|
||||
code = compile('', moduleName, 'exec')
|
||||
|
||||
if code:
|
||||
|
Loading…
x
Reference in New Issue
Block a user