web-dev-qa-db-fra.com

Création d'un ensemble d'applications OSX

Supposons que j'ai créé une application osX sans utiliser Xcode. Après avoir compilé avec GCC, j'obtiens un exécutable qui est lié à plusieurs autres bibliothèques. Certaines de ces bibliothèques peuvent à nouveau être liées dynamiquement à d'autres bibliothèques système non standard

Existe-t-il un outil qui crée un ensemble d'applications OSX en créant d'abord les structures de répertoires requises, puis en copiant/vérifiant/corrigeant de manière récursive les liens pour vous assurer que toutes les dépendances dynamiques se trouvent également dans le groupe d'applications?

Je suppose que je peux essayer d'écrire quelque chose comme ça, mais je me demandais si quelque chose comme ça existe déjà.

77
Yogi

Il existe deux façons de créer un ensemble d'applications sur MacOSX, Easy et Ugly.

Le moyen le plus simple consiste simplement à utiliser XCode. Terminé.

Le problème est parfois impossible.

Dans mon cas, je crée une application qui crée d'autres applications. Je ne peux pas supposer que l'utilisateur a installé XCode. J'utilise également MacPorts pour créer les bibliothèques dont mon application dépend. Je dois m'assurer que ces dylibs sont fournis avec l'application avant de la distribuer.

Avertissement: Je ne suis absolument pas qualifié pour écrire ce post, tout ce qui s'y trouve a été luit de Apple docs, sélectionnant les applications existantes et les essais et erreurs. Cela fonctionne pour moi, mais il est très probablement faux. écrivez-moi si vous avez des corrections.

La première chose que vous devez savoir est qu'un bundle d'applications n'est qu'un répertoire.
Examinons la structure d'un foo.app hypothétique.

 foo.app/
 Table des matières /
 Info.plist 
 MacOS /
 foo 
 Resources /
 foo .icns 

Info.plist est un fichier XML simple. Vous pouvez le modifier avec un éditeur de texte ou l'application Editeur de liste de propriétés fournie avec XCode. (C'est dans le répertoire/Developer/Applications/Utilities /).

Les éléments clés que vous devez inclure sont:

CFBundleName - Le nom de l'application.

CFBundleIcon - Un fichier Icon supposé être dans le répertoire Contents/Resources. Utilisez l'icône Composer app pour créer l'icône. (Elle est également dans le répertoire/Developer/Applications/Utilities /) Vous pouvez simplement faire glisser et déposer un png sur sa fenêtre et devrait générer automatiquement le mip -niveaux pour vous.

CFBundleExecutable - Nom du fichier exécutable supposé se trouver dans le sous-dossier Contents/MacOS /.

Il y a beaucoup plus d'options, celles énumérées ci-dessus ne sont que le strict minimum. Voici quelques Apple sur le fichier Info.plist et structure du bundle d'application .

Voici également un exemple 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> CFBundleGetInfoString </ clé > 
 <string> Foo </string> 
 <key> CFBundleExecutable </key> 
 <string> foo </string> 
 <key> CFBundleIdentifier < /key>
 <string> com.your-company-name.www </string> 
 <key> CFBundleName </key> 
 <string> foo </string> 
 <key> CFBundleIconFile </key> 
 <string> foo.icns </string> 
 <key> CFBundleShortVersionString </key> 
 <string> 0,01 </string> 
 <key> CFBundleInfoDictionaryVersion </key> 
 <string> 6.0 </string> 
 <key> CFBundlePackageType </key> 
 <chaîne > APPL </string> 
 <key> IFMajorVersion </key> 
 <integer> 0 </integer> 
 <key> IFMinorVersion </key> 
 <integer> 1 </integer> 
 </dict> 
 </plist> 

Dans un monde parfait, vous pouvez simplement déposer votre exécutable dans le répertoire Contents/MacOS/et terminer. Cependant, si votre application a des dépendances dylib non standard, cela ne fonctionnera pas. Comme Windows, MacOS est livré avec son propre type spécial de DLL Hell .

Si vous utilisez MacPorts pour créer des bibliothèques avec lesquelles vous liez, les emplacements des dylibs seront codés en dur dans votre exécutable. Si vous exécutez l'application sur une machine qui a les dylibs exactement au même emplacement, elle fonctionnera correctement. Cependant, la plupart des utilisateurs ne les auront pas installés; lorsqu'ils double-cliquent sur votre application, elle se bloque.

Avant de distribuer votre exécutable, vous devrez collecter tous les dylibs qu'il charge et les copier dans le bundle d'application. Vous devrez également modifier l'exécutable afin qu'il recherche les dylibs au bon endroit. c'est-à-dire où vous les avez copiés.

