molovol / MoloVol

MoloVol is a free, cross-plattform, scientific software for volume and surface computations of single molecules and crystallographic unit cells.
https://molovol.com
MIT License
22 stars 4 forks source link

[Solved] Executable requires libtiff.dylib to be installed (v0.1.0.dev) #96

Closed jmaglic closed 3 years ago

jmaglic commented 3 years ago

Problem

For our 0.1.0 release I built a universal binary and bundled it into an appbundle for distribution. I then discovered that the application didn't run on Macs with x86_64 architecture. The error report suggested that the issue was with a dynamic library that was not installed on the machine.

Library not loaded: /usr/local/opt/libtiff/lib/libtiff.5.dylib
  Referenced from: /Users/USER/Downloads/MoloVol.app/Contents/MacOS/MoloVol
  Reason: image not found

Using otool on my executable I was getting this output:

$ otool -L MoloVol.app/Contents/MacOS/MoloVol 
MoloVol.app/Contents/MacOS/MoloVol:
    ...
    /usr/local/opt/libtiff/lib/libtiff.5.dylib (compatibility version 13.0.0, current version 13.0.0)

I discovered that installing libtiff using Homebrew fixed the app crashing. However, it would be unreasonable to expect users to install unrelated libraries for the app to work.

I was confused about why I was encountering the issue in the first place. I had built wxWidgets specifically as static library, so I wasn't expecting dylib dependencies to pop up.

Solution

Eventually I discovered a solution on this web page (I've archived a snap shot of the page with the Wayback Machine if the link ever breaks). I had to change the relevant section a little, but I added the following code to my Makefile:

WXCONFIGLIBS := $(shell wx-config --libs)
WXCONFIGLIBS := $(WXCONFIGLIBS:-ltiff=/usr/local/opt/libtiff//lib/libtiff.a)
LDFLAGS := $(WXCONFIGLIBS)

This section statically links my executable against libtiff. All that was left to do was add $(LDFLAGS) to the linking line in my Makefile in place of `wx-config --libs`. Inspecting the resulting executable with otool confirms that the executable no longer asks for the dylib. Opening the app on the x86_64 machine works as desired.

Alternative Solution

Before I discovered how to statically link libtiff, I constructed a whole working solution by bundling the libtiff with the appbundle. I believe this is even explained in the resource I've linked above.

In essence, the idea is to place all required dylibs inside the bundle directory. Most places I found created a Frameworks directory inside the Contents directory. In my case I needed two dylibs in this directory: libtiff.5.dylib and libjpeg.9.dylib because the latter was a dependency of the former.

As part of the appbundle creation I needed to copy the dylibs into the Frameworks folder, then inform the executable to look for the libtiff library there, instead of the usual place. The relevant make file section looked something like this:

BUNDLE := bin/MoloVol.app
LIBNAME_LIBTIFF := libtiff.5.dylib
LIBDIR_LIBTIFF := /usr/local/opt/libtiff/lib
LIBNAME_JPEG := libjpeg.9.dylib
LIBDIR_JPEG := /usr/local/opt/jpeg/lib
FWDIR := $(BUNDLE)/Contents/Frameworks
FWDIRREL := @executable_path/../Frameworks
...
appbundle:
        ...
        cp $(LIBDIR_LIBTIFF)/$(LIBNAME_LIBTIFF) $(FWDIR)
        cp $(LIBDIR_JPEG)/$(LIBNAME_JPEG) $(FWDIR)
        install_name_tool -change "$(LIBDIR_LIBTIFF)/$(LIBNAME_LIBTIFF)" "$(FWDIRREL)/$(LIBNAME_LIBTIFF)" $(BUNDLE)/Contents/MacOS/MoloVol 
    install_name_tool -change "$(LIBDIR_JPEG)/$(LIBNAME_JPEG)" "$(FWDIRREL)/$(LIBNAME_JPEG)" $(FWDIR)/$(LIBNAME_LIBTIFF)

In order to make the executable look in the right place for the dylib, I had to use install_name_tool with the -change switch as seen above. The first argument is the original dylib path, the second argument is the new path relative to the executable and the last argument is the executable path. Since libtiff also had a dependency (libjpeg), I needed to do this twice, but the second time with libtiff as executable. Using otool -L path/to/executable I could check whether the dylib path was successfully changed.

There are many places that explain how to do this, but I did find parts of this guide to be most useful.

Ultimately, the problem with this approach is that now we're distributing not only our code, but also third-party binaries. This leads to all kinds of licensing requirements that are best left untouched.

Failed Attempts

Before I got to any solution, I tried to create fat/universal binaries of the dylibs so I could include them in the appbundle. However, this ended up not being necessary, since I could just use the binaries already installed on my system. For whatever reason, only the x86_64 build requires the dylib, whereas the arm64 build manages to ignore it when linking. That means no universal libraries are required.

jmaglic commented 2 weeks ago

After my original solution stopped working, I came back to this issue and finally found a proper solution. The solution comes from this 2014 blog post on the wxWidgets website.

This is the relevant quote:

[...] sometimes you may want to avoid using the system libraries, e.g. because you want to build a statically linked version of your program with as few dependencies as possible. This was always possible by explicitly disabling the use of each and every library with -with-libfoo=builtin, but this was relatively tiresome and it was easy to forget a library or two.

To remedy this, I’ve just added a new --disable-sys-libs configure option which does exactly what it says: when it is specified on configure command line, only the built-in versions of the third party libraries will be used.

So the solution is simply to use the option --disable-sys-libs when configuring wxWidgets. No more dependencies on local dylibs.