Friday, December 17, 2010

NSTask

I investigated the use of NSTask (docs). According to the reference:
Using the NSTask class, your program can run another program as a subprocess and can monitor that program’s execution. An NSTask object creates a separate executable entity; it differs from NSThread in that it does not share memory space with the process that creates it.

I think it is possible to get info from the running process (beyond just the fact that it's still running) through stdout, but I haven't tried that yet. Here we just launch a task in three different ways, and use Objective-C for a change of pace. The question is more about permissions, and paths and such.

First, pass /usr/bin/python as the launchPath and an array with two arguments: script0.py (on my Desktop), and the string "to me". Notably, the script file is not executable or even readable by anyone but the user (mode is -rwx------). We set up the task and launch it in one line.

script0.py

import sys
try:
print 'Hello world ' + sys.argv[1] + '!'
except IndexError:
print 'Hello world, everybody!'

task0.m

#import <Foundation/Foundation.h>

int main (int argc, const char* argv[]) {
NSString *path = @"/usr/bin/python";
NSString *home = NSHomeDirectory();
NSString *script = @"/Desktop/script0.py";
NSString *arg1 = [NSString stringWithFormat:@"%@%@", home, script];

NSArray *args = [NSArray arrayWithObjects:arg1,@"to me",nil];
NSTask *t = [NSTask launchedTaskWithLaunchPath:path arguments:args];
NSLog(@"%@",[t description]);
[t waitUntilExit];
return 0;
}


$ gcc -o test task0.m -framework Foundation -fobjc-gc-only
$ ./test
2010-12-17 12:51:41.684 test[10281:903] <NSConcreteTask: 0x20000fa80>
Hello world to me!

Notice that the argument 'to me' came in as a single value. We didn't get this:

$ python script0.py to me
Hello world to!


In the second method we do the shebang or hashbang thing, script1.py is the same as script0.py except we add this as the first line: #! /usr/bin/env python and we make it executable first..

$ chmod +xu script1.py

The code for task1.m

#import <Foundation/Foundation.h>

int main (int argc, const char* argv[]) {
NSString *home = NSHomeDirectory();
NSString *script = @"/Desktop/script1.py";
NSString *path = [NSString stringWithFormat:@"%@%@", home, script];

NSArray *args = [NSArray arrayWithObject:@"to me"];
NSTask *t = [NSTask launchedTaskWithLaunchPath:path arguments:args];
NSLog(@"%@",[t description]);
[t waitUntilExit];
return 0;
}


$ gcc -o test task1.m -framework Foundation -fobjc-gc-only
$ ./test
2010-12-17 12:59:29.594 test[10447:903] <NSConcreteTask: 0x20000cb60>
Hello world to me!


The third method is similar to the first, except we set up the task before launching. This could be useful to set the environment, for example, or change StandardError, etc. We grab the user's environment (post here) and use that. You could change the working directory with setCurrentDirectoryPath, but I didn't try it yet. We re-use script0.py

#import <Foundation/Foundation.h>

int main (int argc, const char* argv[]) {
NSDictionary *eD = [[NSProcessInfo processInfo] environment];
NSTask *t = [[NSTask alloc] init];
[t setLaunchPath:@"/usr/bin/python"];
NSArray *args = [NSArray arrayWithObjects:@"script0.py",@"to me",nil];
[t setArguments:args];
[t setEnvironment:eD];
[t launch];
NSLog(@"%@",[t description]);
[t waitUntilExit];
return 0;
}


$ gcc -o test task2.m -framework Foundation -fobjc-gc-only
$ ./test2010-12-17 13:06:00.543 test[10592:903] <NSConcreteTask: 0x20000f1e0>
Hello world to me!

I see I posted about this before (here). It's so long ago I forgot! (Thanks, Google). In that case, we were in PyObjC and called a C program, so it's a little different. I haven't checked to see that the old code still runs with the current PyObjC. I'll try to do that.