#### Sitemap Getting Started Utilities Spec Files When Things Go Wrong Standalone Executables Python Archives Analyzing Python Modules An Import Framework Bug Tracker | # When Things Go Wrong #### Contents Finding out What Went Wrong Buildtime Warnings Getting Debug Messages Getting Python's Verbose Imports Helping Installer Find Modules Extending the Path Listing Hidden Imports Extending a Package's __path__ Changing Runtime Behavior Adapting to being "frozen" Accessing Data Files Reporting Bugs Miscellaneous Pmw Win9xpopen ## Finding out What Went Wrong ### Buildtime Warnings When an Analysis step runs, it produces a warnings file (named `warnproject.txt`) in the spec file's directory. Generally, most of these warnings are harmless. For example, `os.py` (which is cross-platform) works by figuring out what platform it is on, then importing (and rebinding names from) the appropriate platform-specific module. So analyzing `os.py` will produce a set of warnings like:
          W: no module named dos (conditional import by os)
          W: no module named ce (conditional import by os)
          W: no module named os2 (conditional import by os)
Note that the analysis has detected that the import is within a conditional block (an `if` statement). The analysis also detects if an import within a function or class, (delayed) or at the top level. A top-level, non-conditional import failure is really a hard error. There's at least a reasonable chance that conditional and / or delayed import will be handled gracefully at runtime. Ignorable warnings may also be produced when a class or function is declared in a package (an `__init__.py` module), and the import specifies package.name. In this case, the analysis can't tell if name is supposed to refer to a submodule of package. Warnings are also produced when an `__import__`, `exec` or `eval` statement is encountered. The `__import__` warnings should almost certainly be investigated. Both `exec` and `eval` can be used to implement import hacks, but usually their use is more benign. Any problem detected here can be handled by hooking the analysis of the module. See Listing Hidden Imports below for how to do it. ### Getting Debug Messages Setting `debug=1` on an `EXE` will cause the executable to put out progress messages (for console apps, these go to `stdout`; for Windows apps, these show as `MessageBox`es). This can be useful if you are doing complex packaging, or your app doesn't seem to be starting, or just to learn how the runtime works. ### Getting Python's Verbose Imports You can also pass a -v (verbose imports) flag to the embedded Python. This can be extremely useful. I usually try it even on apparently working apps, just to make sure that I'm always getting my copies of the modules and no import has leaked out to the installed Python. You set this (like the other runtime options) by feeding a phone TOC entry to the EXE. The easiest way to do this is to change the EXE from:
           EXE(..., anal.scripts, ....)
           to
           EXE(..., anal.scripts + [('v', '', 'OPTION')], ...)
These messages will always go to stdout, so you won't see them on Windows if `console=0`. ## Helping Installer Find Modules ### Extending the Path When the analysis phase cannot find needed modules, it may be that the code is manipulating `sys.path`. The easiest thing to do in this case is tell Analysis about the new directory through the second arg to the constructor. ``
           anal = Analysis(['somedir/myscript.py'],
                           ['path/to/thisdir', 'path/to/thatdir'])
In this case, the Analysis will have a search path: `
           ['somedir', 'path/to/thisdir', 'path/to/thatdir'] + sys.path
` You can do the same when running `Makespec`
           Makespec.py --paths=path/to/thisdir;path/to/thatdir ...
(on *nix, use `:` as the path separator). ### Listing Hidden Imports Hidden imports are fairly common. These can occur when the code is using `__import__` (or, perhaps `exec` or `eval`), in which case you will see a warning in the `warnproject.txt` file. They can also occur when an extension module uses the Python/C API to do an import, in which case Analysis can't detect anything. You can verify that hidden import is the problem by using Python's verbose imports flag. If the import messages say "module not found", but the `warnproject.txt` file has no "no module named..." message for the same module, then the problem is a hidden import. Hidden imports are handled by hooking the module (the one doing the hidden imports) at Analysis time. Do this by creating a file named `hook-module.py` (where module is the fully-qualified Python name, eg, `hook-xml.dom.py`), and placing it in the `hooks` package under Installer's root directory, (alternatively, you can save it elsewhere, and then use the `hookspath` arg to `Analysis` so your private hooks directory will be searched). Normally, it will have only one line: ``
          hiddenimports = ['module1', 'module2']
