Załóżmy, że stworzyłem aplikację osX bez użycia Xcode. Po kompilacji z GCC otrzymuję plik wykonywalny, który jest powiązany z kilkoma innymi bibliotekami. Niektóre z tych bibliotek mogą być ponownie dynamicznie połączone z innymi niestandardowymi bibliotekami systemowymi
Czy istnieje jakieś narzędzie, które tworzy pakiet aplikacji OSX, najpierw tworząc wymagane struktury katalogów, a następnie rekurencyjnie kopiując / sprawdzając / naprawiając linki, aby upewnić się, że wszystkie dynamiczne zależności znajdują się również w pakiecie aplikacji?
Chyba mogę spróbować napisać coś takiego, ale zastanawiałem się, czy coś takiego już istnieje.
Odpowiedzi:
Istnieją dwa sposoby tworzenia pakietu aplikacji w systemie MacOSX: łatwy i brzydki.
Najłatwiejszym sposobem jest użycie XCode. Gotowe.
Problem polega na tym, że czasami nie możesz.
W moim przypadku tworzę aplikację, która tworzy inne aplikacje. Nie mogę założyć, że użytkownik ma zainstalowany XCode. Używam również MacPorts do tworzenia bibliotek, od których zależy moja aplikacja. Muszę się upewnić, że te dylibs zostaną dołączone do aplikacji, zanim ją rozpowszechnię.
Zastrzeżenie: nie mam żadnych kwalifikacji do napisania tego posta, wszystko, co jest, zostało przejrzane z dokumentów Apple, oddzielając istniejące aplikacje i próbę i błąd. U mnie działa, ale najprawdopodobniej jest źle. Proszę napisz do mnie, jeśli masz jakieś poprawki.
Pierwszą rzeczą, którą powinieneś wiedzieć, jest to, że pakiet aplikacji to tylko katalog.
Przeanalizujmy strukturę hipotetycznego pliku foo.app.
Info.plist to zwykły plik XML. Możesz go edytować za pomocą edytora tekstu lub aplikacji Property List Editor, która jest dostarczana w pakiecie z XCode. (Znajduje się w katalogu / Developer / Applications / Utilities /).
Najważniejsze rzeczy, które musisz uwzględnić, to:
CFBundleName - nazwa aplikacji.
CFBundleIcon - Zakłada się, że plik ikony znajduje się w katalogu Contents / Resources. Użyj aplikacji Icon Composer, aby utworzyć ikonę. (Jest również w katalogu / Developer / Applications / Utilities /) Możesz po prostu przeciągnąć i upuścić png na jego okno i powinien automatycznie wygenerować dla ciebie poziomy mip.
CFBundleExecutable - nazwa pliku wykonywalnego, który prawdopodobnie znajduje się w podfolderze Contents / MacOS /.
Opcji jest dużo więcej, te wymienione powyżej to tylko absolutne minimum. Oto dokumentacja firmy Apple dotycząca pliku Info.plist i struktury pakietu aplikacji .
Oto przykładowa Info.plist.
W idealnym świecie możesz po prostu wrzucić swój plik wykonywalny do Contents / MacOS / dir i gotowe. Jeśli jednak Twoja aplikacja ma niestandardowe zależności dylib, nie będzie działać. Podobnie jak Windows, MacOS jest wyposażony w specjalny rodzaj biblioteki DLL Hell .
Jeśli używasz MacPorts do budowania bibliotek, z którymi łączysz się, lokalizacje dylibs zostaną zakodowane na stałe w pliku wykonywalnym. Jeśli uruchomisz aplikację na komputerze, który ma dylibs w dokładnie tej samej lokalizacji, będzie działać poprawnie. Jednak większość użytkowników nie będzie ich instalować; po dwukrotnym kliknięciu aplikacji po prostu ulegnie awarii.
Zanim rozpowszechnisz swój plik wykonywalny, musisz zebrać wszystkie pliki dylibs, które ładuje, i skopiować je do pakietu aplikacji. Będziesz także musiał edytować plik wykonywalny, aby szukał dylib we właściwym miejscu. tj. gdzie je skopiowałeś.
Ręczna edycja pliku wykonywalnego brzmi niebezpiecznie, prawda? Na szczęście istnieją narzędzia wiersza poleceń, które mogą pomóc.
To polecenie wyświetli listę wszystkich dylibs, od których zależy Twoja aplikacja. Jeśli zobaczysz takie, które NIE znajdują się w folderze System / Library lub usr / lib, to te, które musisz skopiować do pakietu aplikacji. Skopiuj je do folderu / Contents / MacOS /. Następnie musisz edytować plik wykonywalny, aby używał nowych dylibs.
Po pierwsze, musisz upewnić się, że łączysz się za pomocą flagi -headerpad_max_install_names. To tylko zapewnia, że jeśli nowa ścieżka dylib jest dłuższa niż poprzednia, będzie na nią miejsce.
Po drugie, użyj narzędzia install_name_tool, aby zmienić każdą ścieżkę dylib.
Jako praktyczny przykład, powiedzmy, że Twoja aplikacja używa libSDL , a otool wyświetla swoją lokalizację jako „/opt/local/lib/libSDL-1.2.0.dylib”.
Najpierw skopiuj go do pakietu aplikacji.
Następnie edytuj plik wykonywalny, aby używał nowej lokalizacji (UWAGA: upewnij się, że zbudowałeś go z flagą -headerpad_max_install_names)
Uff, prawie skończyliśmy. Teraz jest mały problem z bieżącym katalogiem roboczym.
Po uruchomieniu aplikacji bieżącym katalogiem będzie katalog powyżej, w którym znajduje się aplikacja. Na przykład: jeśli umieścisz foo.app w folderze / Applcations, wówczas bieżącym katalogiem po uruchomieniu aplikacji będzie folder / Applications. Nie /Applications/foo.app/Contents/MacOS/, jak można się spodziewać.
Możesz zmienić swoją aplikację, aby to uwzględnić, lub możesz użyć tego magicznego małego skryptu uruchamiania, który zmieni bieżący katalog i uruchomi Twoją aplikację.
Upewnij się, że dostosowałeś plik Info.plist tak, aby CFBundleExecutable wskazywało na skrypt uruchamiania, a nie na poprzedni plik wykonywalny.
OK, wszystko gotowe. Na szczęście, kiedy już znasz te wszystkie rzeczy, zakopujesz to w skrypcie kompilacji.
źródło
Właściwie znalazłem bardzo przydatne narzędzie, które zasługuje na uznanie ... NIE - nie rozwinąłem tego;)
https://github.com/auriamg/macdylibbundler/
Rozwiąże wszystkie zależności i „naprawi” plik wykonywalny, a także pliki dylib, aby działały płynnie w pakiecie aplikacji.
... sprawdzi również zależności zależnych dynamicznych bibliotek: D
źródło
Używam tego w moim Makefile ... Tworzy pakiet aplikacji. Przeczytaj to i zrozum, ponieważ będziesz potrzebować pliku ikony png w folderze macosx / wraz z plikami PkgInfo i Info.plist, które tutaj dołączam ...
„to działa na moim komputerze”… Używam tego do wielu aplikacji na Mavericks…
APPNAME=MyApp APPBUNDLE=$(APPNAME).app APPBUNDLECONTENTS=$(APPBUNDLE)/Contents APPBUNDLEEXE=$(APPBUNDLECONTENTS)/MacOS APPBUNDLERESOURCES=$(APPBUNDLECONTENTS)/Resources APPBUNDLEICON=$(APPBUNDLECONTENTS)/Resources appbundle: macosx/$(APPNAME).icns rm -rf $(APPBUNDLE) mkdir $(APPBUNDLE) mkdir $(APPBUNDLE)/Contents mkdir $(APPBUNDLE)/Contents/MacOS mkdir $(APPBUNDLE)/Contents/Resources cp macosx/Info.plist $(APPBUNDLECONTENTS)/ cp macosx/PkgInfo $(APPBUNDLECONTENTS)/ cp macosx/$(APPNAME).icns $(APPBUNDLEICON)/ cp $(OUTFILE) $(APPBUNDLEEXE)/$(APPNAME) macosx/$(APPNAME).icns: macosx/$(APPNAME)Icon.png rm -rf macosx/$(APPNAME).iconset mkdir macosx/$(APPNAME).iconset sips -z 16 16 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_16x16.png sips -z 32 32 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_16x16@2x.png sips -z 32 32 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_32x32.png sips -z 64 64 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_32x32@2x.png sips -z 128 128 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_128x128.png sips -z 256 256 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_128x128@2x.png sips -z 256 256 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_256x256.png sips -z 512 512 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_256x256@2x.png sips -z 512 512 macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_512x512.png cp macosx/$(APPNAME)Icon.png macosx/$(APPNAME).iconset/icon_512x512@2x.png iconutil -c icns -o macosx/$(APPNAME).icns macosx/$(APPNAME).iconset rm -r macosx/$(APPNAME).iconset
Info.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleExecutable</key> <string>MyApp</string> <key>CFBundleGetInfoString</key> <string>0.48.2, Copyright 2013 my company</string> <key>CFBundleIconFile</key> <string>MyApp.icns</string> <key>CFBundleIdentifier</key> <string>com.mycompany.MyApp</string> <key>CFBundleDocumentTypes</key> <array> </array> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>0.48.2</string> <key>CFBundleSignature</key> <string>MyAp</string> <key>CFBundleVersion</key> <string>0.48.2</string> <key>NSHumanReadableCopyright</key> <string>Copyright 2013 my company.</string> <key>LSMinimumSystemVersion</key> <string>10.3</string> </dict> </plist>
PkgInfo
źródło
Najprostszym rozwiązaniem jest: utwórz raz projekt Xcode bez zmiany czegokolwiek (tj. Zachowaj prostą aplikację z jednym oknem, którą tworzy dla Ciebie Xcode), skompiluj ją i skopiuj pakiet, który utworzył dla Ciebie. Następnie edytuj pliki (w szczególności Info.plist) tak, aby odpowiadały Twojej zawartości i umieść swój własny plik binarny w katalogu Contents / MacOS /.
źródło
Istnieje kilka narzędzi typu open source, które pomagają w tworzeniu pakietów aplikacji z zależnymi bibliotekami dla określonych środowisk, na przykład py2app dla aplikacji opartych na języku Python. Jeśli nie znajdziesz bardziej ogólnego, być może dostosujesz go do swoich potrzeb.
źródło
Żałuję, że nie znalazłem tego posta wcześniej ....
Oto mój szkicowy sposób rozwiązania tego problemu przy użyciu
Run script
fazy, która jest wywoływana za każdym razem, gdy tworzęRelease
wersję mojej aplikacji:# this is an array of my dependencies' libraries paths # which will be iterated in order to find those dependencies using otool -L libpaths=("$NDNRTC_LIB_PATH" "$BOOST_LIB_PATH" "$NDNCHAT_LIB_PATH" "$NDNCPP_LIB_PATH" "/opt/local/lib") frameworksDir=$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH executable=$BUILT_PRODUCTS_DIR/$EXECUTABLE_PATH #echo "libpaths $libpaths" bRecursion=0 lRecursion=0 # this function iterates through libpaths array # and checks binary with "otool -L" command for containment # of dependency which has "libpath" path # if such dependency has been found, it will be copied to Frameworks # folder and binary will be fixed with "install_name_tool -change" command # to point to Frameworks/<dependency> library # then, dependency is checked recursively with resolveDependencies function function resolveDependencies() { local binfile=$1 local prefix=$2 local binname=$(basename $binfile) local offset=$((lRecursion*20)) printf "%s :\t%s\n" $prefix "resolving $binname..." for path in ${libpaths[@]}; do local temp=$path #echo "check lib path $path" local pattern="$path/([A-z0-9.-]+\.dylib)" while [[ "$(otool -L ${binfile})" =~ $pattern ]]; do local libname=${BASH_REMATCH[1]} otool -L ${binfile} #echo "found match $libname" printf "%s :\t%s\n" $prefix "fixing $libname..." local libpath="${path}/$libname" #echo "cp $libpath $frameworksDir" ${SRCROOT}/sudocp.sh $libpath $frameworksDir/$libname $(whoami) local installLibPath="@rpath/$libname" #echo "install_name_tool -change $libpath $installLibPath $binfile" if [ "$libname" == "$binname" ]; then install_name_tool -id "@rpath/$libname" $binfile printf "%s :\t%s\n" $prefix "fixed id for $libname." else install_name_tool -change $libpath $installLibPath $binfile printf "%s :\t%s\n" $prefix "$libname dependency resolved." let lRecursion++ resolveDependencies "$frameworksDir/$libname" "$prefix>$libname" resolveBoostDependencies "$frameworksDir/$libname" "$prefix>$libname" let lRecursion-- fi path=$temp done # while done # for printf "%s :\t%s\n" $prefix "$(basename $binfile) resolved." } # resolveDependencies # for some reason, unlike other dependencies which maintain full path # in "otool -L" output, boost libraries do not - they just appear # as "libboost_xxxx.dylib" entries, without fully qualified path # thus, resolveDependencies can't be used and a designated function is needed # this function works pretty much in a similar way to resolveDependencies # but targets only dependencies starting with "libboost_", copies them # to the Frameworks folder and resolves them recursively function resolveBoostDependencies() { local binfile=$1 local prefix=$2 local binname=$(basename $binfile) local offset=$(((bRecursion+lRecursion)*20)) printf "%s :\t%s\n" $prefix "resolving Boost for $(basename $binfile)..." local pattern="[[:space:]]libboost_([A-z0-9.-]+\.dylib)" while [[ "$(otool -L ${binfile})" =~ $pattern ]]; do local libname="libboost_${BASH_REMATCH[1]}" #echo "found match $libname" local libpath="${BOOST_LIB_PATH}/$libname" #echo "cp $libpath $frameworksDir" ${SRCROOT}/sudocp.sh $libpath $frameworksDir/$libname $(whoami) installLibPath="@rpath/$libname" #echo "install_name_tool -change $libname $installLibPath $binfile" if [ "$libname" == "$binname" ]; then install_name_tool -id "@rpath/$libname" $binfile printf "%s :\t%s\n" $prefix "fixed id for $libname." else install_name_tool -change $libname $installLibPath $binfile printf "%s :\t%s\n" $prefix "$libname Boost dependency resolved." let bRecursion++ resolveBoostDependencies "$frameworksDir/$libname" "$prefix>$libname" let bRecursion-- fi done # while printf "%s :\t%s\n" $prefix "$(basename $binfile) resolved." } resolveDependencies $executable $(basename $executable) resolveBoostDependencies $executable $(basename $executable)
Mam nadzieję, że to może być przydatne dla kogoś.
źródło