0

In my Mac OS app I am including a Python.framework (v 2.7), so I added it to 'Linked frameworks' as Required. Also in the app, I am launching a Python script with NSTask like:

//...
pythonEnv = [[[NSBundle mainBundle] privateFrameworksPath] stringByAppendingPathComponent:@"Python.framework/Versions/2.7/bin/python"];
task = [[NSTask alloc] init];
outPipe = [NSPipe pipe];
readHandle = [outPipe fileHandleForReading];
data = [[NSMutableData alloc] init];

args = [NSArray arrayWithObjects: scriptPath, kbt, server, port, username, password, nil];

[task setArguments:args];
[task setLaunchPath: pythonEnv];

readHandle = [outPipe fileHandleForReading];
[task setStandardInput:[NSPipe pipe]];
[task setStandardOutput:outPipe];
[task launch];
[task waitUntilExit];
//...

When I build the app, and in Python script check to see which version it uses with sys.path, it returns:

["/Users/...path to script.../Contents/Resources", "/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip", "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7", "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin", "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac", "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages", "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk", "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-old", "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload", "/Users/tatiana/Library/Python/2.7/lib/python/site-packages", "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages", "/Library/Python/2.7/site-packages"]

... it's not using my included framework. Where else should I be setting the correct path? Is it something in Build Settings I am forgetting?

UPDATE 1 ------------------------

If I don't set task environment, and in Python test with sys.executable, I get:

/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python

... so it looks like it's not using my framework. If set task environment with:

// pythonEnv = [[[NSBundle mainBundle] privateFrameworksPath] stringByAppendingPathComponent:@"Python.framework/Versions/2.7/bin/python"];
NSDictionary *environment = [NSDictionary dictionaryWithObjectsAndKeys: pythonEnv, @"PYTHONPATH", nil];
[task setEnvironment:environment];

and test it, I still get the same result. BUT, if in Python I test with os.environ["PYTHONPATH"], I get:

/Users/tatiana/Desktop/MyApp.app/Contents/Frameworks/Python.framework/Versions/2.7/bin/python

That looks a bit more promissing, but I am confused why sys.executable is not giving me that.

UPDATE 2

My bin/python was not an executable, so I changed it to python2.7; sys.executable still shows that it defaults to Library/...

pythonEnv = [[[NSBundle mainBundle] privateFrameworksPath] stringByAppendingPathComponent:@"Python.framework/Versions/2.7/bin/python2.7"];
NSString *searchPath = [[[NSBundle mainBundle] privateFrameworksPath] stringByAppendingPathComponent:@"Python.framework/Versions/2.7/lib/python2.7"];
NSDictionary *environment = [NSDictionary dictionaryWithObjectsAndKeys: searchPath, @"PYTHONPATH", nil];
[task setEnvironment:environment];
janeh
  • 3,734
  • 7
  • 26
  • 43
  • 3
    This won't solve your problem, but you should use `stringByAppendingPathComponent:` when you want to staple paths together. It more clearly expresses what you're doing. – Peter Hosey Dec 25 '12 at 04:35
  • You've just showed us how you've set the launch path - how do you launch the script? Suppose we need more of your code to be able to help you out – Jay Dec 26 '12 at 08:52

2 Answers2

0

It sounds like you're confusing two related things:

  1. Launching the Python executable that's inside the app bundle
  2. Setting the Python search path to include the Python lib folder that's inside the app bundle

To set the path of the Python executable that NSTask runs, set its launchPath. In Python you can access the value using sys.executable.

To manipulate Python's search path, invoke -[NSTask setEnvironment:] with PYTHONPATH set to your framework's lib/python2.7 folder. In Python you can access that path with sys.path.

paulmelnikow
  • 16,895
  • 8
  • 63
  • 114
  • I rewrote my answer to try to clarify. Have you verified that `/Users/tatiana/Desktop/MyApp.app/Contents/Frameworks/Python.framework/Versions/2.7/bin/python` actually is a python executable and not a symlink? – paulmelnikow Dec 27 '12 at 22:46
  • you're right, python i was linking to was just an alias. I made some updates. – janeh Dec 28 '12 at 04:01
  • added it via 'Linked Frameworks and Libraries'... (I downloaded it form python.org I think??) – janeh Dec 28 '12 at 04:09
  • Oh, so you aren't building the framework. Why then are you copying it into your app bundle? – paulmelnikow Dec 28 '12 at 04:15
  • because I am stupid, I thought you can! ok. back to the drawing board. – janeh Dec 28 '12 at 04:16
  • (i thought when you search for one to link, Xcode finds "framework" builds, and I assumed that was it) – janeh Dec 28 '12 at 04:20
  • I'm lost. Linking to a framework is totally different from invoking a process using NSTask. It'd be easier to answer you question if it were clearer what you were actually trying to accomplish. – paulmelnikow Dec 28 '12 at 04:23
  • Well, since I don't know which version of python users might have, I want to include 2.7 with my app. This python script I am launching via NSTask, and it simply checks mail and returns results to the app. I want to be sure that the script is running from my bundled framework, and not user's default. – janeh Dec 28 '12 at 04:38
0

This may be a known bug. See this old mailing list post and this much more recent bug on bugs.python.org.

There's this comment in Modules/getpath.c of Python 2.7.3:

 /* On Mac OS X, if a script uses an interpreter of the form
  * "#!/opt/python2.3/bin/python", the kernel only passes "python"
  * as argv[0], which falls through to the $PATH search below.
  * If /opt/python2.3/bin isn't in your path, or is near the end,
  * this algorithm may incorrectly find /usr/bin/python. To work
  * around this, we can use _NSGetExecutablePath to get a better
  * hint of what the intended interpreter was, although this
  * will fail if a relative path was used. but in that case,
  * absolutize() should help us out below
  */

While this may not apply directly to you, this is a hint that maybe argv[0] in the executed python process is just "python". Python will then look at $PATH and try to find the absolute path to the Python interpreter if the _NSGetExecutablePath hack mentioned in the comment fails or is not present in your version. Your framework, not being in $PATH, is ignored, so it finds the system Python and uses that.

You could verify this a couple ways. You could set a breakpoint in a debugger and look at the arguments that NSTask eventually passes to exec(). Or, you may be able to look at ps ax and find your Python process (it may help to have your script print its PID and then run forever) and see if the first argument is the full path to your framework. Or, you could check out sys.argv[0] in your script (probably -- maybe it's modified).

If this is indeed the problem, the fix is to get a proper argv[0] set. I don't see a way to do that with NSTask, so your next best bet is probably to include the path to your Python framework's bin/ in the PATH environment variable. At least then Python may infer the correct value for sys.executable.

Or, if nothing in your script really cares about sys.executable, you could just leave it as is after you have determined that your Python is in fact the one being executed, dispite sys.executable.

Phil Frost
  • 3,668
  • 21
  • 29
  • 1
    Also, I intended to suggest you try [invoking libpython directly](http://docs.python.org/2.7/extending/embedding.html#very-high-level-embedding) rather than executing the Python interpreter. It's a little more code to set up, but ultimately you will have better control over the Python environment, and can do neater things. – Phil Frost Jan 03 '13 at 13:47