Узнать расположение исполняемого файла в Какао

Простой вопрос: как узнать расположение исполняемого файла в приложении Какао.

Помните, что во многих 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. Я уверен, что знал это однажды, а потом быстро забыл это.

Другие вопросы по тегам