how to make Xcode project with homebrew libraries portable? - xcode

I have installed FreeType on my mac using Brew. The code on my mac works fine, but when I try to run the project on other mac I get the below mentioned linking error.
dyld: Library not loaded: /usr/local/opt/freetype/lib/libfreetype.6.dylib
Referenced from: /Users/ashutosh/Library/Developer/Xcode/DerivedData/InstrumentChamp-
etytleaabhxmokadgrjzbgtmwxfl/Build/Products/Debug/instrumentchamp.app/Contents/MacOS/instrumentchamp
Reason: image not found (lldb)
All the library directories and include directories of Freetype are included in project's '$SRCROOT/' directory when I try to run the code on other mac.
The path you see in the linking error for library is where brew had installed freetype in the mac where I created this project.
/usr/local/opt/freetype/lib/libfreetype.6.dylib
I have copied all the lib/ include/ directories that were needed to my project's home folder.
And I have set the library and include paths in Xcode.
What is it that I am missing here? What else do I have to do to make my code portable on any other Mac.
I got the project to run on other mac by installing Brew, but I want to do it without the need to install brew.
PS: I had to install freetype using brew, as I couldn't compile the .dylib for freetype for 32bit processor, a 64bit copy of .dylib was giving me error such as 'wrong architecture!'

The basic idea I was getting at in my comments is that OS X is pretty stupid about where it searches for libraries, it will use the same absolute path used during compilation to resolve them at run-time.
Usually when you want to deploy/distribute your application to a different machine from the one that built it, you will include your libraries with the install package/bundle. But you probably want them to use a path relative to your application at run-time, thus install_name_tool -change allows you to replace the nasty absolute path with something relative.
Hope this makes sense, Apple makes it really easy to use system-wide frameworks on OS X, custom libraries not so much. If you compile using a system-wide framework, /System/Library/Frameworks/... is universally available on all OS X installs (given the same target release version).
To solve your problem, I would do the following:
install_name_tool -change /usr/local/opt/freetype/lib/libfreetype.6.dylib #executable_path/lib/libfreetype.6.dylib <executable_name_here>
Then it will stop looking for libfreetype.6.dylib in the location it was when you compiled the software, and instead search for it relative to your executable's location at run-time (in this case, in the sub-directory lib/).