When the Analysis finds this file, it will proceed exactly as though the module explicitly imported `module1` and `module2`. (Full details on the analysis-time hook mechanism is here). If you successfully hook a publicly distributed module in this way, please send me the hook so I can make it available to others. ### Extending a Package's __path__ Python allows a package to extend the search path used to find modules and sub-packages through the `__path__` mechanism. Normally, a package's `__path__` has only one entry - the directory in which the `__init__.py` was found. But `__init__.py` is free to extend its `__path__` to include other directories. For example, the `win32com.shell.shell` module actually resolves to `win32com/win32comext/shell/shell.pyd`. This is because `win32com/__init__.py` appends `../win32comext` to its `__path__`. Because the `__init__.py` is not actually run during an analysis, we use the same hook mechanism we use for hiddenimports. A static list of names won't do, however, because the new entry on `__path__` may well require computation. So `hook-module.py` should define a method `hook(mod)`. The `mod` argument is an instance of `mf.Module` which has (more or less) the same attributes as a real `module` object. The `hook` function should return a `mf.Module` instance - perhaps a brand new one, but more likely the same one used as an arg, but mutated. See mf for details, and `hook/hook-win32com.py` for an example. Note that manipulations of `__path__` hooked in this way apply to the analysis, and only the analysis. That is, at runtime `win32com.shell` is resolved the same way as `win32com.anythingelse`, and `win32com.__path__` knows nothing of `../win32comext`. Once in awhile, that's not enough. ### Changing Runtime Behavior More bizarre situations can be accomodated with runtime hooks. These are small scripts that manipulate the environment before your main script runs, effectively providing additional top-level code to your script. At the tail end of an analysis, the module list is examined for matches in `rthooks.dat`, which is the string representation of a Python dictionary. The key is the module name, and the value is a list of hook-script pathnames. So putting an entry: ``
           'somemodule': ['path/to/somescript.py'],
into `rthooks.dat` is almost the same thing as `
           anal = Analysis(['path/to/somescript.py', 'main.py'], ...
` except that in using the hook, `path/to/somescript.py` will not be analyzed, (that's not a feature - I just haven't found a sane way fit the recursion into my persistence scheme). Hooks done in this way, while they need to be careful of what they import, are free to do almost anything. One provided hook sets things up so that `win32com` can generate modules at runtime (to disk), and the generated modules can be found in the `win32com` package. ### Adapting to being "frozen" In most sophisticated apps, it becomes necessary to figure out (at runtime) whether you're running "live" or "frozen". For example, you might have a configuration file that (running "live") you locate based on a module's `__file__` attribute. That won't work once the code is packaged up. You'll probably want to look for it based on `sys.executable` instead. The `run` executables set `sys.frozen=1` (and, for in-process COM servers, the embedding DLL sets `sys.frozen='dll'`). For really advanced users, you can access the `iu.ImportManager` as `sys.importManager`. See iu for how you might make use of this fact. ## Accessing Data Files In a `--onedir` distribution, this is easy: pass a list of your data files (in `TOC` format) to the `COLLECT`, and they will show up in the distribution directory tree. The `name` in the `(name, path, 'DATA')` tuple can be a relative path name. Then, at runtime, you can use code like this to find the file:``
           os.path.join(os.path.dirname(sys.executable), relativename))
In a `--onefile`, it's a bit trickier. You can cheat, and add the files to the `EXE` as `BINARY`. They will then be extracted at runtime into the work directory by the C code (which does not create directories, so the `name` must be a plain name), and cleaned up on exit. The work directory is best found by `os.environ['_MEIPASS2']`. Be awawre, though, that if you use `--strip` or `--upx`, strange things may happen to your data - `BINARY` is really for shared libs / dlls. If you add them as `'DATA'` to the `EXE`, then it's up to you to extract them. Use code like this:``
           import sys, carchive
           this = carchive.CArchive(sys.executable)
           data = this.extract('mystuff')[1]
to get the contents as a binary string. See `support/unpackTK.py` for an advanced example (the TCL and TK lib files are in a `PKG` which is opened in place, and then extracted to the filesystem). ## Reporting Bugs Report bugs (or feature requests) here. Please make sure you set Product to `Installer`. (If you choose not to become a registered user, please include some contact information in the report itself.) Subscribe to the Installer Mailing List to discuss Installer related issues. And don't forget to show your appreciation here. ## Miscellaneous ### Pmw Pmw comes with a script named `bundlepmw` in the `bin` directory. If you follow the instructions in that script, you'll end up with a module named `Pmw.py`. Ensure that Builder finds that module and not the development package. ### Win9xpopen If you're using `popen` on Windows and want the code to work on Win9x, you'll need to distribute `win9xpopen.exe` with your app. On older Pythons with Win32all, this would apply to `Win32pipe` and `win32popenWin9x.exe`. (On yet older Pythons, no form of popen worked on Win9x).
copyright 1999-2002 McMillan Enterprises, Inc.