views:

803

answers:

4

I don't know why this method returns a blank string:

- (NSString *)installedGitLocation {
    NSString *launchPath = @"/usr/bin/which";

    // Set up the task
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:launchPath];
    NSArray *args = [NSArray arrayWithObject:@"git"];
    [task setArguments:args];

    // Set the output pipe.
    NSPipe *outPipe = [[NSPipe alloc] init];
    [task setStandardOutput:outPipe];

    [task launch];

    NSData *data = [[outPipe fileHandleForReading] readDataToEndOfFile];
    NSString *path = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];

    return path;
}

If instead of passing @"git" as the argument, I pass @"which" I get /usr/bin/which returned as expected. So at least the principle works.

from the terminal

$ which which
$ /usr/bin/which
$
$ which git
$ /usr/local/git/bin/git

So it works there.

The only thing I can think of is that which isn't searching through all the paths in my environment.

This is driving me crazy! Does anyone have any ideas?

EDIT: It looks like this is about setting up either NSTask or the user's shell (e.g., ~/.bashrc) so that the correct environment ($PATH) is seen by NSTask.

+2  A: 

Is /usr/local/git/bin in your $PATH when you run the program? I think which only looks in the user's $PATH.

codelogic
from logging NSProcessInfo in the method, my Path comes up as PATH = "/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"; which isn't my full shell path. How would I get the user's path so that I could set it in the environment?
Abizern
I edited the question to reflect this issue. +1 for pointing toward the problem.
Pat Notz
Since $PATH can be set in multiple places including ~/.profile ~/.bashrc etc, the easiest solution would probably be the technique outlined in Brian Webseter's link. Something like "/bin/bash -c env | grep ^PATH"
codelogic
+1  A: 

Take a look at the question Find out location of an executable file in Cocoa. It looks like the basic problem is the same. The answer unfortunately isn't nice and neat, but there's some useful info there.

Brian Webster
Actually, there is some very useful information in there.
Abizern
+2  A: 

Running a task via NSTask uses fork() and exec() to actually run the task. The user's interactive shell isn't involved at all. Since $PATH is (by and large) a shell concept, it doesn't apply when you're talking about running processes in some other fashion.

Chris Hanson
More to the point, if the user sets his PATH using one of his shell's run-commands files, the app does not receive that variable because it wasn't run from the shell; as such, the subprocess doesn't inherit it because it's the process's direct child. (cont'd because SO limits length for some reason)
Peter Hosey
But if the user sets PATH in ~/.MacOSX/environment.plist, the application *does* receive the PATH variable, and the subprocess will inherit it.
Peter Hosey
+2  A: 

Try,

    [task setLaunchPath:@"/bin/bash"];
    NSArray *args = [NSArray arrayWithObjects:@"-l",
         @"-c",
         @"which git",
         nil];
    [task setArguments: args];

This worked for me on Snow Leopard; I haven't tested on any other system. The -l (lowercase L) tells bash to "act as if it had been invoked as a login shell", and in the process it picked up my normal $PATH. This did not work for me if the launch path was set to /bit/sh, even with -l.

Vincent Gable
That assumes that the user uses bash, or that they use another Bourne-like shell and they set PATH in their .profile. Anyone who uses tcsh and anyone who uses ksh, zsh, or fish and didn't set PATH in .profile (e.g., they set it in .zshenv instead) is screwed.
Peter Hosey
See this answer on another question, and my comment on it, for the full version of this solution: http://stackoverflow.com/questions/208897/find-out-location-of-an-executable-file-in-cocoa/209463#209463 It's certainly not simple.
Peter Hosey