In the end, this did not end up as easy as I originally thought. It works but there are caveats.
Building on the answers from Andon M. Coleman & Alexander Klimetschek, here is a script function which takes dylib names from Homebrew, copies them into the app bundle's Contents/Frameworks directory, and sets the executable's corresponding search path using install_name_tool. I noticed that sometimes the actual .dylib location is different from what the Xcode linker finds when putting together the executable and this handles that as well by querying the exe with otool -L.
The script below copies the .dylib for the liblo library into the app bundle. Adding new libs should be as simple as adding a new copy lib ###.dlyib line to the bottom of the script.
# exit on error
set -e
# paths
LOCALLIBS_PATH="$SRCROOT/libs"
HOMEBREW_PATH="/usr/local"
FRAMEWORKS_PATH="$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH"
EXE_PATH="$BUILT_PRODUCTS_DIR/$EXECUTABLE_FOLDER_PATH/$EXECUTABLE_NAME"
# code signing identity
IDENTITY="$EXPANDED_CODE_SIGN_IDENTITY_NAME"
if [ "$IDENTITY" == "" ] ; then
IDENTITY="$CODE_SIGN_IDENTITY"
fi
# copy lib into Contents/Frameworks and set lib search path
# $1: library .dylib
# $2: optional path to libs folder, otherwise use Homebrew
copylib() {
LIB=$1
if [ "$2" == "" ] ; then
# use homebrew
LIB_PATH=$(find "$HOMEBREW_PATH" -type f -name $LIB)
else
# use given path
LIB_PATH="$2/$LIB"
fi
echo "$LIB:"
echo " $LIB_PATH -> $FRAMEWORKS_FOLDER_PATH/$LIB"
# copy lib, set permissions, and sign
mkdir -p "$FRAMEWORKS_PATH"
cp "$LIB_PATH" "$FRAMEWORKS_PATH/"
chmod 755 "$FRAMEWORKS_PATH/$LIB"
codesign --verify --force --sign "$IDENTITY" "$FRAMEWORKS_PATH/$LIB"
# set new path within executable
# note: grep --max 1 as multi-arch builds will print path once *per arch*
OLD=$(otool -L "$EXE_PATH" | grep --max 1 $LIB | awk '{print $1}')
NEW="#executable_path/../Frameworks/$LIB"
install_name_tool -change "$OLD" "$NEW" "$EXE_PATH"
# print to confirm new path
otool -L "$EXE_PATH" | grep $LIB
}
##### GO
# use lib from homebrew
copy lib liblo.7.dylib
# or use local lib
#copylib liblo.7.dylib "$LOCALLIBS_PATH/liblo/lib"
Add this as a Run Script build phase in the Xcode project. You can also save it as an external file and call it using sh within the Run Script phase with:
sh $PROJECT_DIR/path-to/copy-libs.sh
Example build report output:
liblo.7.dylib:
/usr/local/Cellar/liblo/0.29/lib/liblo.7.dylib ->> YOURAPP.app/Contents/Frameworks/liblo.7.dylib
#executable_path/../Frameworks/liblo.7.dylib (compatibility version 11.0.0, current version 11.0.0)
The $(find $HOMEBREW_PATH -type f -name $LIB) could probably be improved by following the .dylib symlink in /usr/local/lib instead.
UPDATE: You will probably also need to make sure the new dylibs are signed otherwise the code signing step will fail when building, at least it eventually did for me. Following this SO post, adding --deep to the Other Code Signing Flags option in the project Build Settings works.
There might be a better way to sign only the files that the script adds, but I didn't feel like digging into running codesign manually. From what I've read, however, --deep is not suggested by Apple for anything but temporary fixes, so it's probably not a best practice.
UPDATE 2: Running --deep didn't actually solve the problem and the app still would not run on other machines. In the end, I needed to code sign the copied .dylib manually which turned out to be relatively easy.
One note: I ran into an ambiguous "Mac Developer" identities error when running codesign which seemed to be confused by two certificates with the same name in the Keychain: ie. the current developer certificate and the previous expired developer certificate. Opening Keychain Access and deleting the expired certificate solved the problem.
UPDATE 3: Quoted path variable usage to fix paths with spaces.
UPDATE 4: This solution works fine for building apps for recent systems, but I ran into a code signing problem when running the app on macOS 10.10. Judging from this SO post, older macOS versions use a different codesign hash algorithm and the app would run fine on a 10.12 system but fail with a code signing error on 10.10. On both systems, codesign verified that everything was signed correctly.
You basically need to build the libraries with the -mmacosx-version-min set so codesign will be able to tell which algorithms to use to support the min and the recent macOS versions. I tried to pass custom CFLAGS/LDFLAGS to Homebrew while building liblo from source, but it's not possible without editing the brew formula. In the end, I decided to write a build script to build liblo from source myself and I modified the copy script so it can also take a local location. Now everything is finally working.
TLDR: Homebrew libs work great for initial development & testing, but building the libs manually works better for deployment.

