CMake: Shared Libs und undefined references



  • Hi, ich habe mehrere Libs die auch teilweise (zirkulare) Abhängigkeiten untereinander haben. Mit Visual Studio hatte ich damit nie Probleme aber ich möchte das ganze nun auch mit CMake kompilieren können.

    Und da bin ich nach tagelanger Recherche im Internet nun völlig am Ende.
    Nichts funktioniert.

    Die Abhängigkeiten sehen in etwa so aus:

    Lib:  Dependencies:
    LibA  -
    LibB  LibA + LibD
    LibC  LibA + LibB + LibD
    LibD  LibA + LibB + LibC
    

    Hier mal ein abgespecktes CMakeLists.txt File um LibB zu kompilieren.
    Diese ist von LibA und LibC abhängig.

    # ---- INTERFACE ----
    
    #add_library(LibA INTERFACE)
    #set_target_properties(LibA PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${...})
    #target_include_directories(LibA INTERFACE ${...})
    
    #add_library(LibD INTERFACE)
    #set_target_properties(LibD PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${...})
    #target_include_directories(LibD INTERFACE ${...})
    
    # ---- SHARED ----
    
    add_library(LibA SHARED IMPORTED GLOBAL)
    set_target_properties(LibA PROPERTIES IMPORTED_LOCATION "...LibA.so")
    
    add_library(LibD SHARED IMPORTED GLOBAL)
    set_target_properties(LibD PROPERTIES IMPORTED_LOCATION "...LibD.so")
    
    # ---- BUILD LIB B ----
    
    add_library(objlib OBJECT ${LIBB_SOURCES} ${LIBB_HEADERS})
    
    add_library(LibB SHARED $<TARGET_OBJECTS:objlib>)
    
    target_link_libraries(LibB PRIVATE LibA LibD)
    

    Problem: LibA zu linken funktioniert so zwar, da diese keine anderen Abhängigkeiten hat. Aber LibB ist zusätzlich noch von LibD abhängig.
    LibD aber wiederum von LibA und der gerade zu kompilierenden LibB.
    Sprich LibB bräuchte eine Lib die es noch gar nicht gibt zu diesem Zeitpunkt.
    Der Linkvorgang bricht dann mit der Meldung, ab das keine Regel vorhanden wäre das Ziel LibD zu erstellen.
    Das soll dieses besch.... CMake ja auch gar nicht. Es soll einfach nur die die Symbole zu dieser Lib importieren. Kann das so schwer sein?

    Deswegen war ich mir nicht sicher, ob SHARED IMPORTED überhaupt der richtige Modus ist. Evtl. wäre ein INTERFACE oder eine HEADER_ONLY_LIB der richtige Weg? Oder dann gibt es noch eine export/import Syntax wo ich überhaupt nicht blicke wozu das gut sein soll.

    Bitte kann mir jemand sagen wie ich ein solches Scenario mit CMake abbilde? Oder geht das am Ende mit CMake gar nicht?
    Ich bin mittlerweile völlig ratlos. Noch nie habe ich ein Werkzeug mit einer schlechteren Doku als CMake gesehen.
    Völlig skurrile Bezeichner/Syntax noch oben drauf. Das Internet ist voll mit völlig veralteten und nicht mehr funktionierenden Beispielen, weil scheinbar alle drei Tage die Syntax über den Haufen geworfen wird... Bin langsam mehr als nur angefressen :).



  • Nur so ne Idee: Teile CMake mal exakt alle Abhängigkeiten mit. Auch für die importierten Bibliotheken, z.B. mit dem Target Property IMPORTED_LINK_DEPENDENT_LIBRARIES.

    Bei solchen Konstellationen ist nämlich die Link-Reihenfolge der Bibliotheken im Kommandozeilen-Aufruf des Linkers extrem wichtig (zuerst Objekte/Biblioteken, welche externe Symbole verwenden, und dann die Objekte/Bibliotheken, welche diese zur Verfügung stellen. Bei solchen zirkulären Abhängigkeiten muss evtl. auch eine Bibliothek in der Kommandozeile mehrmals angegeben werden - hatte das selbst noch nicht so häufig, das sollte aber theoretisch funktionieren). Von CMake würde ich eigentlich erwarten, dass es in der Lage ist, so etwas korrekt aufzulösen, wenn es alle Abhängigkeiten kennt.

    Soweit ich das sehe, weiss CMake hier bei deinem Beispiel nichts von den Abhängigkeiten, die z.B. LibD hat. Es dürfte daher schwierig für CMake sein, dafür einen korrekten Linker-Aufruf zu generieren.



  • Danke für den Tipp. Aber da passiert gar nichts. Ich kann nicht mal sagen wie die Syntax zu diesem IMPORTED_LINK_DEPENDENT_LIBRARIES Property aussehen muss, da man online nicht ein einziges Beispiel findet. Habe ich schonmal erwähnt dass ich CMake hasse...

    Es wird nicht mal ein "NEEDED" Eintrag in der .so Lib angelegt.
    Das einzige was überhaupt mal funktioniert hat ist der Aufruf

    target_link_libraries(LibB PUBLIC LibA LibD)
    

    Aber dazu braucht es wie gesagt die fertige Lib für.

    Vielleicht habe ich auch einfach eine falsche Vorstellung.
    Darf ich evtl. gar nicht erst mit "-Wl,--no-undefined" linken bei Libs mit Abhängigkeiten?

    Fangen wir doch mal bei Adam und Eva an.
    Unter Windows schreibt man sich sein Import/Export Macro mit entweder dllimport/dllexport. Damit ist vollkommen klar, ob die Klasse importiert oder exportiert werden soll. Kompiliere ich z.B. LibB setze ich das entsprechende Preprozessor Export Define und die Symbole der LibB werden exportiert. Die Symbole der LibA und LibD aber importiert.

    Unter diesem Linux Frickel Misst (selber jahrelanger Linux User :D) gibt man sowohl für den Import als auch Export

    __attribute__((visibility ("default")))
    

    an. Woher zum Teufel soll denn der Kompiler nun wissen, dass er wenn ich LibB kompiliere er die Symbole von LibA importieren, aber die von LibB exportieren soll? Die Klassen beider Libs haben dann doch alle eine Default-Visibility wenn ich sie nicht gerade als hidden definiert habe. Kann mir das mal jemand erklären?



  • @Enumerator sagte in CMake: Shared Libs und undefined references:

    -Wl,--no-undefined

    Du hast zirkuläre Abhängigkeiten und baust mit -Wl,--no-undefined? Echt jetzt?

    Davon abgesehen: Wieso hast du überhaupt zirkuläre Abhängigkeiten? Mach die wech. Bzw. wenns denn unbedinge sein muss, dann link wenigstens statisch.



  • Naja, in meiner scheinbar naiven Vorstellung sollte das kein Problem sein wenn ich die entsprechenden Abhängigkeiten mitlinke. Dazu sind doch diese Import/Export Macros da oder nicht? Kompiliere ich LibD werden deren Klassen exportiert und sind im Code von LibD Abhängigkeiten zu Klassen aus z.B. LibA dann werden deren Klassen/Symbole importiert? Insofern würde ich auch gerne nochmal zu dem Punkt mit der Visibility unter Linux zurückkommen. Woher weis der Kompiler, ob er importieren oder exportieren muss, wenn doch beide Defines die gleiche Visibility "default" haben? Ich habe da echt noch einen Knoten im Kopf.



  • @Enumerator
    Wenn du libfoo.so mit -Wl,--no-undefined baust dann geht der Linker her, und guckt ob alle Importe die libfoo.so braucht auch verfügbar sind. Nehmen wir an libfoo.so hat jetzt Importe in die libbar.so definiert sind. Also muss libbar.so verhanden sein und diese Symbole exportieren. Also musst du libbar.so vorher bauen. Jetzt hat aber libbar.so Importe die in libfoo.so definiert sind. Und wenn du beide mit -Wl,--no-undefined baust, dann heisst das du brauchst an der Stelle erstmal die libfoo.so. Und so beisst sich der Schwanz in den Hund.

    Insofern würde ich auch gerne nochmal zu dem Punkt mit der Visibility unter Linux zurückkommen. Woher weis der Kompiler, ob er importieren oder exportieren muss, wenn doch beide Defines die gleiche Visibility "default" haben?

    Der Compiler weiss das gar nicht, aber der Linker. Wenn der Linker sieht dass ein Symbol gleichzeitig als "visibility default" markiert ist, und es dann beim Linken auch in einem Object File oder Archive (static lib) findet, dann exportiert er es halt einfach. Und ansonsten bleibt es "undefined", wird also zu einem Import.



  • Danke für die Erklärung. Dann linke ich besser wieder ohne "no-undefined" :). Aber wie bekomme ich es nun hin, dass mir CMake in der Lib einen "NEEDED" Eintrag für die anderen Libs, von der die aktuelle Lib abhängt, anlegt? Oder braucht es das auch nicht?



  • Keine Ahnung erhlich gesagt. Denn: zirkuläre Abhängigkeiten sind Schmarrn. Das lässt man am besten einfach. Und genau das wäre auch was ich dir empfehlen würde.


Log in to reply