Вот моя реализация, основанная на ответах выше, которая будет вызвана из applicationDidFinishLaunching:
// from http://cocoawithlove.com/2009/05/invoking-other-processes-in-cocoa.html
#import "NSTask+OneLineTasksWithOutput.h"
void FixUnixPath() {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
NSString *userShell = [[[NSProcessInfo processInfo] environment] objectForKey:@"SHELL"];
NSLog(@"User's shell is %@", userShell);
// avoid executing stuff like /sbin/nologin as a shell
BOOL isValidShell = NO;
for (NSString *validShell in [[NSString stringWithContentsOfFile:@"/etc/shells" encoding:NSUTF8StringEncoding error:nil] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]) {
if ([[validShell stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] isEqualToString:userShell]) {
isValidShell = YES;
break;
}
}
if (!isValidShell) {
NSLog(@"Shell %@ is not in /etc/shells, won't continue.", userShell);
return;
}
NSString *userPath = [[NSTask stringByLaunchingPath:userShell withArguments:[NSArray arrayWithObjects:@"-c", @"echo $PATH", nil] error:nil] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (userPath.length > 0 && [userPath rangeOfString:@":"].length > 0 && [userPath rangeOfString:@"/usr/bin"].length > 0) {
// BINGO!
NSLog(@"User's PATH as reported by %@ is %@", userShell, userPath);
setenv("PATH", [userPath fileSystemRepresentation], 1);
}
});
}
P.S. Причина, по которой это работает, заключается в том, что он улавливает изменения среды, сделанные оболочкой. Например. RVM добавляет PATH=$PATH:$HOME/.rvm/bin
к .bashrc при установке. Приложения какао запускаются из launchd, поэтому они не имеют этих изменений в своем PATH.
Я не на 100% доволен этим кодом, потому что он не ловит все. Моим первоначальным намерением было специально работать с RVM, поэтому мне пришлось использовать оболочку без входа в систему, но на практике люди случайным образом помещают модификацию PATH в .bashrc и .bash_profile, так что было бы лучше запустить оба.
У одного из моих пользователей даже было интерактивное меню (!!!) в его профиле оболочки, что, естественно, привело к зависанию этого кода, и я экспортировал флаг оболочки env только для него. :-) Добавление таймаута, вероятно, хорошая идея.
Это также предполагает, что оболочка совместима с bourne и поэтому не работает с fish 2.0, который становится все более популярным среди сообщества хакеров. (Fish считает $ PATH массивом, а не строкой, разделенной двоеточиями. И, таким образом, по умолчанию он печатает ее, используя пробелы в качестве разделителей. Вероятно, можно создать простое исправление, например, запустить for i in $PATH; echo "PATH=$i"; end
, а затем взять только строки, начинающиеся с PATH=
. Фильтрация - хорошая идея в любом случае, потому что скрипты профиля часто печатают что-то самостоятельно.)
В заключение следует отметить, что этот код является важной частью приложения для доставки уже более года (10 лучших платных инструментов для разработчиков в Mac App Store большую часть года). Тем не менее, я сейчас реализую песочницу и убираю ее; естественно, вы не можете сделать этот трюк из изолированного приложения. Я заменяю его явной поддержкой RVM и друзей и вручную воспроизводю их соответствующие изменения в env.
Для тех, кто хочет использовать что-то вроде системного Git из изолированного приложения, обратите внимание, что, хотя у вас нет доступа для чтения файлов и перечисления каталогов, у вас есть доступ к stat - [[NSFileManager defaultManager] fileExistsAtPath:path]
. Вы можете использовать это, чтобы проверить жестко запрограммированный список типичных папок, ищущих ваш бинарный файл, и когда вы найдете места (например, / usr / local или / opt / local или что-то еще), попросите пользователя предоставить вам доступ через NSOpenPanel. Это не охватит каждый случай, но будет обрабатывать 90% случаев использования, и это лучшее, что вы можете сделать для своих пользователей из коробки.