Modifier à la main un exécutable semble dangereux, non? Heureusement, il existe des outils en ligne de commande pour vous aider.

 otool -L nom_exécutable 

Cette commande répertorie tous les dylibs dont votre application dépend. Si vous en voyez qui ne sont PAS dans le dossier System/Library ou usr/lib, ce sont ceux que vous devrez copier dans le bundle d'application. Copiez-les dans le dossier/Contents/MacOS /. Ensuite, vous devrez modifier l'exécutable pour utiliser les nouveaux dylibs.

Tout d'abord, vous devez vous assurer que vous établissez une liaison à l'aide de l'indicateur -headerpad_max_install_names. Cela garantit simplement que si le nouveau chemin dylib est plus long que le précédent, il y aura de la place pour lui.

Deuxièmement, utilisez l'install_name_tool pour modifier chaque chemin dylib.

 install_name_tool -change existing_path_to_dylib @ executable_path/blah.dylib executable_name 

À titre d'exemple pratique, supposons que votre application utilise libSDL , et otool répertorie son emplacement en tant que "/opt/local/lib/libSDL-1.2.0.dylib".

Copiez-le d'abord dans le bundle d'application.

 cp /opt/local/lib/libSDL-1.2.0.dylib foo.app/Contents/MacOS/

Modifiez ensuite l'exécutable pour utiliser le nouvel emplacement (REMARQUE: assurez-vous de l'avoir créé avec l'indicateur -headerpad_max_install_names)

 install_name_tool -change /opt/local/lib/libSDL-1.2.0.dylib @ executable_path/libSDL-1.2.0.dylib foo.app/Contents/MacOS/foo

Ouf, nous avons presque fini. Il y a maintenant un petit problème avec le répertoire de travail actuel.

Lorsque vous démarrez votre application, le répertoire actuel sera le répertoire ci-dessus où se trouve l'application. Par exemple: si vous placez foo.app dans le dossier/Applcations, le répertoire actuel lorsque vous lancerez l'application sera le dossier/Applications. Pas le /Applications/foo.app/Contents/MacOS/ comme vous pourriez vous y attendre.

Vous pouvez modifier votre application pour en tenir compte, ou vous pouvez utiliser ce petit script de lancement magique qui changera le répertoire actuel et lancera votre application.

 #!/bin/bash 
 cd "$ {0%/*}" 
 ./ foo 

Assurez-vous que vous ajustez le fichier Info.plist afin que CFBundleExecutable pointe vers le script de lancement et non vers l'exécutable précédent.

Ok, tout est fait maintenant. Heureusement, une fois que vous connaissez toutes ces choses, vous les enterrez dans un script de construction.

123
hyperlogic

En fait, j'ai trouvé un outil très pratique qui mérite un certain crédit ... NON - je n'ai pas développé cela;)

https://github.com/auriamg/macdylibbundler/

Il résoudra toutes les dépendances et "corrigera" votre exécutable ainsi que vos fichiers dylib pour fonctionner en douceur dans votre bundle d'application.

... il vérifiera également les dépendances de vos bibliothèques dynamiques dépendantes: D

18
Chris

J'utilise ceci dans mon Makefile ... Il crée un bundle d'application. Lisez-le et comprenez-le, car vous aurez besoin d'un fichier d'icône png dans un dossier macosx/avec les fichiers PkgInfo et Info.plist que j'inclus ici ...

"ça marche sur mon ordinateur" ... Je l'utilise pour plusieurs applications sur 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/[email protected]
    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/[email protected]
    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/[email protected]
    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/[email protected]
    sips -z 512 512   macosx/$(APPNAME)Icon.png --out macosx/$(APPNAME).iconset/icon_512x512.png
    cp macosx/$(APPNAME)Icon.png macosx/$(APPNAME).iconset/[email protected]
    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

APPLMyAp
7
subatomic

La solution la plus simple consiste à: créer une fois un projet Xcode sans rien changer (c'est-à-dire conserver la simple application à fenêtre unique que Xcode crée pour vous), la créer et copier le bundle qu'elle a créé pour vous. Ensuite, modifiez les fichiers (notamment Info.plist) en fonction de votre contenu et placez votre propre binaire dans le répertoire Contents/MacOS /.

7
F'x

Il existe des outils open source pour aider à créer des ensembles d'applications avec des bibliothèques dépendantes pour des environnements spécifiques, par exemple py2app pour les applications basées sur Python. Si vous n'en trouvez pas de plus général, vous pouvez peut-être l'adapter à vos besoins.

2
Ned Deily

J'aimerais avoir trouvé ce post plus tôt ....

Voici ma façon sommaire de résoudre ce problème en utilisant un Run script phase qui est invoquée chaque fois que je crée une version Release de mon application:

# 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)

J'espère que cela pourrait être utile à quelqu'un.

2
peetonn