Узнать расположение исполняемого файла в Какао
Простой вопрос: как узнать расположение исполняемого файла в приложении Какао.
Помните, что во многих Unix-подобных ОС люди используют среду PATH для назначения предпочтительного местоположения для своих исполняемых файлов, особенно когда в их системе есть несколько версий одного и того же приложения. Хорошей практикой является то, что наше приложение Cocoa должно найти PREFERRED расположение нужного исполняемого файла.
Например, SVN 1.4 в конфигурации Leopard по умолчанию находился в / usr / bin, и вы установили гораздо более новую версию, скажем SVN 1.5.3, через MacPorts в / opt / local / bin. И вы устанавливаете свой PATH, используя /etc/path.d или.bash_profile или.zshrc, вот так:
экспорт PATH=/opt/local/bin:$PATH
Таким образом, вы можете использовать новую версию SVN вместо старой версии из системы. Хорошо работает в любой терминальной среде. Но не в приложениях Какао. Какао-приложение, насколько я знаю, имеет только стандартную среду PATH, например:
экспорт PATH="/usr/bin:/bin:/usr/sbin:/sbin"
По умолчанию он не будет использовать конфигурацию в /etc/path.d, .bash_profile, .profile, .zshrc и т. Д.
Так как именно мы можем сделать?
ps У нас есть полу-решение, но оно не может полностью удовлетворить цель этого вопроса.
5 ответов
Сложность в попытках сделать это состоит в том, что у пользователя может быть установлена любая оболочка: sh, bash, csh, tcsh и т. Д., И каждая оболочка по-разному настраивает свою терминальную среду. Я не уверен, что сам пойду на неприятности из-за этого, но если ты действительно хочешь, вот путь, по которому я бы пошел.
Первым шагом является выяснение оболочки пользователя. В OS X эта информация хранится в службах каталогов, к которым можно получить доступ либо через API-интерфейсы в DirectoryService.framework, либо с помощью dscl
инструмент командной строки. API DirectoryService - это настоящая боль в заднице, поэтому я бы, вероятно, пошел по пути CLI. В Какао вы можете использовать NSTask для запуска инструмента с аргументами, чтобы получить оболочку пользователя (подробности об этом я оставлю в другом месте). Команда будет выглядеть примерно так:
dscl -plist localhost -read /Local/Default/Users/username UserShell
Это вернет XML-текст, который вы можете интерпретировать как plist и преобразовать в NSDictionary, или вы можете опустить -plist
вариант и разбирать текстовый вывод самостоятельно.
Как только вы узнаете путь к пользовательской оболочке, следующим шагом будет выполнение этой оболочки и указание ей запустить env
Команда для распечатки среды пользователя. Похоже, большинство снарядов принимают -c
опция командной строки, которая позволяет вам передать строку для выполнения - я думаю, вам нужно будет просто предположить, что это общий интерфейс для любой оболочки, выбранной пользователем.
Когда у вас есть пользовательская среда, вы можете извлечь из этого список путей и выполнить поиск любого исполняемого файла, который вы ищете по этому адресу. Как я уже сказал, я действительно не знаю, стоит ли это проблем, но я бы пошел в этом направлении, если бы реализовывал это.
Связанный с ответом Брайана Вебстера:
Более простой способ получить оболочку пользователя - использовать класс NSProcessInfo. например
NSDictionary *environmentDict = [[NSProcessInfo processInfo] environment];
NSString *shellString = [environmentDict objectForKey:@"SHELL"];
Что проще, чем использовать dscl и анализ XML-ввода.
Вот моя реализация, основанная на ответах выше, которая будет вызвана из 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);
}
});
}
PS Причина, по которой это работает, заключается в том, что он улавливает изменения среды, сделанные оболочкой. Например, 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% случаев использования, и это лучшее, что вы можете сделать для своих пользователей из коробки.
Насколько вероятно, что ваши пользователи будут иметь пользовательские версии используемого вами инструмента (и насколько вероятно, что ваше приложение совместимо с произвольными версиями инструмента)? Если ответ "не очень", рассмотрите возможность использования пути к предоставленному системой инструменту по умолчанию и предоставления опытным пользователям способа указать свой собственный путь в качестве предпочтения.
Не указан ли путь для Finder (и, следовательно, для любых приложений Cocoa, запускаемых с помощью графического интерфейса) из вашей оболочки входа? Если ваша оболочка входа в систему и оболочка, которую вы используете в Terminal.app, не совпадают, это, вероятно, приведет к некоторой путанице.
Эта информация может быть полезна: http://lists.apple.com/archives/cocoa-dev/2005/Oct/msg00528.html
По-видимому, "правильный" способ задания переменных среды для процессов графического интерфейса пользователя - это скрытый файл.plist. Я уверен, что знал это однажды, а потом быстро забыл это.