Making Tesseract Portable in MacOS with Runtime Linking
Tags: unix, bash
Published: 2021-04-07 16:38:34

## Background
I like the plug and play experience. Recently, I developed a [desktop app for people to learn Japanese](https://dev.to/mathewthe2/i-launched-my-side-project-on-reddit-now-what-36h7), but it required MacOS users to first install brew and tesseract. It may not seem much effort to developers, but these extra steps could detract the user experience and make it less accessible.

On Windows, it is easy to compile the entire Tesseract into one exe file and all I did was copy a compiled Tesseract.exe from another github repo (Probably not the safest idea).

On Mac, I have tried rebuilding all the separate dependencies and Tesseract but to no avail. I was using Python so I couldn't simply import Tesseract dependencies to Python like you would in Swift or Objective C, and the tesserocr interface required interacting with Cython and compiling paths at build time...which would probably not work with relative paths.

## otool & install_name_tool

Finally I stumbled upon **install_name_tool**. Apparently in Unix you could change the dynamic shared libraries at run time and I only needed to use the command *install_name_tool* to point the dynamic libraries to a relative path after copying the files I needed to run Tesseract.

``` bash
$ otool -L /usr/local/Cellar/tesseract/4.1.1/bin/tesseract
/usr/local/Cellar/tesseract/4.1.1/bin/tesseract:
	/usr/local/Cellar/tesseract/4.1.1/lib/libtesseract.4.dylib (compatibility version 5.0.0, current version 5.1.0)
	/usr/local/opt/leptonica/lib/liblept.5.dylib (compatibility version 6.0.0, current version 6.3.0)
	/usr/lib/libcurl.4.dylib (compatibility version 7.0.0, current version 9.0.0)
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 800.7.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.0.0)
```

I was not even sure which dylib needed to be changed. That was why I first copied the Tesseract folder and its dependency folders from the installed *Cellar* folder to a new folder on my desktop.

``` bash
tesseract
leptonica
giflib
jpeg
libpng
libtiff
openjpeg
webp
```

Then I ran the cloned tesseract, read the error log, and updated the dylib images that were missing.

```bash
$ cd tesseract_standalone/tesseract/4.1.1/bin/
$ sudo install_name_tool -change /usr/local/opt/leptonica/lib/liblept.5.dylib ../../../leptonica/1.80.0/lib/liblept.5.dylib tesseract 
```

## Executable Path

Just when I thought I had fixed all the errors and Tesseract was able to run fine, I got a missing image error when I double clicked the tesseract executable and when I called it through pytesseract.

With a [bit of searching](https://stackoverflow.com/questions/46426136/cannot-run-a-program-by-clicking-on-it-while-i-can-from-terminal), I found out that the system couldn't find this library with just the usual relative path notation like *../* but required the use of **@executable_path**.

```bash
sudo install_name_tool -change /usr/local/Cellar/tesseract/4.1.1/lib/libtesseract.4.dylib @executable_path/../lib/libtesseract.4.dylib tesseract
```

With this extra dylib updated, I can now also run the tesseract executable by clicking on it or through pytesseract.  

Here is the final script!

```bash
#!/bin/sh

if ! command -v tesseract &> /dev/null
then
    brew install tesseract
fi

if [ "$EUID" -ne 0 ]
  then echo "Please run as root"
  exit
fi

mkdir tesseract_standalone
cp -r /usr/local/Cellar/tesseract tesseract_standalone/
cp -r /usr/local/Cellar/leptonica tesseract_standalone/
cp -r /usr/local/Cellar/libpng tesseract_standalone/
cp -r /usr/local/Cellar/libtiff tesseract_standalone/
cp -r /usr/local/Cellar/webp tesseract_standalone/
cp -r /usr/local/Cellar/openjpeg tesseract_standalone/
cp -r /usr/local/Cellar/jpeg tesseract_standalone/
cp -r /usr/local/Cellar/giflib tesseract_standalone/

cd tesseract_standalone/tesseract/4.1.1/bin/
install_name_tool -change /usr/local/opt/leptonica/lib/liblept.5.dylib ../../../leptonica/1.80.0/lib/liblept.5.dylib tesseract 
install_name_tool -change /usr/local/Cellar/tesseract/4.1.1/lib/libtesseract.4.dylib @executable_path/../lib/libtesseract.4.dylib tesseract
install_name_tool -change /usr/local/opt/leptonica/lib/liblept.5.dylib ../../../leptonica/1.80.0/lib/liblept.5.dylib ../../../tesseract/4.1.1/lib/libtesseract.4.dylib
install_name_tool -change /usr/local/opt/libpng/lib/libpng16.16.dylib ../../../libpng/1.6.37/lib/libpng16.16.dylib ../../../leptonica/1.80.0/lib/liblept.5.dylib
install_name_tool -change /usr/local/opt/jpeg/lib/libjpeg.9.dylib ../../../jpeg/9d/lib/libjpeg.9.dylib ../../../leptonica/1.80.0/lib/liblept.5.dylib
install_name_tool -change /usr/local/opt/giflib/lib/libgif.7.dylib ../../../giflib/5.2.1/lib/libgif.7.dylib ../../../leptonica/1.80.0/lib/liblept.5.dylib
install_name_tool -change /usr/local/opt/libtiff/lib/libtiff.5.dylib ../../../libtiff/4.2.0/lib/libtiff.5.dylib ../../../leptonica/1.80.0/lib/liblept.5.dylib
install_name_tool -change /usr/local/opt/webp/lib/libwebp.7.dylib ../../../webp/1.2.0/lib/libwebp.7.dylib ../../../leptonica/1.80.0/lib/liblept.5.dylib
install_name_tool -change /usr/local/opt/openjpeg/lib/libopenjp2.7.dylib ../../../openjpeg/2.4.0/lib/libopenjp2.2.4.0.dylib ../../../leptonica/1.80.0/lib/liblept.5.dylib
install_name_tool -change /usr/local/opt/jpeg/lib/libjpeg.9.dylib ../../../jpeg/9d/lib/libjpeg.9.dylib ../../../libtiff/4.2.0/lib/libtiff.5.dylib
```