Embarquer dylib et sous-fameworks, résolution des liens grâce à  l'install_name

ChachaChacha Membre
juin 2008 modifié dans API AppKit #1
1. Le Contexte
2. Les chemins
   2.1 Le install_name
   2.2 le loader_path
   2.3 le rpath
   2.4 les dépendances
3. Les outils
   3.1 otool
   3.2 install_name_tool
4. La résolution de l'exemple
5. Pour finir

[size=10pt]1. Le Contexte[/size]

Suite au fil de discussion Linkage de dylib dans un framework, je vous propose quelques explications sur la façon de créer un framework encapsulant, de façon invisible pour l'utilisateur final, des bibliothèques dynamiques (fichiers dylib) et des sous-frameworks.
Il se trouve que l'exemple qui m'a confronté à  ce problème contient quasiment toutes les difficultés possibles, donc devrait couvrir un large panel de cas.
Même sans en être conscient, j'ai peut-être commis des erreurs dues à  une mauvaise compréhension, donc n'hésitez pas à  discuter tout cela.

Pour commencer, voyez sur l'image jointe la structure que je veux obtenir pour mon framework.
index.php?action=dlattach;topic=2728.0;attach=2094
-ObjectLoader a été créé avec le modèle XCode "Framework".
-J'ai mis SDL.framework et SDL_image.framework dans une "Build Phase" "Copy Files" a destination des Frameworks
-J'ai mis les libboost*.dylib dans une "Build Phase" "Copy Files" à  destination des Executables

Je considère aussi que je n'ai pas à  recompiler moi-même, avec des paramètres modifiés, les SDL*.framework et libbost*.dylib. Je veux pouvoir les utiliser tels que je les ai récupérés.

Dans la suite, lorsque je parle d'un framework de façon équivalente à  une bibliothèque dylib, je parle en réalité du binaire embarqué dans le framework (et qui porte le même nom).

[size=10pt]2. Les chemins[/size]

La première chose à  comprendre est que lors du linkage dynamique entre une cible (le futur "appelant") et la bibliothèque -framework ou dylib- (la future "appelée"), des informations contenues dans la bibliothèque sont aussi inscrites dans la cible. Utilisons des noms de code : ma cible sera T et la bibliothèque (ou framework) B.

[size=9pt]2.1 Le install_name[/size]
Au moment de la compilation de B, il lui est attribué un "install_name", déterminant le chemin où cette bibliothèque sera installée (disponible). Si le chemin n'est pas absolu, il peut être relatif à  un jeton représentant le chemin de toute cible T à  laquelle B sera liée.
Par exemple, le framework Cocoa de Apple a un install_name égal à  /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa  (absolue), tandis que dans mon ObjectLoader.framework aura un install_name égal à  @executable_path/../Frameworks/ObjectLoader.framework/Versions/A/ObjectLoader.
En effet, voyez l'image
index.php?action=dlattach;topic=2728.0;attach=2093
Mon application (EssaiMultiDoc), à  son lancement, a un @executable_path égal à  (...)/EssaiMultiDoc/Contents/MacOS. Le chemin relatif pour trouver ObjectLoader est bien celui que j'ai spécifié précédemment.

Nous voyons donc que le install_name d'une bibliothèque n'est pas anodin : il doit être spécifié intelligemment lors de la compilation, en fonction de l'usage. Et si une bibliothèque est destinée à  être embarquée de façon différente dans des structures différentes, il faut prévoir de personnaliser le install_name dans chaque cas.

Las ! Si vous n'êtes pas propriétaire de la bibliothèque, comment définir cet install_name ? La bonne nouvelle, c'est que vous pouvez le modifier, après la compilation, grâce à  des outils présentés dans la section suivante (Les outils).

[size=9pt]2.2 le loader_path[/size]
Dans le cas d'une imbrication de frameworks, le @executable_path montre ses limites. Par exemple, lors du chargement de ObjectLoader.Framework contenu dans EssaiMultiDoc, le @executable_path reste égal à  (...)/EssaiMultiDoc/Contents/MacOS, alors qu'on aurait pu s'attendre à  ce qu'il devienne (...)/EssaiMultiDoc/Frameworks/ObjectLoader.framework/Versions/A
En réalité, c'est le @loader_path qui se comporte ainsi.

