21

I'm trying to do something pretty simple and typical, which is use dynamically linked libraries in my Xcode project and then deploy with all the necessary libraries embedded.

However I must be doing something the wrong way, because Xcode 8 won't allow me to embed .dylib files, only frameworks! The picture below is what happens when I try to add anything to Embedded Binaires, the dylibs just don't show up, and Add Other... adds them to the project but not to Embedded Binaries.

Xcode not allowing me to add any of the dylibs as embedded binaries

There must be a very simple way to do it but I just can't find it...

Epilogue

So apparently since I do need to run a script that calls install_lib_tool I made a pretty universal script that will change anything that has /local/ in its path to the path of the embedded copy:

#!/bin/sh

app=$BUILT_PRODUCTS_DIR/$EXECUTABLE_PATH
fw_path=$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH
app_dyl_list=(`ls $fw_path | grep dylib`)

function change_paths {
    local bin=$1
    echo change_path $bin
    dyl_list=(`otool -L $bin | grep local | awk '{print $1}'`)

    for dyl in ${dyl_list[*]}; do
        libname=$(basename $dyl)
        libname=${libname%%.*}
        actual_libname=(`ls $fw_path | grep $libname | xargs basename`)
        install_name_tool -change $dyl "@executable_path/../Frameworks/$actual_libname" $bin
        printf "\t%s edited\n" $actual_libname
    done
}

change_paths $app
for dyl_bin in ${app_dyl_list[*]}; do
    change_paths $fw_path/$dyl_bin
done

Then all it takes is adding a Run Script step after the copying of the dylibs to just run it with no arguments (the environment variables contain everything needed).

Michel Rouzic
  • 1,013
  • 1
  • 9
  • 22
  • 1
    I used the above scrip via install_name_tool and everything looks correct! my problem is name with "LC_ID_DYLIB" so " cmd LC_ID_DYLIB name /usr/local/opt/cpprestsdk/lib/libcpprest.2.10.dylib (offset 24)" they won't change after running the script and I can't run the app, so I need to make a symlink on the opt folder and framework dylibs inside of my app! is there any way to figure out? – Mo Farhand Mar 26 '20 at 23:57
  • I also used the above script in Xcode in Build phases as Run Script but no results. @MichelRouzic any update on this? – peco Apr 08 '20 at 08:55

3 Answers3

18

I never used "Embedded Libraries", but instead a copy phase to copy all needed 3rd party libs and those created by the various targets in the project. You can simply drag dylibs you wanna copy from the "Frameworks" and "Products" outline nodes into this copy phase (or add them via the "+" button). XCode will also automatically sign each lib, if that's enabled (which it is by default):

enter image description here

Take also a look at the "Installation Directory" setting. You should set this value to @executable_path/../Frameworks (provided this is your actual framework folder, at least that is the recommended one). The value is used by XCode to set the ID for your dylib and is essential to make them load correctly

enter image description here

You can also use a tool like MacDependency to check the details of your .app, .dylib and .framework packages. It will show you also which other dylibs a library depends on and under which path it expect them. That path must be the same as the ID of the linked library or loading will fail.

Another very useful tool is otool, which comes with XCode. It gives similar infos like MacDependency and more. Here is a list of dependencies for a dylib with paths and versions expected by it:

Mikes-iMac:Debug mike$ otool -L libcdbc.dylib
libcdbc.dylib:
    @executable_path/../Frameworks/libcdbc.dylib (compatibility version 1.0.0, current version 1.0.0)
    @executable_path/../Frameworks/libwbbase.dylib (compatibility version 1.0.0, current version 1.0.0)
    @executable_path/../Frameworks/libgrt.dylib (compatibility version 1.0.0, current version 1.0.0)
    @executable_path/../Frameworks/libgmodule-2.0.0.dylib (compatibility version 3401.0.0, current version 3401.2.0)
    @executable_path/../Frameworks/libgthread-2.0.0.dylib (compatibility version 3401.0.0, current version 3401.2.0)
    @executable_path/../Frameworks/libglib-2.0.0.dylib (compatibility version 3401.0.0, current version 3401.2.0)
    @executable_path/../Frameworks/libmysqlcppconn.7.1.1.8.dylib (compatibility version 7.0.0, current version 7.1.1)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 307.4.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.0.0)

Another discussion about loader paths is here: How to set dyld_library_path in Xcode.

If your app or a library you created expects dylibs from a system location instead of a relative one then it could mean you linked the wrong lib to it. In the "Link Binary" build phase you can select the libraries to link against and the selection dialog lists all your project targets there. Also for the linker list you can simply drag a dylib from the "Products" node in your project outline into it to make the setting. Also for such libs the requirement to set a proper path holds true. You can do that with a script that changes the ID after the copy phase or you keep a copy of these libs somewhere else, change the IDs of them once (e.g. after you downloaded them) and copy them from there to your final app.

