Re: Factor

Factor: the language, the theory, and the practice.

Deploy Issues on MacOS

Thursday, July 18, 2024

While trying to help get the BitGuessr game deployed on macOS, I ran into a few issues that were interesting, and I wanted to discuss the process of troubleshooting them.

Sometimes, using the deploy tool is easy, and sometimes it is not-so-easy. There are some challenges around choosing the right level of reflection for the features used in the application you are trying to deploy – we suggest starting with Full environment and then reducing until the program breaks – but besides that it is typically one command:

IN: scratchpad "bitguessr" deploy

That command results in a per-platform executable, which on macOS is an .app bundle that includes the Factor executable, a deployed image, any resources the deployed image uses, and any libraries that the deployed image depends on:

$ find bitguessr.app 
bitguessr.app
bitguessr.app/Contents
bitguessr.app/Contents/Frameworks
bitguessr.app/Contents/Frameworks/libraylib.dylib
bitguessr.app/Contents/Info.plist
bitguessr.app/Contents/MacOS
bitguessr.app/Contents/MacOS/bitguessr
bitguessr.app/Contents/Resources
bitguessr.app/Contents/Resources/bitguessr
bitguessr.app/Contents/Resources/bitguessr/_resources
bitguessr.app/Contents/Resources/bitguessr/_resources/bitguessr_icon.png
bitguessr.app/Contents/Resources/bitguessr/_resources/bitguessr_soundtrack.wav
bitguessr.app/Contents/Resources/bitguessr/_resources/button-0.png
bitguessr.app/Contents/Resources/bitguessr/_resources/button-1.png
bitguessr.app/Contents/Resources/bitguessr/_resources/correct.wav
bitguessr.app/Contents/Resources/bitguessr/_resources/wrong.wav
bitguessr.app/Contents/Resources/bitguessr.image
bitguessr.app/Contents/Resources/Icon.icns

After building this application, checking that it works for me, and uploading it to the server, of course we got a bug report when someone else tried to run it:

$ ./bitguessr.app/Contents/MacOS/bitguessr
...
Cannot resolve C library function
Library: DLL" libraylib.dylib"
Symbol: InitWindow
DlError: none
See https://concatenative.org/wiki/view/Factor/Requirements

Is the InitWindow symbol in the library?

$ nm -gU ./bitguessr.app/Contents/Frameworks/libraylib.dylib | grep InitWindow
0000000000017920 T _InitWindow

Yes, it is.

Is it loading the correct libraylib.dylib file?

$ DYLD_PRINT_LIBRARIES=1 ./bitguessr.app/Contents/MacOS/bitguessr
...
dyld[69951]: <B5534AF8-58E9-3F59-A5DE-F33164570F6B> ./bitguessr.app/Contents/Frameworks/libraylib.dylib

Yes, it seems to be.

Let’s learn more about how dynamic libraries work. There is a nice thread on dynamic library identification that goes into some details about how these are identified and then loaded.

Let’s start with the library – we get our Raylib from Homebrew:

$ cd $(brew --prefix raylib)

$ otool -l libraylib.dylib | grep -A 2 LC_ID_DYLIB
          cmd LC_ID_DYLIB
      cmdsize 72
         name /usr/local/opt/raylib/lib/libraylib.450.dylib (offset 24)

Okay, so this probably needs to be relative to a “runtime path” or rpath, which you can either set:

$ install_name_tool -id "@rpath/libraylib.dylib" libraylib.dylib

Or, fix by downloading a Raylib release that is already set properly for embedding.

Did it change?

$ otool -l libraylib.dylib | grep -A 2 LC_ID_DYLIB
          cmd LC_ID_DYLIB
      cmdsize 48
         name @rpath/libraylib.dylib (offset 24)

Yes, it did!

Now that we have that, we can re-deploy and see if it works:

$ ./bitguessr.app/Contents/MacOS/bitguessr
...
Cannot resolve C library function
Library: DLL" libraylib.dylib"
Symbol: InitWindow
DlError: none
See https://concatenative.org/wiki/view/Factor/Requirements

Nope.

Okay, maybe the rpath that is used to lookup dynamic libraries isn’t set properly:

$ otool -l ./bitguessr.app/Contents/MacOS/bitguessr| grep -A 2 LC_RPATH

Hmm, it is not set at all. The dynamic linker maintains a list of these “runtime path” directories. Maybe we can make sure it looks in the right place by adding one:

$ cd ./bitguessr.app/Contents/MacOS

$ install_name_tool -add_rpath "@executable_path/../Frameworks" bitguessr

Okay, now it looks right:

$ otool -l ./bitguessr.app/Contents/MacOS/bitguessr | grep -A 2 LC_RPATH
          cmd LC_RPATH
      cmdsize 48
         path @executable_path/../Frameworks (offset 12)

Let’s try again… and, it works!

I pushed a change to set the rpath directory properly for future deploys, changed to distributing it as an Apple Disk image, and also made sure to codesign the application so that it launches easily after being downloaded and gave Joseph Oziel an updated macOS build of BitGuessr which included an Icons.icns file in the Apple Icon Image format for the application icon.

Neat!