[size=9pt]2.3 le rpath[/size]
MacOS 10.5 a introduit un @rpath, qui permet une recherche récursive automatique à  partir d'un emplacement donné. Ne l'ayant pas utilisé, je n'en parle pas ici.

[size=9pt]2.4 Les dépendances[/size]
Autre précision : lorsqu'une bibliothèque B est liée à  une cible T, cette dernière enregistre le install_name de B dans ses dépendances. Si une bibliothèque dynamique est déplacée, il faut donc envisager de modifier les références contenues dans les cibles auxquelles elle a été liée. Heureusement, là  aussi, des outils existent pour réaliser cela.

[size=10pt]3. Les outils[/size]

Je ne présente, et succintement, encore, que deux outils. Le premier outil qui nous est utile permet de lister le install_name et les dépendances d'un binaire. Il s'agit de otool avec l'option -L.

[size=9pt]3.1 otool[/size]


otool -L SDL_image.framework/SDL_image
SDL_image.framework/SDL_image:
@executable_path/../Frameworks/SDL_image.framework/Versions/A/SDL_image (compatibility version 1.0.0, current version 1.0.0)
@executable_path/../Frameworks/SDL.framework/Versions/A/SDL (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.3)
/usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.3.3)

On voit ici que :
-le install_name de SDL_image est @executable_path/../Frameworks/SDL_image.framework/Versions/A/SDL_image
-SDL_image dépend de SDL.framework, qu'il s'attend à  trouver à  l'emplacement relatif @executable_path/../Frameworks/SDL.framework/Versions/A/SDL
-les autres dépendances avec de librairies du système sont avec des chemins absolus

[size=9pt]3.2 install_name_tool[/size]
Le deuxième et dernier outil qui nous sera utile est install_name_tool.
Son option -id permet de définir un install_name sur un binaire, tandis que son option -change permet de modifier le chemin d'une dépendance dans un binaire.

[size=10pt]4. La résolution de l'exemple[/size]
Fort de toutes les explications précédentes, nous pouvons nous attaquer à  l'exemple initial et étudier le travail nécessaire.

SDL.framework initialement :

otool -L SDL.framework/SDL
SDL.framework/SDL:
@executable_path/../Frameworks/SDL.framework/Versions/A/SDL (compatibility version 1.0.0, current version 1.0.0)

L'install_path ne conviendra pas à  notre exemple,  car @executable_path sera au final EssaiMultiDoc/Contents/MacOS. Nous devons donc changer le install_path

install_name_tool -id @loader_path/Frameworks/SDL.framework/Versions/A/SDL SDL.framework/SDL
otool -L SDL.framework/SDL
SDL.framework/SDL:
@loader_path/Frameworks/SDL.framework/Versions/A/SDL (compatibility version 1.0.0, current version 1.0.0)



SDL_image.framework initialement :

otool -L SDL_image.framework/SDL_image
SDL_image.framework/SDL_image:
@executable_path/../Frameworks/SDL_image.framework/Versions/A/SDL_image
@executable_path/../Frameworks/SDL.framework/Versions/A/SDL

Ici encore, le install_path ne conviendra pas, mais la dépendance à  SDL n'est pas bonne non plus au vu de notre structure finale ! Le loader_path de SDL_image sera (...)ObjectLoader.framework/Versions/A/Frameworks/SDL.framework/Versions/A. Il faut donc modifier le chemin relatif vers SDL en @loader_path/../../../SDL.framework/Versions/A/SDL

install_name_tool -id @loader_path/Frameworks/SDL_image.framework/Versions/A/SDL_image SDL_image.framework/SDL_image
install_name_tool -change @executable_path/../Frameworks/SDL.framework/Versions/A/SDL  @loader_path/../../../SDL.framework/Versions/A/SDL


