Swift

Linking Swift Executables with Dynamic Libraries

Building Swift executables that rely on dynamic libraries but failing to distribute them? You’re not alone. While Swift makes building command-line tools straightforward, deploying them with dynamic library dependencies can be tricky. In this guide, we’ll explore how to properly link and distribute Swift executables with their dynamic library dependencies on macOS.

Understanding Libraries: The Foundation

Dynamic libraries (.dylib files) and static libraries serve different purposes in software development:

  • Static Libraries: Think of them as books permanently bound into your application at compile time.
  • Dynamic Libraries: More like reference materials your application looks up at runtime, making them more flexible but also more complex to manage.

Real-World Example: The XMLJson Case Study

Let’s dive into a practical example. We’ll use XMLJson, a command-line tool, to illustrate common challenges and solutions. When we build the executable, it is built in the .build/release folder. we can use the tool direcly by acciccing the binary from the build folder.

like this:

$ .build/release/xmljson

The Problem

Everything works perfectly in your build folder, but try to make it globally available and:

$ cd .build/release
$ cp -f xmljson /usr/local/bin/xmljson

You’ll encounter this dreaded error:

dyld: Library not loaded: @rpath/libSwiftToolsSupport.dylib
Referenced from: /usr/local/bin/xmljson
Reason: image not found
[1]    44343 abort      xmljson

Understanding the Error

Let’s break down this error message:

  1. dyld is macOS’s dynamic linker - think of it as a librarian trying to find books
  2. It’s looking for libSwiftToolsSupport.dylib
  3. The search is failing because of something called @rpath

the error is telling us that the library is not found at the expected location, which is the standard library location. The real question is why and how to to make it find the missing library. Let us dive into the magic of install names and how to properly link dynamic libraries on macOS.

The Magic of Install Names

Each dynamic library in your Swift executable has an “install name” - a path baked into the library that tells dyld where to find it at runtime. This path gets saved in your executable during linking.

Key Path Concepts

When building Swift executables, understanding three key path concepts is crucial:

  1. @executable_path

    • Points to your executable’s location
    • Example: For /usr/local/bin/xmljson, expands to /usr/local/bin/
  2. @loader_path

    • Points to the location of the binary currently loading a dependency. Follows the chain of library dependencies
    • Useful for nested library dependencies
  3. @rpath

    • A list of search paths embedded in the executable
    • Configurable during build or post-build

Solving the Deployment Puzzle

Analyzing the Binary

The otool (object file displaying tool) is an essential utility for inspecting Mach-O binaries on macOS. Let’s use it to understand our executable’s dependencies both before and after our modifications.

otool -L xmljson

this will show us the install name of the library that is being linked to the executable.

$ otool -L xmljson
xmljson:
    @rpath/libSwiftToolsSupport.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/swift/libswiftCore.dylib (compatibility version 1.0.0, current version 9999.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1200.3.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)

In this output, we can see:

  • Our problematic dependency using @rpath
  • System Swift libraries with absolute paths
  • Standard system libraries

now after moving the executable to a different location, the install name of the library will be wrong and the library will not be found.

After Moving to /usr/local/bin:

$ otool -L /usr/local/bin/xmljson
/usr/local/bin/xmljson:
    @rpath/libSwiftToolsSupport.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/swift/libswiftCore.dylib (compatibility version 1.0.0, current version 9999.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1200.3.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)

Notice the dependency path hasn’t changed - this is why our executable fails.

The Solution: install_name_tool

Here’s how to fix the library paths:

  1. Copy files to their proper locations:
# Copy executable
cp xmljson /usr/local/bin/
# Copy library to the standard library location
cp libSwiftToolsSupport.dylib /usr/local/lib/
  1. Update the library path:
install_name_tool -change \
    ".build/release/libSwiftToolsSupport.dylib" \
    "/usr/local/lib/libSwiftToolsSupport.dylib" \
    "/usr/local/bin/xmljson"

Now if we run the otool again, we can see that the install name of the library has been updated to the correct path.

$ otool -L /usr/local/bin/xmljson
/usr/local/bin/xmljson:
    /usr/local/lib/libSwiftToolsSupport.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/swift/libswiftCore.dylib (compatibility version 1.0.0, current version 9999.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1200.3.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)

Now we see:

  • The @rpath is replaced with the absolute path
  • Our library is properly referenced in /usr/local/lib
  • System dependencies remain unchanged

Best Practices

  1. Always place libraries in standard system locations
  2. Use install_name_tool for post-build path adjustments
  3. Consider using relative paths with @rpath for redistributable software
  4. Document your library dependencies clearly

Conclusion

Understanding dynamic libraries and their linking mechanism is crucial for macOS development. While they may seem complex at first, the tools and concepts we’ve covered provide a solid foundation for handling library-related challenges. Remember: proper library management is like maintaining a well-organized book collection - it requires understanding where everything belongs and how to find it.