For command-line MacOS apps, allows you to package all your dependencies in one single executable, without users having to install library dependencies.
If you're building general-purpose Unix command-line tools, and not Mac apps, the OS does not support statically linking external libraries.
That's not a big deal if you're linking to libraries that are available across all macs, but it's a problem if you want to ship any other third party software.
Generally, tools with such dependencies try to get users to install through Homebrew to manage those dependencies. But many users don't want to go through that trouble.
- Your executable uses pseudolink to bundle all your dependencies. All you have to do is generate a single C file that you compile and link to your project. There's no function you have to call, because;
- The code generated by pseudolink runs before your main entrypoint. If not already present, it writes your dependencies to /tmp/dyld_lib_files_[uid]/ (inserting the UID of the user).
- It sets up the correct value for DYLD_LIBRARY_PATH, if necessary.
- If the generated code did any work in steps #2 and #3, it re-executes your program. This is necessary to make the dynamlic loader recognize the path.
Let's say that, for some reason, you need to bundle in OpenSSL3 to your app, which is definitely not installed on Macs by default.
viega@UpDog pseudolink % make
cc -Wall -O3 -o pseudolink pseudolink.c
viega@UpDog pseudolink %
viega@UpDog pseudolink % ./pseudolink /opt/homebrew/opt/openssl@3/lib/libcrypto.3.dylib /opt/homebrew/opt/openssl@3/lib/libssl.3.dylib
Output bundled dylibs in dyld_decls.c.
Compile and link that file to your program.
viega@UpDog pseudolink %
You need this to be statically compiled into your project, or it won't work. But once it is...
You're done!
Each user gets their own private tmp directory. I did not make them application-specific, but if anyone else wants to use this technique I'd be willing to do that.
Similarly, I didn't make it configurable where that directory goes. I'd consider an option in the home directory, even though I'm fond of extra cruft auto-cleaning on reboot.
Additionally, pseudolink will currently overwrite any existing DYLD_LIBRARY_PATH. That was the right choice for what we needed it for, but again, if anyone needs different behavior, reach out to me (or feel free to submit a PR).
Pseudolink doesn't actually bother to do any checking to ensure the content is actually a dylib. There's no advantage to using this to drop other assets, but I guess if you wanted to do that, you could have at it!
There's also no configurability around the symbol names we chose. You can just edit the generated code if you need to.
If your program has other code that runs before the main entry point (i.e., main() in C), you will need to ensure that, if it depends on the bundled code, pseudolink's code runs first.
The generated C file declares string constants that contain your dylibs. We use the compiler 'constuctor' attribute to have the generated code run before your program's main entry point.
If we don't find the directory and files we expect, we write them out. And, if that directory isn't set in the dynamic loading library path, then we add it (which leads to us quickly self-execing to commit the changes).
John Viega ([email protected]) Copyright 2023, Crash Override Inc.
Pseudolink is made available under the Apache 2.0 license.