Here is an automated script based on Andon's answer that will replace all #executable_path/../Frameworks/ with #executable_path/, so that all frameworks can be in the same folder as the executable. This also adapts the frameworks themselves, if they have dependencies on each other.
Beware, this only makes sense for command-line applications. If it's a regular OSX bundles app, one probably shouldn't change the "../Frameworks" convention.
Instructions
Add this as "Run Script" using Shell = /bin/sh at the end of your Xcode project Build Phases.
Also make sure to have the "Copy Files" step with a "Destination = Products Directory" for all the frameworks.
Script
# change the hardcoded ../Frameworks relative path that Xcode does by rewriting the binaries after the build
# check with:
# otool -L <binary>
# this is the new location
# make sure this is the same Destination the Frameworks are copied to in the "Copy Files" step
# the default "#executable_path/" would be Destination = Products Directory
NEW_FRAMEWORKS_PATH="#executable_path/"
# the one we want to replace
DEFAULT_FRAMEWORKS_PATH="#executable_path/../Frameworks/"
function change_binary {
local libpaths=`otool -L "$1" | grep "$DEFAULT_FRAMEWORKS_PATH" | tr '\t' ' ' | cut -d " " -f2`
local lib
for lib in $libpaths; do
if [ "$2" == "recursive" ]; then
local libbinary=`echo $lib | sed "s,$DEFAULT_FRAMEWORKS_PATH,,"`
change_binary "$libbinary"
fi
local newlib=`echo $lib | sed "s,$DEFAULT_FRAMEWORKS_PATH,$NEW_FRAMEWORKS_PATH,"`;
echo "changing library path in '$1': '$lib' => '$newlib'"
install_name_tool -change "$lib" "$newlib" "$1"
done
}
cd $BUILT_PRODUCTS_DIR
change_binary "$EXECUTABLE_NAME" recursive
Note that changing NEW_FRAMEWORKS_PATH to e.g. a relative subpath does not work, the script would need to become a bit more complex to handle this.
Result Structure
$BUILT_PRODUCTS_DIR/
executable
Some.framework/
Another.framework/
...
And otool -L executable would look like this:
executable:
#executable_path/Some.framework/Versions/A/Some (...)
#executable_path/Another.framework/Versions/A/Another (...)
...

Related

Why can this dynamic library file include all dependencies? [duplicate]