Mike Lischke
  • 48,925
  • 16
  • 119
  • 181
  • I tried that, sadly when launching the app it still looks for the dylibs in the original /usr/local/lib/ location, ignores the embedded dylibs and fails to start properly. Do you know how to work around this path issue? – Michel Rouzic Feb 06 '17 at 02:02
  • Updated my answer. Hope that helps. – Mike Lischke Feb 06 '17 at 07:42
  • Thank you, however this still doesn't work, the dylibs get copied properly but the paths stay the same, see this screenshot http://i.imgur.com/nuN3o3w.png – Michel Rouzic Feb 06 '17 at 11:40
  • Oh and by "this" I mean I tried adding the copy phase and changing the paths as shown in the screenshot. – Michel Rouzic Feb 06 '17 at 12:15
  • Ah, that looks like you need to set the IDs of these 3rd party libs (for your own dylibs this is done during compilation, but 3rd party libs are not changed automatically). They are made to install in the system, hence the system path. But when you want to embed them you have to change the IDs of these libs (see the glib example in the otools output above). You can use the `install_name_tool` for that (for instance in a script build phase for your application). – Mike Lischke Feb 06 '17 at 12:46
  • Thanks, making a script did it (see my edited question for a script that should run with any project). Hard to believe this is the way Apple meant for it to work, that's pretty pathetic, but the more I learn the more I know to expect this from them. – Michel Rouzic Feb 06 '17 at 17:03
  • Oh God I need to do the same to the dylibs too, don't I? http://i.imgur.com/GnoAye0.png – Michel Rouzic Feb 06 '17 at 17:39
  • OK I did that (my script now does the same to all the dylibs) and it works! Thank you for the help! And to hell with Apple, I shouldn't have to do all this. – Michel Rouzic Feb 06 '17 at 18:23
  • Well, usually you would compile the 3rd party libs yourself and set the IDs there. What I do is to set them when I don't build myself but download prebuilt binaries. This is a simple job and after changing the IDs I never need to think about again. Also, your case is a bit special as you want to embed libraries which are supposed to be installed in the system. All that together makes your setup a bit more complicated than usual. But I'm glad you solved it now. – Mike Lischke Feb 07 '17 at 07:41
  • I just realised that my script ruins the signing, so when you download the program and launch it it tells you it's "damaged". – Michel Rouzic Feb 08 '17 at 11:03
  • Didn't think about this, right. So, best is to set the correct IDs once you download the 3rd party libs and then just let XCode sign them on build. There's no need anyway to constantly set the ID, if you do that once in the source dylibs. – Mike Lischke Feb 08 '17 at 17:07
  • I used the above scrip via install_name_tool and everything looks correct! my problem is name with "LC_ID_DYLIB" so " cmd LC_ID_DYLIB name /usr/local/opt/cpprestsdk/lib/libcpprest.2.10.dylib (offset 24)" they won't change after running the script and I can't run the app, so I need to make a symlink on the opt folder and framework dylibs inside of my app! is there any way to figure out? – Mo Farhand Mar 26 '20 at 23:57
1

Embedding dylibs now works (checked with Xcode Version 10.2). Targets -> Frameworks, Libraries and Embedded content.

see https://help.apple.com/xcode/mac/11.0/index.html?localePath=en.lproj#/dev51a648b07

Waterman
  • 131
  • 3
  • 11
  • Do you mean "Embed Libraries"? – Raptor Jan 23 '20 at 07:47
  • Very good news, because the approach using a build phase has a big problem: code signing happens after all phases ran. So, before that new setting was introduced it could happen that you copied unsigned versions of your libs to the app bundle, which failed the application build, because the app could not be signed. – Mike Lischke Apr 08 '20 at 14:24
1

All of the above solutions are not working for later version of Xcode and iOS. This is updated with Xcode 13 and tested with iOS 14.

Here is a step by step guide:

  1. When the dylib is created, and make sure the "Install Directory" path for dylib's Build Setting is set as:

    @executable_path/Frameworks

  2. Copy your dylib to a subdirectory such as: ./libs/ under your project directory.

  3. In your project's Xcode Build Settings tab, Search Path ->Library Search Path has a path to the subdirectory in above 2).

  4. Select Build Phase, add your dylib to Copy Bundle Resource as well as Embedded Frameworks as shown bellow (see entries for libgrt.dylib as an example) :
    [Xcode Project Build Phase1

Make sure the path is Frameworks and leave subpath field empty.

Now build and test your app!

us_david
  • 4,431
  • 35
  • 29