Friday, January 7, 2011

Finding Python (for PyObjC)

This is a post exploring how to hunt down which Python is launched when we build and run a Python Cocoa application using Xcode and the templates from here.

The problem to be solved is that in some situations, the wrong Python is run, and it doesn't have PyObjC installed, so the statement import objc fails with ImportError, and the App terminates.

In order to fix that, we need to figure out which Python is running and either (i) change which one is run, (ii) remove the "wrong" one, (iii) install PyObjC into the "wrong" one or (iv) provide a path to PyObjC. I fixed my problem before by (ii). I never got (iii) working because I haven't figured out yet how to install easy_install when it's not there yet, or alternatively build PyObjC from scratch.

What we'll see here leads to the recommendation that you try (i).

We can start our troubleshooting by inserting this code into before the import of objc, as shown:

import sys
print sys.version
import objc

2.5.4 (r254:67916, Jun 24 2010, 21:47:25) 

Where does this come from? To begin with, it does not depend on this line in main.m:


The line can be commented out and everything still runs fine. I think it may be roadkill left over from the old days.

The second thing is that if we look at the Xcode build settings for either the project or the target we see:

Base SDK Mac OS X 10.5

The name or path of the base SDK being used during the build. The product will be built against the headers and libraries located inside the indicated SDK. This path will be prepended to all search paths, and will be passed through the environment to the compiler and linker. Normally, this path is set at the project level via the "Cross-Develop Using Target SDK" popup in the General tab of the project inspector. Additional SDKs can be specified in the ADDITIONAL_SDKS setting. [SDKROOT]

Since the default SDK was set for 10.5, at link time we expect to link against:


If we double-click on the Python.framework (under Frameworks > Linked Frameworks in the Xcode project) we get a Finder window open to:


If we do get info with the Python.framework selected we get

Name: Python.framework
Path: /System/Library/Frameworks/Python.framework

but all of this says nothing about the version so one might expect it to go with Current, which is actually Python 2.6:


I've looked at environment info. Put this in

import os
for k in os.environ:
print k, os.environ[k]

The only thing interesting is:

PATH /Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin
PYTHONPATH /Users/telliott_admin/Desktop/X/build/Debug/
DYLD_FRAMEWORK_PATH /Users/telliott_admin/Desktop/X/build/Debug
DYLD_LIBRARY_PATH /Users/telliott_admin/Desktop/X/build/Debug

The PATH variable is not the one from my shell. But there isn't any Python in /Developer/usr/bin and both Python and python in /usr/bin give 2.6. So that's not where it comes from. $PYTHONPATH just points to the App.

Let's try to find out more using this trick:

> ~/Desktop/X/build/Debug/
dyld: loaded: /System/Library/Frameworks/Python.framework/Versions/2.5/Python
2.5.4 (r254:67916, Jun 24 2010, 21:47:25)
[GCC 4.2.1 (Apple Inc. build 5646)]
dyld: loaded: /System/Library/Frameworks/Python.framework/Versions/2.5/Extras/lib/python/PyObjC/objc/

We're clearly loading the 2.5 framework. And this behavior can be changed by changing the SDK in Xcode. However, I can't show you anything more right now because I no longer have a failing example! (here)

I don't see anything in any of the settings for the Project, Target or Executable that would explain which Python is actually launched when the App runs. (As you'll see in a minute, there are two choices even for the Python.framework, for example in Version 2.5).

There is some weirdness about the system framework. On three different machines running OS X 10.6.5: Python at top-level in the 2.5 framework seems to be a dynamically linked library.

But it actually runs /usr/bin/Python which is 2.6!:

> cd /System/Library/Frameworks/Python.framework/Versions/2.5
> ls -al P*
-rwxr-xr-x 1 root wheel 3702720 Nov 6 21:53 Python
> Python
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
> file Python
Python: Mach-O universal binary with 3 architectures
Python (for architecture x86_64): Mach-O 64-bit dynamically linked shared library x86_64
Python (for architecture i386): Mach-O dynamically linked shared library i386
Python (for architecture ppc7400): Mach-O dynamically linked shared library ppc

The whole second half of the post, and the part just above, is a hideous mistake. See here for details.

The stupid, it burns.

Or as my boss likes to tell me, "some days you're the dog, and some days you're the hydrant"---he's amused, me not so much.