Program is part of the Xenomai test suite, cross-compiled from Linux PC into Linux+Xenomai ARM toolchain.
# echo $LD_LIBRARY_PATH
/lib
# ls /lib
ld-2.3.3.so libdl-2.3.3.so libpthread-0.10.so
ld-linux.so.2 libdl.so.2 libpthread.so.0
libc-2.3.3.so libgcc_s.so libpthread_rt.so
libc.so.6 libgcc_s.so.1 libstdc++.so.6
libcrypt-2.3.3.so libm-2.3.3.so libstdc++.so.6.0.9
libcrypt.so.1 libm.so.6
# ./clocktest
./clocktest: error while loading shared libraries: libpthread_rt.so.1: cannot open shared object file: No such file or directory
Is the .1 at the end part of the filename? What does that mean anyway?
Your library is a dynamic library.
You need to tell the operating system where it can locate it at runtime.
To do so,
we will need to do those easy steps:
Find where the library is placed if you don't know it.
sudo find / -name the_name_of_the_file.so
Check for the existence of the dynamic library path environment variable(LD_LIBRARY_PATH)
echo $LD_LIBRARY_PATH
If there is nothing to be displayed, add a default path value (or not if you wish to)
LD_LIBRARY_PATH=/usr/local/lib
We add the desired path, export it and try the application.
Note that the path should be the directory where the path.so.something is. So if path.so.something is in /my_library/path.so.something, it should be:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/my_library/
export LD_LIBRARY_PATH
./my_app
Reference to source
Here are a few solutions you can try:
ldconfig
As AbiusX pointed out: If you have just now installed the library, you may simply need to run ldconfig.
sudo ldconfig
ldconfig creates the necessary links and cache to the most recent
shared libraries found in the directories specified on the command
line, in the file /etc/ld.so.conf, and in the trusted directories
(/lib and /usr/lib).
Usually your package manager will take care of this when you install a new library, but not always, and it won't hurt to run ldconfig even if that is not your issue.
Dev package or wrong version
If that doesn't work, I would also check out Paul's suggestion and look for a "-dev" version of the library. Many libraries are split into dev and non-dev packages. You can use this command to look for it:
apt-cache search <libraryname>
This can also help if you simply have the wrong version of the library installed. Some libraries are published in different versions simultaneously, for example, Python.
Library location
If you are sure that the right package is installed, and ldconfig didn't find it, it may just be in a nonstandard directory. By default, ldconfig looks in /lib, /usr/lib, and directories listed in /etc/ld.so.conf and $LD_LIBRARY_PATH. If your library is somewhere else, you can either add the directory on its own line in /etc/ld.so.conf, append the library's path to $LD_LIBRARY_PATH, or move the library into /usr/lib. Then run ldconfig.
To find out where the library is, try this:
sudo find / -iname *libraryname*.so*
(Replace libraryname with the name of your library)
If you go the $LD_LIBRARY_PATH route, you'll want to put that into your ~/.bashrc file so it will run every time you log in:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/library
Update
While what I write below is true as a general answer about shared libraries, I think the most frequent cause of these sorts of message is because you've installed a package, but not installed the -dev version of that package.
Well, it's not lying - there is no libpthread_rt.so.1 in that listing. You probably need to re-configure and re-build it so that it depends on the library you have, or install whatever provides libpthread_rt.so.1.
Generally, the numbers after the .so are version numbers, and you'll often find that they are symlinks to each other, so if you have version 1.1 of libfoo.so, you'll have a real file libfoo.so.1.0, and symlinks foo.so and foo.so.1 pointing to the libfoo.so.1.0. And if you install version 1.1 without removing the other one, you'll have a libfoo.so.1.1, and libfoo.so.1 and libfoo.so will now point to the new one, but any code that requires that exact version can use the libfoo.so.1.0 file. Code that just relies on the version 1 API, but doesn't care if it's 1.0 or 1.1 will specify libfoo.so.1. As orip pointed out in the comments, this is explained well at here.
In your case, you might get away with symlinking libpthread_rt.so.1 to libpthread_rt.so. No guarantees that it won't break your code and eat your TV dinners, though.
You need to ensure that you specify the library path during
linking when you compile your .c file:
gcc -I/usr/local/include xxx.c -o xxx -L/usr/local/lib -Wl,-R/usr/local/lib
The -Wl,-R part tells the resulting binary to also look for the library
in /usr/local/lib at runtime before trying to use the one in /usr/lib/.
Try adding LD_LIBRARY_PATH, which indicates search paths, to your ~/.bashrc file
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path_to_your_library
It works!
The linux.org reference page explains the mechanics, but doesn't explain any of the motivation behind it :-(
For that, see Sun Linker and Libraries Guide
In addition, note that "external versioning" is largely obsolete on Linux, because symbol versioning (a GNU extension) allows you to have multiple incompatible versions of the same function to be present in a single library. This extension allowed glibc to have the same external version: libc.so.6 for the last 10 years.
cd /home/<user_name>/
sudo vi .bash_profile
add these lines at the end
LD_LIBRARY_PATH=/usr/local/lib:<any other paths you want>
export LD_LIBRARY_PATH
Another possible solution depending on your situation.
If you know that libpthread_rt.so.1 is the same as libpthread_rt.so then you can create a symlink by:
ln -s /lib/libpthread_rt.so /lib/libpthread_rt.so.1
Then ls -l /lib should now show the symlink and what it points to.
I had a similar error and it didn't fix with giving LD_LIBRARY_PATH in ~/.bashrc .
What solved my issue is by adding .conf file and loading it.
Go to terminal an be in su.
gedit /etc/ld.so.conf.d/myapp.conf
Add your library path in this file and save.(eg: /usr/local/lib).
You must run the following command to activate path:
ldconfig
Verify Your New Library Path:
ldconfig -v | less
If this shows your library files, then you are good to go.
running:
sudo ldconfig
was enough to fix my issue.
I had this error when running my application with Eclipse CDT on Linux x86.
To fix this:
In Eclipse:
Run as -> Run configurations -> Environment
Set the path
LD_LIBRARY_PATH=/my_lib_directory_path
Wanted to add, if your libraries are in a non standard path, run ldconfig followed by the path.
For instance I had to run:
sudo ldconfig /opt/intel/oneapi/mkl/2021.2.0/lib/intel64
to make R compile against Intel MKL
All I had to do was run:
sudo apt-get install libfontconfig1
I was in the folder located at /usr/lib/x86_64-linux-gnu and it worked perfectly.
Try to install lib32z1:
sudo apt-get install lib32z1
If you are running your application on Microsoft Windows, the path to dynamic libraries (.dll) need to be defined in the PATH environment variable.
If you are running your application on UNIX, the path to your dynamic libraries (.so) need to be defined in the LD_LIBRARY_PATH environment variable.
The error occurs as the system cannot refer to the library file mentioned. Take the following steps:
Running locate libpthread_rt.so.1 will list the path of all the files with that name. Let's suppose a path is /home/user/loc.
Copy the path and run cd home/USERNAME. Replace USERNAME with the name of the current active user with which you want to run the file.
Run vi .bash_profile and at the end of the LD_LIBRARY_PATH parameter, just before ., add the line /lib://home/usr/loc:.. Save the file.
Close terminal and restart the application. It should run.
I got this error and I think its the same reason of yours
error while loading shared libraries: libnw.so: cannot open shared
object file: No such file or directory
Try this. Fix permissions on files:
cd /opt/Popcorn (or wherever it is)
chmod -R 555 * (755 if not ok)
I use Ubuntu 18.04
Installing the corresponding -dev package worked for me,
sudo apt install libgconf2-dev
Before installing the above package, I was getting the below error:
turtl: error while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory
I got this error and I think its the same reason of yours
error while loading shared libraries: libnw.so: cannot open shared object
file: No such file or directory
Try this. Fix permissions on files:
sudo su
cd /opt/Popcorn (or wherever it is)
chmod -R 555 * (755 if not ok)
chown -R root:root *
A similar problem can be found here.
I've tried the mentioned solution and it actually works.
The solutions in the previous questions may work. But the following is an easy way to fix it.
It works by reinstalling the package libwbclient
in fedora:
dnf reinstall libwbclient
You can read about libraries here:
https://domiyanyue.medium.com/c-development-tutorial-4-static-and-dynamic-libraries-7b537656163e