libbost_filesystem-mt-1_35.dylib initialement :

otool -L libboost_filesystem-mt-1_35.dylib
libboost_filesystem-mt-1_35.dylib:
libboost_filesystem-mt-1_35.dylib
libboost_system-mt-1_35.dylib

À modifier en

install_name_tool -id @loader_path/libboost_filesystem-mt-1_35.dylib libboost_filesystem-mt-1_35.dylib
install_name_tool -change libboost_system-mt-1_35.dylib @loader_path/libboost_system-mt-1_35.dylib libboost_system-mt-1_35.dylib


Et pour finir, le install_name de ObjectLoader, lui, peut être laissé à  sa valeur par défaut tout à  fait convenable dans notre cas de
@executable_path/../Frameworks/ObjectLoader.framework/Versions/A/ObjectLoader

[size=10pt]5. Pour finir[/size]
Toutes les modification à  base de install_name_tool sont à  faire après la compil/linkage de ObjectLoader, et je vous conseille de le faire dans une ultime "Script build phase" avec XCode.

+
Chacha

Réponses

  • ChachaChacha Membre
    10:58 modifié #2
    Salut,

    Je m'inquiétais de savoir si j'avais raconté des âneries ou fait des omissions dommageables... À ceux qui maà®trisent le sujet, y a-t-il des choses à  savoir en plus ? Comme j'ai découvert tout ça en bricolant, je n'ai pas garantie d'exactitude.
    Ce serait d'ailleurs intéressant de comparer avec les .so de Linux ou les DLL de Windows. Quelqu'un sait comment fonctionnent ces dernières dans des situations similaires ?

    +
    Chacha
  • Philippe49Philippe49 Membre
    10:58 modifié #3
    Je ne maà®trise pas du tout le sujet, mais je veux bien jouer le rôle du cobaye ...

    Première question :
    Où trouve-t-on  un framework SDL de qualité indiscutable ?
    Quelle attitude prendre pour les mises à  jour de ce framework ?
    Documentation ?
  • Philippe49Philippe49 Membre
    10:58 modifié #4
    dans 1212652746:

    Première question :
    Où trouve-t-on  un framework SDL de qualité indiscutable ?
    Quelle attitude prendre pour les mises à  jour de ce framework ?
    Documentation ?


    http://www.libsdl.org/ ?
  • ChachaChacha Membre
    10:58 modifié #5
    dans 1212660461:

    Heu... oui, la SDL se trouve sur http://www.libsdl.org. Mais quel rapport avec le sujet ? Ici il s'agit juste d'un framework d'exemple, alors que j'ai ouvert ce fil sur la problématique du linkage...
  • Philippe49Philippe49 Membre
    10:58 modifié #6
    dans 1212660902:

    Mais quel rapport avec le sujet ?

    Aucun ...  ::) désolé ...
    Je veux simplement lire ton document en mode "action", et comme je n'avais jamais utilisé SDL, je me suis dit que c'était le moment ... Tout cela peut effectivement sembler tortueux, et relever de la vie privée, redésolé  ::). 
  • 10:58 modifié #7
    Merci.
  • 10:58 modifié #8
    Bonjour,

    Je n'ai pas encore cherché à  résoudre mon problème mais déjà  je le connais...

    Une dylib est embarquée dans un framewok sans problème grace à  la install_name_tool (merci chacha pour ta poste pile au bon moment).

    Maintenant cette même dylib est linkée à  d'autres (libcurl et libxml dans mon cas).
    Elle sont embarquées toutes les trois au même niveau (même dossier que l'executable du Framework).

    Un otool -L donne donc :

    @loader_path/libsupermic.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/local/lib/libxml2.2.dylib (compatibility version 9.0.0, current version 9.31.0)
    /usr/local/lib/libcurl.4.dylib (compatibility version 6.0.0, current version 6.0.0)

    Question : si on arrive à  modifier le premier, comment éditer les autres ?

    J'offre un mars à  celui qui trouvera !

    Michaël.
  • ChachaChacha Membre
    10:58 modifié #9
    dans 1217319659:

    @loader_path/libsupermic.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/local/lib/libxml2.2.dylib (compatibility version 9.0.0, current version 9.31.0)
    /usr/local/lib/libcurl.4.dylib (compatibility version 6.0.0, current version 6.0.0)

    Question : si on arrive à  modifier le premier, comment éditer les autres ?

    J'offre un mars à  celui qui trouvera !

    Michaël.

    Je dirais que le premier est éditable par un install_name_tool -id, alors que les autres sont modifiables par install_name_tool -change.
    Saviez-vous que la NASA a trouvé de l'eau sur Mars en 2005 ? http://apod.nasa.gov/apod/ap050401.html
  • 10:58 modifié #10
    Parfait ! Je te l'envoie pas la poste ?

    J'avais essayé mais comme un âne avec juste le nom de la lib et non le chemin complet !

  • ChachaChacha Membre
    10:58 modifié #11
    dans 1217321106:

    Parfait ! Je te l'envoie pas la poste ?

    Je crois que je vais faire don du mars aux bonnes oe“uvres d'Objective-Cocoa (et en plus je ne mange pas de barres sucrées, pour rester svelte et athlétique).

    >J'avais essayé mais comme un âne avec juste le nom de la lib et non le chemin complet !
    Un mars et ça répare !
  • 10:58 modifié #12
    Ok ;)

    En tous cas impossible de vivre sans install_name_tool maintenant ?
  • AliGatorAliGator Membre, Modérateur
    novembre 2008 modifié #13
    Bonjour,

    Je réouvre ce sujet car je pense avoir un problème similaire.
    J'ai créé un projet que je veux linker avec une dylib (libUniMotion.dylib, compilée par mes soins via un Makefile fourni).

    J'ai donc procédé comme suit :
    • J'ai vérifié le install_name utilisé dans le Makefile, c'est @executable_path/$@ pour la cible "../Frameworks/libUniMotion.dylib"... autrement dit le install_name de ma dylib c'est "@executable_path/../Frameworks/libUniMotion.dylib"
    • Dans Xcode, j'ai rajouté ma dylib dans le dossier (enfin le "Groupe") nommé "Frameworks", à  côté de Cocoa.framework
    • J'ai rajouté une "Copy Files Build Phase" de destination "Frameworks", ainsi la dylib est copiée dans le dossier "Frameworks" dans le Contents du bundle, à  côté du dossier "MacOS" et "Resources"
    • J'ai bien "libUniMotion.dylib" dans la build phase "Link Binary With Libraries", et si je regarde les options de mon Target, j'ai bien la lib aussi dans "Linked Libraries" dans l'onglet Général à  côté de Cocoa.


    Au final, impossible de linker en Release. Pourtant ça compile parfaitement en debug (mais bon c'est normal vu qu'en debug ça link pas, avec zerolink) et l'appli fonctionne bien avec les appels que je fais aux fonctions de la librairies. Mais en release j'ai des erreurs de linkage dans tous les sens... y compris avec des symboles comme NSDate ou "_objc_msgSendSuper".

    Qu'est ce que j'ai loupé ?!

    [EDIT] Ah ben en fait ça n'a sans doute rien à  voir avec la dylib, je crée un projet "Cocoa Application" tout neuf et j'essaye de le compiler en Release j'ai les mêmes soucis ! C'est quoi ce bordel ?!

    [EDIT 2] Bon je crois que j'ai compris, j'avais passé un coup de "Monolingual" dernièrement pour enlever quelques langues inutiles dans les applis sur mon mac, et dans la foulée j'ai fait l'erreur de supprimer les architectures autres que la mienne... Donc entre autres l'architecture PPC !
    C'est au moment du linkage PPC qu'il foire du coup, forcément... j'espère que la réinstall des DevTools suffira à  remettre ça à  plat et que je vais pas devoir réinstaller OSX aussi...

    Désolé pour le dérangement :D
Connectez-vous ou Inscrivez-vous pour répondre.