As described in Architecture, MacTerm has two major goals that complicate imports: it wants to avoid requiring an installer with administrator privileges, and it wants to support several versions of macOS and Python.
So, in the MacTerm front-end, the runtime linker and Python interpreter are manually instructed on how to find components, and each component has a different location depending on the version of the OS.
Example of Importing Quills
In the MacTerm application bundle, you will find two scripts that demonstrate how importing is achieved in a highly portable way:
- MacTerm.app/Contents/MacOS/MacTerm is the main entry point that sets up C++ and Python library paths, and this is what allows imports to work. Once the environment is set up, a Python interpreter is invoked for the actual program, RunApplication.py.
- MacTerm.app/Contents/MacOS/RunApplication.py is a Python script that imports quills modules (near the top). For the purposes of this tutorial, you can ignore everything else that is done in RunApplication.py.
The Architecture page contains full details on the directory structure of components.
Prerequisites for Importing
If you write your own scripts that rely on Quills, you must perform the same steps to locate libraries as those in the example scripts above (assuming you still want to support all macOS versions; see below).
Quills is partly compiled, as noted in Architecture, so you must make sure both the compiled and Python parts are found. There are two key locations:
- Library path. Before the Python interpreter starts, the system’s dynamic linker must already know how to find the C++ part of the Quills library. This code is packaged as a framework, and is platform-specific.
- Module path. The Python interpreter must know how to find the Python interface to quills. By Python convention, this requires a directory that contains both a pure Python interface quills.py and a _quills.so binding. These files are small, but a set must usually exist for every Python version.
To set the library path, define the DYLD_LIBRARY_PATH environment variable (or an equivalent) before starting your Python interpreter (i.e. use the parent shell). Run man dyld for more information on linker search paths.
Sometimes, installing a framework in a path that macOS searches by default, such as /Library/Frameworks, allows you to avoid setting a library path at all. However, MacTerm has not yet been tested with this configuration, and attempting to install the Quills framework system-wide is discouraged.
The module path can be set in a few ways:
- Set the PYTHONPATH environment variable before the Python interpreter that imports Quills begins (i.e. use the parent shell).
- Or, change the sys.path in your Python script to include the appropriate directory, before you import.
- Or, use install privilege to put the appropriate copy of quills.py and _quills.so in a location that Python searches by default. Where this is depends on how Python was built, but it is often a place like /usr/local/lib/pythonX.Y, where X.Y is the base Python version. Since this only saves you from typing one extra line in your Python scripts and adds complexity for your users, it is not recommended!
As noted in Architecture, the version of macOS that is in use may dictate both the path to the compiled component and the path to the script bindings (Python module). If your personal script does not care about older versions of macOS, you can take a simpler approach and only deal with the paths that matter for your computer.
Writing Quills-Dependent Python Scripts
Assuming you have used the steps above to set up an appropriate environment to find frameworks, the following approach is recommended for actually importing modules in your Python code:
try: from quills import Base, Events, Prefs, Session, Terminal except ImportError, err: import sys print >>sys.stderr, "Unable to import Quills." if "DYLD_LIBRARY_PATH" in os.environ: print >>sys.stderr, "Shared library path:", os.environ["DYLD_LIBRARY_PATH"] print >>sys.stderr, "Python path:", sys.path raise err
The advantage of the above code is that failed imports will show you where the linker and the interpreter are actually looking for frameworks and Python modules; the default message is less helpful.