Browse Source

better frozen module handling

David Rose 16 years ago
parent
commit
da2f1cd9c9

+ 9 - 4
direct/src/plugin/p3dInstance.cxx

@@ -67,13 +67,13 @@ P3DInstance(P3D_request_ready_func *func,
 
 
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   _instance_id = inst_mgr->get_unique_id();
   _instance_id = inst_mgr->get_unique_id();
-
-  INIT_LOCK(_request_lock);
-
+  _full_disk_access = false;
   _session = NULL;
   _session = NULL;
   _panda3d = NULL;
   _panda3d = NULL;
   _splash_window = NULL;
   _splash_window = NULL;
   _instance_window_opened = false;
   _instance_window_opened = false;
+
+  INIT_LOCK(_request_lock);
   _requested_stop = false;
   _requested_stop = false;
 
 
 #ifdef __APPLE__
 #ifdef __APPLE__
@@ -844,11 +844,16 @@ scan_app_desc_file(TiXmlDocument *doc) {
     return;
     return;
   }
   }
 
 
-  const char *log_basename = xpackage->Attribute("log");
+  const char *log_basename = xpackage->Attribute("log_basename");
   if (log_basename != NULL) {
   if (log_basename != NULL) {
     _log_basename = log_basename;
     _log_basename = log_basename;
   }
   }
 
 
