Supongamos que he creado una aplicación osX sin usar Xcode. Después de compilar con GCC, obtengo un ejecutable que está vinculado a varias otras bibliotecas. Algunas de esas bibliotecas podrían volver a estar vinculadas dinámicamente a otras bibliotecas del sistema no estándar.
¿Existe alguna herramienta que cree un paquete de aplicaciones OSX creando primero las estructuras de directorio requeridas y luego copiando / comprobando / arreglando enlaces de forma recursiva para asegurarse de que todas las dependencias dinámicas también estén en el paquete de aplicaciones?
Supongo que puedo intentar escribir algo como esto, pero me preguntaba si ya existe algo como esto.
Respuestas:
Hay dos formas de crear un paquete de aplicaciones en MacOSX, Easy y Ugly.
La forma más sencilla es utilizar XCode. Hecho.
El problema es que a veces no puedes.
En mi caso, estoy creando una aplicación que crea otras aplicaciones. No puedo asumir que el usuario tiene XCode instalado. También estoy usando MacPorts para crear las bibliotecas de las que depende mi aplicación. Necesito asegurarme de que estos dylibs se incluyan con la aplicación antes de distribuirla.
Descargo de responsabilidad: no estoy calificado para escribir esta publicación, todo lo que contiene ha sido extraído de los documentos de Apple, seleccionando aplicaciones existentes y prueba y error. Funciona para mí, pero probablemente sea incorrecto. Por favor envíeme un correo electrónico si tiene alguna corrección.
Lo primero que debe saber es que un paquete de aplicaciones es solo un directorio.
Examinemos la estructura de un foo.app hipotético.
Info.plist es un archivo XML simple. Puede editarlo con un editor de texto o la aplicación Property List Editor que viene con XCode. (Está en el directorio / Developer / Applications / Utilities /).
Las cosas clave que debe incluir son:
CFBundleName : el nombre de la aplicación.
CFBundleIcon : un archivo de icono que se supone que está en el directorio de contenidos / recursos. Utilice la aplicación Icon Composer para crear el icono. (También está en el directorio / Developer / Applications / Utilities /). Puede simplemente arrastrar y soltar un png en su ventana y debería generar automáticamente los niveles de mip para usted.
CFBundleExecutable : nombre del archivo ejecutable que se supone que está en la subcarpeta Contenidos / MacOS /.
Hay muchas más opciones, las enumeradas anteriormente son solo las mínimas. Aquí hay algo de documentación de Apple sobre el archivo Info.plist y la estructura del paquete de aplicaciones .
Además, aquí hay una muestra de Info.plist.
En un mundo perfecto, simplemente podría colocar su ejecutable en el directorio Contenidos / MacOS / y listo. Sin embargo, si su aplicación tiene dependencias dylib no estándar, no funcionará. Al igual que Windows, MacOS viene con su propio tipo especial de DLL Hell .
Si está utilizando MacPorts para crear bibliotecas con las que se vincula, las ubicaciones de los dylibs estarán codificadas en su ejecutable. Si ejecuta la aplicación en una máquina que tiene los dylibs en la misma ubicación exacta, funcionará bien. Sin embargo, la mayoría de los usuarios no los instalarán; cuando hagan doble clic en su aplicación, simplemente se bloqueará.
Antes de distribuir su ejecutable, deberá recopilar todos los archivos dylib que carga y copiarlos en el paquete de aplicaciones. También necesitará editar el ejecutable para que busque los dylibs en el lugar correcto. es decir, dónde los copió.
La edición manual de un ejecutable suena peligroso, ¿verdad? Afortunadamente, existen herramientas de línea de comandos para ayudar.
Este comando enumerará todos los dylibs de los que depende su aplicación. Si ve alguno que NO esté en la carpeta System / Library o usr / lib, esos son los que deberá copiar en el paquete de la aplicación. Cópielos en la carpeta / Contenido / MacOS /. A continuación, deberá editar el ejecutable para usar los nuevos dylibs.
Primero, debes asegurarte de vincular usando la marca -headerpad_max_install_names. Esto solo asegura que si la nueva ruta dylib es más larga que la anterior, habrá espacio para ella.
En segundo lugar, use install_name_tool para cambiar cada ruta de dylib.
Como ejemplo práctico, digamos que su aplicación usa libSDL y otool enumera su ubicación como "/opt/local/lib/libSDL-1.2.0.dylib".
Primero cópielo en el paquete de la aplicación.
Luego edite el ejecutable para usar la nueva ubicación (NOTA: asegúrese de haberlo construido con el indicador -headerpad_max_install_names)
Vaya, casi hemos terminado. Ahora hay un pequeño problema con el directorio de trabajo actual.
Cuando inicie su aplicación, el directorio actual será el directorio arriba donde se encuentra la aplicación. Por ejemplo: si coloca foo.app en la carpeta / Applcations, el directorio actual cuando inicie la aplicación será la carpeta / Aplicaciones. No el /Applications/foo.app/Contents/MacOS/ como cabría esperar.
Puede modificar su aplicación para que tenga en cuenta esto, o puede usar este pequeño script de iniciador mágico que cambiará el directorio actual e iniciará su aplicación.
Asegúrese de ajustar el archivo Info.plist para que CFBundleExecutable apunte al script de inicio y no al ejecutable anterior.
Ok, todo hecho ahora. Afortunadamente, una vez que conoces todo esto, lo entierras en un script de compilación.
fuente
De hecho, encontré una herramienta muy útil que merece algo de crédito ... NO, no desarrollé esto;)
https://github.com/auriamg/macdylibbundler/
Resolverá todas las dependencias y "arreglará" su ejecutable y sus archivos dylib para que funcionen sin problemas en su paquete de aplicaciones.
... también comprobará las dependencias de sus bibliotecas dinámicas dependientes: D
fuente
Lo uso en mi Makefile ... Crea un paquete de aplicaciones. Léalo y entiéndalo, porque necesitará un archivo de icono png en una carpeta macosx / junto con los archivos PkgInfo e Info.plist que incluyo aquí ...
"funciona en mi computadora" ... lo uso para múltiples aplicaciones en 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
fuente
La solución más simple es: cree una vez un proyecto de Xcode sin cambiar nada (es decir, mantenga la aplicación simple de una ventana que Xcode crea para usted), compile y copie el paquete que creó para usted. Luego, edite los archivos (especialmente el Info.plist) para que se adapten a su contenido y coloque su propio binario en el directorio Contenidos / MacOS /.
fuente
Existen algunas herramientas de código abierto para ayudar a crear paquetes de aplicaciones con bibliotecas dependientes para entornos específicos, por ejemplo, py2app para aplicaciones basadas en Python. Si no encuentra uno más general, quizás pueda adaptarlo a sus necesidades.
fuente
Ojalá hubiera encontrado esta publicación antes ...
Esta es mi forma esquemática de resolver este problema usando una
Run script
fase que se invoca cada vez que construyo unaRelease
versión de mi aplicación:# 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)
Espero que esto pueda ser útil para alguien.
fuente