Trouble installing OpenCV with Cmake

I am trying to install the openCV library for Python however I am new to CMake and have run into some trouble after having cloned the repository in ~/opencv.
I've made a build directory in it with the mkdir command however once inside it when trying to set CMake options in it.
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local
I get prompted with the following error:
CMake Error: The source directory "/Users/eDen/opencv/build/CMAKE_INSTALL_PREFIX=/usr/local" does not exist.
It seems you aren't making the right directory, some Mac OS X installations doesn't include /usr/local/. You can make the directory using, if it's not already created, with:
sudo mkdir /usr/local/
But you say you want to use OpenCV with Python. I recommend you to obtain an already compiled copy unless you need some advanced features not available in the compiled version, like Qt integration or CUDA programming. But these features are included in the arguments of the cmake command.
Instructions on how to obtain OpenCV from Homebrew repository, this page explains the process. Basically, you install Homebrew, then Python, configure it and install some dependencies.
As Tsyvarev mentioned in the comments, you need to specify the path to source directory (i.e. where the main CMakeLists.txt file exists) at the end of your command. So, supposing you are now in the build directory, the final cmake command would be as follows:
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ..
I have the last argument as .. but still get the error.
In my case, there is a bad whitespace in the above arguments. So the last .. is ignored.

How to direct build output for OpenSSL on OS X