+  int full_disk_access = 0;
+  if (xpackage->QueryIntAttribute("full_disk_access", &full_disk_access) == TIXML_SUCCESS) {
+    _full_disk_access = (full_disk_access != 0);
+  }
+
   TiXmlElement *xrequires = xpackage->FirstChildElement("requires");
   TiXmlElement *xrequires = xpackage->FirstChildElement("requires");
   while (xrequires != NULL) {
   while (xrequires != NULL) {
     const char *name = xrequires->Attribute("name");
     const char *name = xrequires->Attribute("name");

+ 1 - 0
direct/src/plugin/p3dInstance.h

@@ -155,6 +155,7 @@ private:
   string _session_key;
   string _session_key;
   string _python_version;
   string _python_version;
   string _log_basename;
   string _log_basename;
+  bool _full_disk_access;
 
 
   // Not ref-counted: session is the parent.
   // Not ref-counted: session is the parent.
   P3DSession *_session;
   P3DSession *_session;

+ 27 - 9
direct/src/plugin/p3dSession.cxx

@@ -652,7 +652,16 @@ start_p3dpython(P3DInstance *inst) {
 
 
   _python_root_dir = inst->_panda3d->get_package_dir();
   _python_root_dir = inst->_panda3d->get_package_dir();
 
 
-  mkdir_complete(_start_dir, nout);
+  // We'll be changing the directory to the standard start directory
+  // only if we don't have full disk access set for the instance.  If
+  // we do have this setting, we'll keep the current directory
+  // instead.
+  bool change_dir = !inst->_full_disk_access;
+  string start_dir;
+  if (change_dir) {
+    start_dir = _start_dir;
+    mkdir_complete(start_dir, nout);
+  }
 
 
   // Build up a search path that includes all of the required packages
   // Build up a search path that includes all of the required packages
   // that have already been installed.
   // that have already been installed.
@@ -753,8 +762,8 @@ start_p3dpython(P3DInstance *inst) {
   string log_basename = inst->_log_basename;
   string log_basename = inst->_log_basename;
 
 
   // But we also let it be overridden by the tokens.
   // But we also let it be overridden by the tokens.
-  if (inst->get_fparams().has_token("log")) {
-    log_basename = inst->get_fparams().lookup_token("log");
+  if (inst->get_fparams().has_token("log_basename")) {
+    log_basename = inst->get_fparams().lookup_token("log_basename");
   }
   }
 
 
   // However, it is always written into the temp directory only; the
   // However, it is always written into the temp directory only; the
@@ -790,12 +799,12 @@ start_p3dpython(P3DInstance *inst) {
   nout << "Attempting to start python from " << p3dpython << "\n";
   nout << "Attempting to start python from " << p3dpython << "\n";
 #ifdef _WIN32
 #ifdef _WIN32
   _p3dpython_handle = win_create_process
   _p3dpython_handle = win_create_process
-    (p3dpython, _start_dir, env, _output_filename,
+    (p3dpython, start_dir, env, _output_filename,
      _pipe_read, _pipe_write);
      _pipe_read, _pipe_write);
   bool started_p3dpython = (_p3dpython_handle != INVALID_HANDLE_VALUE);
   bool started_p3dpython = (_p3dpython_handle != INVALID_HANDLE_VALUE);
 #else
 #else
   _p3dpython_pid = posix_create_process
   _p3dpython_pid = posix_create_process
-    (p3dpython, _start_dir, env, _output_filename,
+    (p3dpython, start_dir, env, _output_filename,
      _pipe_read, _pipe_write);
      _pipe_read, _pipe_write);
   bool started_p3dpython = (_p3dpython_pid > 0);
   bool started_p3dpython = (_p3dpython_pid > 0);
 #endif
 #endif
@@ -1037,10 +1046,17 @@ win_create_process(const string &program, const string &start_dir,
   startup_info.wShowWindow = SW_HIDE;
   startup_info.wShowWindow = SW_HIDE;
   startup_info.dwFlags |= STARTF_USESHOWWINDOW;
   startup_info.dwFlags |= STARTF_USESHOWWINDOW;
 
 
+  // If the start directory is empty, meaning not to change the
+  // current directory, then pass NULL in to CreateProcess().
+  const char *start_dir_cstr = NULL;
+  if (!start_dir.empty()) {
+    start_dir_cstr = start_dir.c_str();
+  }
+
   PROCESS_INFORMATION process_info; 
   PROCESS_INFORMATION process_info; 
   BOOL result = CreateProcess
   BOOL result = CreateProcess
     (program.c_str(), NULL, NULL, NULL, TRUE, 0,
     (program.c_str(), NULL, NULL, NULL, TRUE, 0,
-     (void *)env.c_str(), start_dir.c_str(),
+     (void *)env.c_str(), start_dir_cstr,
      &startup_info, &process_info);
      &startup_info, &process_info);
   bool started_program = (result != 0);
   bool started_program = (result != 0);
 
 
@@ -1129,9 +1145,11 @@ posix_create_process(const string &program, const string &start_dir,
     close(to_fd[1]);
     close(to_fd[1]);
     close(from_fd[0]);
     close(from_fd[0]);
 
 
-    if (chdir(start_dir.c_str()) < 0) {
-      nout << "Could not chdir to " << start_dir << "\n";
-      _exit(1);
+    if (!start_dir.empty()) {
+      if (chdir(start_dir.c_str()) < 0) {
+        nout << "Could not chdir to " << start_dir << "\n";
+        _exit(1);
+      }
     }
     }
 
 
     // build up an array of char strings for the environment.
     // build up an array of char strings for the environment.

+ 16 - 0
direct/src/showbase/Sources.pp

@@ -21,6 +21,22 @@
   #define IGATESCAN all
   #define IGATESCAN all
 #end lib_target
 #end lib_target
 
 
+// Define a Python extension module for operating on frozen modules.
+// This is a pure C module; it involves no Panda code or C++ code.
+#begin lib_target
+  #define TARGET extend_frozen
+  #define LIB_PREFIX
+  #if $[OSX_PLATFORM]
+    #define LINK_AS_BUNDLE 1
+    #define BUNDLE_EXT .so
+  #endif
+  #if $[WINDOWS_PLATFORM]
+    #define DYNAMIC_LIB_EXT .pyd
+  #endif
+
+  #define SOURCES extend_frozen.c
+#end lib_target
+
 #if $[CTPROJS]
 #if $[CTPROJS]
   #define INSTALL_SCRIPTS ppython
   #define INSTALL_SCRIPTS ppython
 #endif
 #endif

+ 62 - 41
direct/src/showbase/VFSImporter.py

@@ -8,7 +8,7 @@ import imp
 import struct
 import struct
 import __builtin__
 import __builtin__
 
 
-__all__ = ['register', 'reload_from', 'reload_packages']
+__all__ = ['register', 'freeze_new_modules']
 
 
 vfs = VirtualFileSystem.getGlobalPtr()
 vfs = VirtualFileSystem.getGlobalPtr()
 
 
@@ -273,44 +273,65 @@ def register():
     if not _registered:
     if not _registered:
         _registered = True
         _registered = True
         sys.path_hooks.insert(0, VFSImporter)
         sys.path_hooks.insert(0, VFSImporter)
-    
-def reload_from(root_path, moduleName):
-    """ Reloads the named module from the indicated root directory,
-    merging it with the module already loaded, if any.  This is
-    particularly useful for merging a VFS-mounted package with a
-    previously-frozen package.  It allows you to release the initial
-    version of a package via the freeze mechanism, while still
-    allowing new additions to be added later via multifile.
-
-    See also reload_packages(), which is a convenience function
-    wrapped around this one.  """
-
-    path = root_path + '/' + '/'.join(moduleName.split('.')[:-1])
-    importer = VFSImporter(path)
-    loader = importer.find_module(moduleName)
-    if loader:
-        loader.load_module(moduleName)
         
         
-def reload_packages(multifile, root_path):
-    """ Walks the multifile and looks for Python packages that already
-    exist as frozen modules.  For any such packages found, calls
-    reload_from() to merge them with the preloaded frozen package. """
-
-    for i in range(multifile.getNumSubfiles()):
-        filename = multifile.getSubfileName(i)
-        isInit = False
-        for ext in ['py'] + compiledExtensions:
-            if filename.endswith('/__init__.' + ext):
-                isInit = True
-                break
-        if not isInit:
-            continue
-
-        # Found a package.
-        moduleName = '.'.join(filename.split('/')[:-1])
-        module = sys.modules.get(moduleName, None)
-        if module:
-            file = getattr(module, '__file__', None)
-            if file == '<frozen>':
-                # It's a frozen module; replace it.
-                reload_from(root_path, moduleName)
+def freeze_new_modules(multifile, root_path):
+    """ Walks the multifile and looks for Python packages that are
+    children of frozen modules.  These are converted to frozen
+    modules, since the Python runtime system only supports loading
+    frozen children of frozen modules.
+
+    The multifile must be already mounted at root_path.  """
+
+    # This module is defined by extend_frozen.c in
+    # direct/src/showbase.  It's a special extension module that
+    # provides hooks into the array of frozen modules, which is
+    # otherwise accessible only to the C level.
+    import extend_frozen
+
+    modules = []
+    pyExtensions = ['py'] + compiledExtensions
+    for filename in multifile.getSubfileNames():
+        filename = Filename(filename)
+        ext = filename.getExtension()
+        if ext in pyExtensions:
+            # A Python file.
+            moduleName = Filename(filename)
+            moduleName.setExtension('')
+            isPackage = False
+            if moduleName.getBasename() == '__init__':
+                # A package.
+                moduleName = moduleName.getDirname()
+            else:
+                moduleName = moduleName.cStr()
+            moduleName = '.'.join(moduleName.split('/'))
+            modules.append(moduleName)
+
+    modules.sort()
+    
+    # Now look for any children of frozen modules; these children need
+    # to become frozen modules themselves.
+    existingFrozenModules = {}
+    newFrozen = []
+    for moduleName in modules:
+        if extend_frozen.is_frozen_module(moduleName):
+            # It's a frozen module.  All children require freezing also.
+            existingFrozenModules[moduleName] = True
+        else:
+            # It's not a frozen module, but maybe it needs to be.
+            if '.' in moduleName:
+                parentModuleName = moduleName.rsplit('.', 1)[0]
+                if parentModuleName in existingFrozenModules:
+                    # Bad news.  We have to freeze this one.
+                    existingFrozenModules[moduleName] = True
+
+                    # Load up the module code.
+                    path = root_path + '/' + '/'.join(moduleName.split('.')[:-1])
+                    importer = VFSImporter(path)
+                    loader = importer.find_module(moduleName)
+                    if loader:
+                        code = loader.get_code(moduleName)
+                        newFrozen.append((moduleName, marshal.dumps(code)))
+
+    # Now pass our list of newly-frozen modules to the low level code.
+    if newFrozen:
+        extend_frozen.extend(newFrozen)

+ 204 - 0
direct/src/showbase/extend_frozen.c

@@ -0,0 +1,204 @@
+#include <Python.h>
+
+#ifdef _WIN32
+#define DLLEXPORT __declspec(dllexport)
+#else
+#define DLLEXPORT
+#endif
+
+/*
+ * Call this function to extend the frozen modules array with a new
+ * array of frozen modules, provided in a C-style array, at runtime.
+ * Returns the total number of frozen modules.
+ */
+static int 
+extend_frozen_modules(const struct _frozen *new_modules, int new_count) {
+  int orig_count;
+  struct _frozen *realloc_FrozenModules;
+
+  /* First, count the number of frozen modules we had originally. */
+  orig_count = 0;
+  while (PyImport_FrozenModules[orig_count].name != NULL) {
+    ++orig_count;
+  }
+
+  if (new_count == 0) {
+    /* Trivial no-op. */
+    return orig_count;
+  }
+
+  /* Reallocate the PyImport_FrozenModules array bigger to make room
+     for the additional frozen modules.  We just leak the original
+     array; it's too risky to try to free it. */
+  realloc_FrozenModules = (struct _frozen *)malloc((orig_count + new_count + 1) * sizeof(struct _frozen));
+
+  /* The new frozen modules go at the front of the list. */
+  memcpy(realloc_FrozenModules, new_modules, new_count * sizeof(struct _frozen));
+
+  /* Then the original set of frozen modules. */
+  memcpy(realloc_FrozenModules + new_count, PyImport_FrozenModules, orig_count * sizeof(struct _frozen));
+
+  /* Finally, a single 0-valued entry marks the end of the array. */
+  memset(realloc_FrozenModules + orig_count + new_count, 0, sizeof(struct _frozen));
+
+  /* Assign the new pointer. */
+  PyImport_FrozenModules = realloc_FrozenModules;
+
+  return orig_count + new_count;
+}
+
+/* 
+ * Call this function to extend the frozen modules array with a new
+ * list of frozen modules, provided in a Python-style list of (name,
+ * code) tuples, at runtime.  This function is designed to be called
+ * from Python.
+ *
+ * Returns the total number of frozen modules.
+ */
+static PyObject *
+py_extend_frozen_modules(PyObject *self, PyObject *args) {
+  PyObject *list;
+  int num_elements;
+  int i;
+  struct _frozen *new_modules;
+
+  if (!PyArg_ParseTuple(args, "O", &list)) {
+    return NULL;
+  }
+
+  if (!PySequence_Check(list)) {
+    Py_DECREF(list);
+    PyErr_SetString(PyExc_TypeError, "List required");
+    return NULL;
+  }
+
+  num_elements = PySequence_Size(list);
+  new_modules = (struct _frozen *)malloc(sizeof(struct _frozen) * num_elements);
+
+  for (i = 0; i < num_elements; ++i) {
+    PyObject *tuple;
+    const char *name;
+    const char *code;
+    int size;
+
+    tuple = PySequence_GetItem(list, i);
+    if (!PyArg_ParseTuple(tuple, "ss#", &name, &code, &size)) {
+      return NULL;
+    }
+
+    /* We have to malloc new pointers for the name and code arrays.
+       These pointers will never be freed. */
+    new_modules[i].name = strdup(name);
+    new_modules[i].code = (unsigned char *)malloc(size);
+    new_modules[i].size = size;
+    memcpy(new_modules[i].code, code, size);
+
+    Py_DECREF(tuple);
+  }
+
+  Py_DECREF(list);
+
+  int total_count = extend_frozen_modules(new_modules, num_elements);
+  free(new_modules);
+  
+  return Py_BuildValue("i", total_count);
+}
+
+/* 
+ * Call this function to query whether the named module is already a
+ * frozen module or not.
+ */
+static PyObject *
+py_is_frozen_module(PyObject *self, PyObject *args) {
+  const char *name;
+  int i;
+
+  if (!PyArg_ParseTuple(args, "s", &name)) {
+    return NULL;
+  }
+
+  i = 0;
+  while (PyImport_FrozenModules[i].name != NULL) {
+    if (strcmp(PyImport_FrozenModules[i].name, name) == 0) {
+      Py_INCREF(Py_True);
+      return Py_True;
+    }
+    ++i;
+  }
+
+  Py_INCREF(Py_False);
+  return Py_False;
+}
+
+/*
+ * This returns the tuple (code, isPackage), where code is the code
+ * string associated with the named frozen module, and isPackage is
+ * true if the module is a package, or false if it is a normal
+ * module).  The return value is None if there is no such frozen
+ * module.  You must use the marshal module to convert the code string
+ * to a code object.
+ */ 
+static PyObject *
+py_get_frozen_module_code(PyObject *self, PyObject *args) {
+  const char *name;
+  int i;
+
+  if (!PyArg_ParseTuple(args, "s", &name)) {
+    return NULL;
+  }
+
+  i = 0;
+  while (PyImport_FrozenModules[i].name != NULL) {
+    if (strcmp(PyImport_FrozenModules[i].name, name) == 0) {
+      int is_package = (PyImport_FrozenModules[i].size < 0);
+      return Py_BuildValue("(s#i)", PyImport_FrozenModules[i].code,
+                           abs(PyImport_FrozenModules[i].size),
+                           is_package);
+    }
+    ++i;
+  }
+
+  return Py_BuildValue("");
+}
+
+/* 
+ * Call this function to return a list of the existing frozen module
+ * names.
+ */
+static PyObject *
+py_get_frozen_module_names(PyObject *self, PyObject *args) {
+  int i;
+  PyObject *list;
+
+  if (!PyArg_ParseTuple(args, "")) {
+    return NULL;
+  }
+
+  list = PyList_New(0);
+  i = 0;
+  while (PyImport_FrozenModules[i].name != NULL) {
+    PyObject *name = PyString_FromString(PyImport_FrozenModules[i].name);
+    PyList_Append(list, name);
+    Py_DECREF(name);
+    ++i;
+  }
+
+  return list;
+}
+
+/* Initializes the Python module with our functions. */
+DLLEXPORT void initextend_frozen() {
+  static PyMethodDef extend_frozen_methods[] = {
+    { "extend", py_extend_frozen_modules, METH_VARARGS,
+      "Adds new frozen modules at runtime." },
+    { "is_frozen_module", py_is_frozen_module, METH_VARARGS,
+      "Returns true if the named module is a frozen module." },
+    { "get_frozen_module_code", py_get_frozen_module_code, METH_VARARGS,
+      "Returns the code string associated with the named module." },
+    { "get_frozen_module_names", py_get_frozen_module_names, METH_VARARGS,
+      "Returns a list of frozen module names." },
+    { NULL, NULL, 0, NULL }        /* Sentinel */
+  };
+
+  Py_InitModule("extend_frozen", extend_frozen_methods);
+}

+ 99 - 20
direct/src/showutil/FreezeTool.py

@@ -9,6 +9,12 @@ import imp
 import platform
 import platform
 from distutils.sysconfig import PREFIX, get_python_inc, get_python_version
 from distutils.sysconfig import PREFIX, get_python_inc, get_python_version
 
 
+# Temporary (?) try..except to protect against unbuilt extend_frozen.
+try:
+    import extend_frozen
+except ImportError:
+    extend_frozen = None
+
 import direct
 import direct
 from pandac.PandaModules import *
 from pandac.PandaModules import *
 from pandac.extension_native_helpers import dll_suffix, dll_ext
 from pandac.extension_native_helpers import dll_suffix, dll_ext
@@ -298,21 +304,49 @@ static PyMethodDef nullMethods[] = {
   {NULL, NULL}
   {NULL, NULL}
 };
 };
 
 
-%(dllexport)svoid init%(moduleName)s() {
-  int count;
-  struct _frozen *new_FrozenModules;
+/*
+ * Call this function to extend the frozen modules array with a new
+ * array of frozen modules, provided in a C-style array, at runtime.
+ * Returns the total number of frozen modules.
+ */
+static int 
+extend_frozen_modules(const struct _frozen *new_modules, int new_count) {
+  int orig_count;
+  struct _frozen *realloc_FrozenModules;
+
+  /* First, count the number of frozen modules we had originally. */
+  orig_count = 0;
+  while (PyImport_FrozenModules[orig_count].name != NULL) {
+    ++orig_count;
+  }
 
 
-  count = 0;
-  while (PyImport_FrozenModules[count].name != NULL) {
-    ++count;
+  if (new_count == 0) {
+    /* Trivial no-op. */
+    return orig_count;
   }
   }
-  new_FrozenModules = (struct _frozen *)malloc((count + %(newcount)s + 1) * sizeof(struct _frozen));
-  memcpy(new_FrozenModules, _PyImport_FrozenModules, %(newcount)s * sizeof(struct _frozen));
-  memcpy(new_FrozenModules + %(newcount)s, PyImport_FrozenModules, count * sizeof(struct _frozen));
-  memset(new_FrozenModules + count + %(newcount)s, 0, sizeof(struct _frozen));
 
 
-  PyImport_FrozenModules = new_FrozenModules;
+  /* Reallocate the PyImport_FrozenModules array bigger to make room
+     for the additional frozen modules.  We just leak the original
+     array; it's too risky to try to free it. */
+  realloc_FrozenModules = (struct _frozen *)malloc((orig_count + new_count + 1) * sizeof(struct _frozen));
+
+  /* The new frozen modules go at the front of the list. */
+  memcpy(realloc_FrozenModules, new_modules, new_count * sizeof(struct _frozen));
+
+  /* Then the original set of frozen modules. */
+  memcpy(realloc_FrozenModules + new_count, PyImport_FrozenModules, orig_count * sizeof(struct _frozen));
 
 
+  /* Finally, a single 0-valued entry marks the end of the array. */
+  memset(realloc_FrozenModules + orig_count + new_count, 0, sizeof(struct _frozen));
+
+  /* Assign the new pointer. */
+  PyImport_FrozenModules = realloc_FrozenModules;
+
+  return orig_count + new_count;
+}
+
+%(dllexport)svoid init%(moduleName)s() {
+  extend_frozen_modules(_PyImport_FrozenModules, %(newcount)s);
   Py_InitModule("%(moduleName)s", nullMethods);
   Py_InitModule("%(moduleName)s", nullMethods);
 }
 }
 """
 """
@@ -525,6 +559,7 @@ class Freezer:
         try:
         try:
             module = __import__(moduleName)
             module = __import__(moduleName)
         except:
         except:
+            print "couldn't import %s" % (moduleName)
             module = None
             module = None
 
 
         if module != None:
         if module != None:
@@ -559,6 +594,7 @@ class Freezer:
         try:
         try:
             module = __import__(moduleName)
             module = __import__(moduleName)
         except:
         except:
+            print "couldn't import %s" % (moduleName)
             module = None
             module = None
 
 
         if module != None:
         if module != None:
@@ -733,7 +769,7 @@ class Freezer:
                 self.modules[origName] = self.ModuleDef(origName, implicit = True)
                 self.modules[origName] = self.ModuleDef(origName, implicit = True)
                             
                             
         missing = []
         missing = []
-        for origName in self.mf.any_missing():
+        for origName in self.mf.any_missing_maybe()[0]:
             if origName in startupModules:
             if origName in startupModules:
                 continue
                 continue
             if origName in self.previousModules:
             if origName in self.previousModules:
@@ -752,13 +788,15 @@ class Freezer:
             if prefix not in sourceTrees:
             if prefix not in sourceTrees:
                 # If it's in not one of our standard source trees, assume
                 # If it's in not one of our standard source trees, assume
                 # it's some wacky system file we don't need.
                 # it's some wacky system file we don't need.
+                print "ignoring missing %s" % (origName)
                 continue
                 continue
                 
                 
             missing.append(origName)
             missing.append(origName)
-                
+
         if missing:
         if missing:
             error = "There are some missing modules: %r" % missing
             error = "There are some missing modules: %r" % missing
             print error
             print error
+            print "previous = %s" % (self.previousModules,)
             raise StandardError, error
             raise StandardError, error
 
 
     def __loadModule(self, mdef):
     def __loadModule(self, mdef):
@@ -769,11 +807,23 @@ class Freezer:
             # disk.  In this case, the moduleName may not be accurate
             # disk.  In this case, the moduleName may not be accurate
             # and useful, so load it as a file instead.
             # and useful, so load it as a file instead.
 
 
+            tempPath = None
+            if '.' not in mdef.moduleName:
+                # If we loaded a python file from the root, we need to
+                # temporarily add its directory to the module search
+                # path, so the modulefinder can find any sibling
+                # python files it imports as well.
+                tempPath = Filename(mdef.filename.getDirname()).toOsSpecific()
+                self.mf.path.append(tempPath)
+
             pathname = mdef.filename.toOsSpecific()
             pathname = mdef.filename.toOsSpecific()
             fp = open(pathname, modulefinder.READ_MODE)
             fp = open(pathname, modulefinder.READ_MODE)
             stuff = ("", "r", imp.PY_SOURCE)
             stuff = ("", "r", imp.PY_SOURCE)
             self.mf.load_module(mdef.moduleName, fp, pathname, stuff)
             self.mf.load_module(mdef.moduleName, fp, pathname, stuff)
 
 
+            if tempPath:
+                del self.mf.path[-1]
+
         else:
         else:
             # Otherwise, we can just import it normally.
             # Otherwise, we can just import it normally.
             self.mf.import_hook(mdef.moduleName)
             self.mf.import_hook(mdef.moduleName)
@@ -1063,6 +1113,12 @@ class Freezer:
 
 
         filename = basename + self.sourceExtension
         filename = basename + self.sourceExtension
 
 
+        dllexport = ''
+        dllimport = ''
+        if self.platform == 'win32':
+            dllexport = '__declspec(dllexport) '
+            dllimport = '__declspec(dllimport) '
+
         if compileToExe:
         if compileToExe:
             code = self.frozenMainCode
             code = self.frozenMainCode
             if self.platform == 'win32':
             if self.platform == 'win32':
@@ -1070,6 +1126,8 @@ class Freezer:
             initCode = self.mainInitCode % {
             initCode = self.mainInitCode % {
                 'frozenMainCode' : code,
                 'frozenMainCode' : code,
                 'programName' : os.path.basename(basename),
                 'programName' : os.path.basename(basename),
+                'dllexport' : dllexport,
+                'dllimport' : dllimport,
                 }
                 }
             if self.platform == 'win32':
             if self.platform == 'win32':
                 initCode += self.frozenExtensions
                 initCode += self.frozenExtensions
@@ -1080,17 +1138,16 @@ class Freezer:
             compileFunc = self.compileExe
             compileFunc = self.compileExe
             
             
         else:
         else:
-            dllexport = ''
             if self.platform == 'win32':
             if self.platform == 'win32':
-                dllexport = '__declspec(dllexport) '
                 target = basename + dllext + '.pyd'
                 target = basename + dllext + '.pyd'
             else:
             else:
                 target = basename + '.so'
                 target = basename + '.so'
             
             
             initCode = dllInitCode % {
             initCode = dllInitCode % {
-                'dllexport' : dllexport,
                 'moduleName' : os.path.basename(basename),
                 'moduleName' : os.path.basename(basename),
                 'newcount' : len(moduleList),
                 'newcount' : len(moduleList),
+                'dllexport' : dllexport,
+                'dllimport' : dllimport,
                 }
                 }
             compileFunc = self.compileDll
             compileFunc = self.compileDll
 
 
@@ -1215,21 +1272,43 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
         try:
         try:
             return modulefinder.ModuleFinder.find_module(self, name, path, parent = parent)
             return modulefinder.ModuleFinder.find_module(self, name, path, parent = parent)
         except ImportError:
         except ImportError:
-            # It wasn't found.  Maybe it's one of ours.
+            # It wasn't found through the normal channels.  Maybe it's
+            # one of ours, or maybe it's frozen?
             if path:
             if path:
                 # Only if we're not looking on a particular path,
                 # Only if we're not looking on a particular path,
                 # though.
                 # though.
                 raise
                 raise
 
 
-        # This loop is roughly lifted from
-        # extension_native_helpers.Dtool_PreloadDLL().
+            if extend_frozen and extend_frozen.is_frozen_module(name):
+                # It's a frozen module.
+                return (None, name, ('', '', imp.PY_FROZEN))
+
+        # Look for a dtool extension.  This loop is roughly lifted
+        # from extension_native_helpers.Dtool_PreloadDLL().
         filename = name + dll_suffix + dll_ext
         filename = name + dll_suffix + dll_ext
         for dir in sys.path + [sys.prefix]:
         for dir in sys.path + [sys.prefix]:
             lib = os.path.join(dir, filename)
             lib = os.path.join(dir, filename)
             if os.path.exists(lib):
             if os.path.exists(lib):
                 file = open(lib, 'rb')
                 file = open(lib, 'rb')
-                return (file, lib, (dll_ext, 'rb', 3))
+                return (file, lib, (dll_ext, 'rb', imp.C_EXTENSION))
 
 
         message = "DLL loader cannot find %s." % (name)
         message = "DLL loader cannot find %s." % (name)
         raise ImportError, message
         raise ImportError, message
         
         
+    def load_module(self, fqname, fp, pathname, (suffix, mode, type)):
+        if type == imp.PY_FROZEN:
+            # It's a frozen module.
+            co, isPackage = extend_frozen.get_frozen_module_code(pathname)
+            m = self.add_module(fqname)
+            m.__file__ = '<frozen>'
+            if isPackage:
+                m.__path__ = pathname
+            co = marshal.loads(co)
+            if self.replace_paths:
+                co = self.replace_paths_in_code(co)
+            m.__code__ = co
+            self.scan_code(co, m)
+            self.msgout(2, "load_module ->", m)
+            return m
+        
+        return modulefinder.ModuleFinder.load_module(self, fqname, fp, pathname, (suffix, mode, type))

+ 43 - 21
direct/src/showutil/Packager.py

@@ -46,10 +46,20 @@ class Packager:
             if not self.newName:
             if not self.newName:
                 self.newName = self.filename.cStr()
                 self.newName = self.filename.cStr()
 
 
-            packager = package.packager
             ext = Filename(self.newName).getExtension()
             ext = Filename(self.newName).getExtension()
+            if ext == 'pz':
+                # Strip off a .pz extension; we can compress files
+                # within the Multifile without it.
+                filename = Filename(self.newName)
+                filename.setExtension('')
+                self.newName = filename.cStr()
+                ext = Filename(self.newName).getExtension()
+                if self.compress is None:
+                    self.compress = True
+
+            packager = package.packager
             if self.compress is None:
             if self.compress is None:
-                self.compress = (ext not in packager.uncompressibleExtensions)
+                self.compress = (ext not in packager.uncompressibleExtensions and ext not in packager.imageExtensions)
 
 
             if self.executable is None:
             if self.executable is None:
                 self.executable = (ext in packager.executableExtensions)
                 self.executable = (ext in packager.executableExtensions)
@@ -176,7 +186,7 @@ class Packager:
             # Add the explicit py files that were requested by the
             # Add the explicit py files that were requested by the
             # pdef file.  These get turned into Python modules.
             # pdef file.  These get turned into Python modules.
             for file in self.files:
             for file in self.files:
-                ext = file.filename.getExtension()
+                ext = Filename(file.newName).getExtension()
                 if ext != 'py':
                 if ext != 'py':
                     continue
                     continue
 
 
@@ -252,13 +262,36 @@ class Packager:
             else:
             else:
                 self.__addImplicitDependenciesPosix()
                 self.__addImplicitDependenciesPosix()
 
 
-            # Now add all the real, non-Python files.  This will
-            # include the extension modules we just discovered above.
+            # Now add all the real, non-Python files (except model
+            # files).  This will include the extension modules we just
+            # discovered above.
+            for file in self.files:
+                ext = Filename(file.newName).getExtension()
+                if ext == 'py':
+                    # Already handled, above.
+                    continue
+
+                if file.isExcluded(self):
+                    # Skip this file.
+                    continue
+                
+                if not self.dryRun:
+                    if ext == 'egg' or ext == 'bam':
+                        # Skip model files this pass.
+                        pass
+                    else:
+                        # Any other file.
+                        self.addComponent(file)
+
+            # Finally, now add the model files.  It's important to add
+            # these after we have added all of the texture files, so
+            # we can determine which textures need to be implicitly
+            # pulled in.
 
 
             # We walk through the list as we modify it.  That's OK,
             # We walk through the list as we modify it.  That's OK,
             # because we may add new files that we want to process.
             # because we may add new files that we want to process.
             for file in self.files:
             for file in self.files:
-                ext = file.filename.getExtension()
+                ext = Filename(file.newName).getExtension()
                 if ext == 'py':
                 if ext == 'py':
                     # Already handled, above.
                     # Already handled, above.
                     continue
                     continue
@@ -268,25 +301,13 @@ class Packager:
                     continue
                     continue
                 
                 
                 if not self.dryRun:
                 if not self.dryRun:
-                    if ext == 'pz':
-                        # Strip off an implicit .pz extension.
-                        filename = Filename(file.filename)
-                        filename.setExtension('')
-                        filename = Filename(filename.cStr())
-                        ext = filename.getExtension()
-
-                        filename = Filename(file.newName)
-                        if filename.getExtension() == 'pz':
-                            filename.setExtension('')
-                            file.newName = filename.cStr()
-
                     if ext == 'egg':
                     if ext == 'egg':
                         self.addEggFile(file)
                         self.addEggFile(file)
                     elif ext == 'bam':
                     elif ext == 'bam':
                         self.addBamFile(file)
                         self.addBamFile(file)
                     else:
                     else:
-                        # Any other file.
-                        self.addComponent(file)
+                        # Handled above.
+                        pass
 
 
             # Now that we've processed all of the component files,
             # Now that we've processed all of the component files,
             # (and set our platform if necessary), we can generate the
             # (and set our platform if necessary), we can generate the
@@ -838,7 +859,7 @@ class Packager:
 
 
         def addEggFile(self, file):
         def addEggFile(self, file):
             # Precompile egg files to bam's.
             # Precompile egg files to bam's.
-            np = self.packager.loader.loadModel(file.filename, okMissing = True)
+            np = self.packager.loader.loadModel(file.filename)
             if not np:
             if not np:
                 raise StandardError, 'Could not read egg file %s' % (file.filename)
                 raise StandardError, 'Could not read egg file %s' % (file.filename)
 
 
@@ -938,6 +959,7 @@ class Packager:
 
 
             self.addFile(filename, newName = newName, explicit = False,
             self.addFile(filename, newName = newName, explicit = False,
                          compress = False)
                          compress = False)
+            return newName
 
 
         def addComponent(self, file):
         def addComponent(self, file):
             if file.platformSpecific:
             if file.platformSpecific:

+ 4 - 0
direct/src/showutil/packp3d.py

@@ -134,6 +134,9 @@ def main(appRunner):
     packp3d.p3d. """
     packp3d.p3d. """
 
 
     print "args = %s" % (appRunner.argv,)
     print "args = %s" % (appRunner.argv,)
+    vfs = VirtualFileSystem.getGlobalPtr()
+    print "cwd = %s, %s" % (vfs.getCwd(), ExecutionEnvironment.getCwd())
+    print "sys.path = %s" % (sys.path,)
     try:
     try:
         makePackedApp(appRunner.argv[1:])
         makePackedApp(appRunner.argv[1:])
     except ArgumentError, e:
     except ArgumentError, e:
@@ -142,6 +145,7 @@ def main(appRunner):
     sys.exit(0)
     sys.exit(0)
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
+    print "sys.path = %s" % (sys.path,)
     try:
     try:
         makePackedApp(sys.argv[1:])
         makePackedApp(sys.argv[1:])
     except ArgumentError, e:
     except ArgumentError, e:

+ 5 - 4
direct/src/showutil/runp3d.py

@@ -191,9 +191,10 @@ class AppRunner(DirectObject):
         os.listdir = file.listdir
         os.listdir = file.listdir
         os.walk = file.walk
         os.walk = file.walk
 
 
-        # Make "/mf" our "current directory", for running the multifiles
-        # we plan to mount there.
-        vfs.chdir(MultifileRoot)
+        if not self.fullDiskAccess:
+            # Make "/mf" our "current directory", for running the multifiles
+            # we plan to mount there.
+            vfs.chdir(MultifileRoot)
 
 
     def startIfReady(self):
     def startIfReady(self):
         if self.started:
         if self.started:
@@ -300,7 +301,7 @@ class AppRunner(DirectObject):
 
 
         # Mount the Multifile under /mf, by convention.
         # Mount the Multifile under /mf, by convention.
         vfs.mount(mf, MultifileRoot, vfs.MFReadOnly)
         vfs.mount(mf, MultifileRoot, vfs.MFReadOnly)
-        VFSImporter.reload_packages(mf, MultifileRoot)
+        VFSImporter.freeze_new_modules(mf, MultifileRoot)
 
 
         # Load any prc files in the root.  We have to load them
         # Load any prc files in the root.  We have to load them
         # explicitly, since the ConfigPageManager can't directly look
         # explicitly, since the ConfigPageManager can't directly look

+ 5 - 1
direct/src/stdpy/file.py

@@ -248,7 +248,11 @@ open = file
 def listdir(path):
 def listdir(path):
     """ Implements os.listdir over vfs. """
     """ Implements os.listdir over vfs. """
     files = []
     files = []
-    for file in _vfs.scanDirectory(path):
+    dirlist = _vfs.scanDirectory(path)
+    if dirlist is None:
+        message = 'No such file or directory: %s' % (path)
+        raise OSError, message
+    for file in dirlist:
         files.append(file.getFilename().getBasename())
         files.append(file.getFilename().getBasename())
     return files
     return files