I am building OpenSSL on OS X. I would like to direct the built libraries (libcrypto.a, libssl.a etc) to a directory of my choice. Currently they are put in the current working directory.
How do I do achieve that?
This is what I'm doing. I thought specifying the directory for the openssldiroption might do it but it doesn't.
./Configure darwin64-x86_64-cc --openssldir=my-dir
make
I would like to direct the built libraries (libcrypto.a, libssl.a etc) to a directory of my choice. Currently they are put in the current working directory.
OpenSSL 1.1.0 can build in-tree or out-of-tree. OpenSSL 1.0.2 builds in-tree. In both cases all the artifacts are in the OpenSSL root directory. Its effectively hard coded into the scripts (there's two or three of them that interact).
You put them in the directory of your choice by following the build with a make install. If you only care about the libraries (libcrypto.a, libssl.a) then manually cp them.
If needed you can locate the artifacts with:
find . -name 'libssl*'
find . -name 'libcrypto*'
find . -name '*\.o'
There's also some rules about when to use --prefix=XXX and when to use --openssldir=XXX. It depends on both the OpenSSL version number, and the make recipe like make install or make install_sw. For the details, see Compilation and Installation on the OpenSSL wiki.
If you need to re-direct OpenSSL output to your specific directory, then you need to execute configure script like this:
$ ./configure darwin64-x86_64-cc --prefix=/Users/username/mydir --openssldir=/Users/username/mydir/ssl
where "username" is your user name. In this example the output will be performed into "~/mydir" directory. Here:
--prefix option sets the output directory
--openssldir option sets the ssl settings directory

Cant get gtest working with Xcode 6.2

I'm trying to follow the instructions from Here.
The instructions seem very straight forward, but I can't get it to compile. If I download the gtest file from the website, it appears there are header files missing from the include directory which means they don't get into the gtest-framework.
the files are the gtest-port-arch.h and everything in the internal/custom folder. I get complier errors that these files are not found.
If I get the source from the svn, all of the files are present in the include dir of the sdk, however, the source folder in the xcode project omits the same files from being added to the project. I have to add them myself then build the frame work. The missing files are then present, but when I build the gtest-framework and add it to the test project, then the Test target's main class does not recognize the TEST calls and I get errors like C++ requires all types to be defined. I am following the steps. I really need to get this framework functioning.
Any help appreciated.
I had the same issue and couldn't find out the problem.
Ended up installing the libraries and simply linking them to Xcode, as described in the answer here
I copy the instructions here too. The only difference is that I used /usr/local/include and /usr/local/lib
Moreover, do not forget to set these for your "Tests" target:
Library search paths: /usr/local/lib
User header search paths: /usr/local/include
other linker flags: -lgtest
Copied:
1. Get the googletest framework
$ wget http://googletest.googlecode.com/files/gtest-1.7.0.zip
2. Unzip and build google test
$ unzip gtest-1.7.0.zip
$ cd gtest-1.7.0
$ ./configure
$ make
3. "Install" the headers and libs on your system.
$ sudo cp -a include/gtest /usr/include
$ sudo cp -a lib/.libs/* /usr/lib/

boost installation won't install without stating at least one lib?

I'd like to install just the mpl and preprocessor portions of the library but if I use this command, it tries to build and install all of them:
./bootstrap.sh --with-libraries= --prefix=<my lib path>
I see that it is trying to because it is executing the compiler. Using ./b2 -n also shows the commands being executed.
Does anyone know what is happening? The default of --with-libraries= is supposed to be all according to the help:
--with-libraries=list build only a particular set of libraries,
describing using either a comma-separated list of
library names or "all"
[all]
--without-libraries=list build all libraries except the ones listed []
Empty shouldn't default to all, Empty should mean empty. Not specifying --with-libraries= should default to all.
Also, --without-libraries=all doesn't work either. It's a bit disappointing considering that this library has been around so long. You'd think that these people would check a simple boundary case like that. :(
There doesn't appear to be any way except to view all libraries and then specify them all after the --without-libraries= flag. However, it looks like it actually doesn't do anything, which means, I can just copy the header folder over to the include directory that I desire.
In my experience when upgrading boost, there are some tests run to see if boost is compatible with your system/compiler. In [our] case we needed filesystem and system, so it was the method we used in case of boost 1_51_0. I hope it will help.
Note: We needed static libraries only (.a) that could be compiled with shared libraries (.so). That is why we add cxxflags=-fPIC, and then we copy .a files into lib64 directory.
tar xf boost_1_51_0.tar.gz
rm boost_1_51_0.tar.gz
./bootstrap.sh --with-libraries=filesystem,system --exec-prefix=$(pwd)
./b2 cxxflags=-fPIC
mkdir lib64
cp $(find . -name '*.a' -print | grep -v stage | grep release ) lib64
cd lib64
# Now you can delete any libraries you don't need.
Note also that you can call ./bootstrap.sh --show-libraries to see all available libraries.

Resources