I’m currently working on a Crystal project, and I’ve run into an issue when trying to run my compiled binary on another macOS machine. The application throws the following error:
dyld[29586]: Library not loaded: /usr/local/opt/pcre2/lib/libpcre2-8.0.dylib
Referenced from: <2270A1BE-CF50-31DF-8947-32B67C9A0FCD> /Users/SOMEONE/Downloads/SOMEExECUTABLE
Reason: tried: '/usr/local/opt/pcre2/lib/libpcre2-8.0.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/opt/pcre2/lib/libpcre2-8.0.dylib' (no such file), '/usr/local/opt/pcre2/lib/libpcre2-8.0.dylib' (no such file)
zsh: abort ./SOMEPROGRAM
The issue arises because the target machine does not have libpcre2-8.0.dylib installed in the expected location.
I compiled the program with the following command to link the static library:
Many Mac users manage their tools with Homebrew, so option 2 is simple.
I haven’t tried option 3, so I hope someone more experienced can explain how to do it.
macOS doesn’t support fully statically linked binaries, but the missing part is the system library. It should be entirely possible to statically link libpcre2.
Following the instructions on Static Linking - Crystal. You’ll need a static build of libpcre2 for that.
libpcre2 is used for regular expressions. If your program doesn’t use regular expressions, you wouldn’t need libpcre2.
Fully portable builds are impossible on macOS because there is no static version of the system library.
So portability only reaches as far as the system library is binary compatible with other versions of the operating system.
So there’s some limit.
I don’t have much experience with building for macOS, maybe others can chip in on that.
For what it’s worth I don’t seem to have problems with static builds on macOS
Dynamic build :
crystal build my_toy_lang.cr
otool -L my_toy_lang
/opt/local/lib/libpcre2-8.0.dylib (compatibility version 14.0.0, current version 14.0.0)|
/opt/local/lib/libgc.1.dylib (compatibility version 7.0.0, current version 7.3.0)|
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.61.1)|
/opt/local/lib/libevent-2.1.7.dylib (compatibility version 8.0.0, current version 8.1.0)|
/opt/local/lib/libiconv.2.dylib (compatibility version 9.0.0, current version 9.1.0)|
Static build :
crystal build my_toy_lang.cr --static
otool -L my_toy_lang
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.61.1)
Only the system lib is still dynamic but not the other ones (and obviously the size of the executable increases with static build)
encountered an issue while trying to create static builds on macOS. When running the following command:
bash
crystal build helloworld.cr --static
I received the following error:
ld: library not found for -lcrt0.o (this usually means you need to install the development package for libcrt0.o)
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Error: execution of command failed with exit status 1: cc "${@}" -o /Users/idan/Documents/dictionary/helloworld -rdynamic -static -L/opt/homebrew/Cellar/crystal/1.13.1/bin/../../../../lib -L/opt/homebrew/Cellar/pcre2/10.44/lib -lpcre2-8 -D_THREAD_SAFE -pthread -L/opt/homebrew/Cellar/bdw-gc/8.2.6/lib -lgc -lpthread -L/opt/homebrew/Cellar/libevent/2.1.12_1/lib -levent -liconv
It seems that the linker is unable to find -lcrt0.o, which is typically indicative of a missing development package. I would appreciate any advice on how to resolve this issue and successfully build a static binary.
The discussion here involves two different topics.
(1) Creating a statically linked executable on Mac
I think this is very challenging. Many sites like StackOverflow appear when searching for issues related to missing -lcrt0, but there doesn’t seem to be an easy solution.
(2) Do not link libpcre2 when regular expressions are not used
This issue is different from (1), and I felt it was worth investigating further.
hw1.cr
puts "hello world"
At first glance, this code seems simple, but it’s not as straightforward as it looks. It uses strings and performs the complex operation of outputting to standard output. Let’s compile this code and see what functions are included.
crystal build --release hw1.cr
nm hw1
You’ll notice that it includes many functions. Unless specified otherwise, Crystal loads the following libraries:
For much simpler code, the standard library isn’t necessary.
Let’s try modifying the following line like this. It will likely fail since ptr %40.
%41 = add i32 0, 17
Let’s try compiling without pcre2.
clang hw1.ll -lm -lz -levent -lgc
We did it. It compiles without pcre2.
ldd a.out
Indeed, it’s not linked.
Running it produces an error.
Unhandled exception: Invalid libpcre2 version (RuntimeError)
from /usr/local/share/crystal/src/regex/pcre2.cr:18:33 in '~Regex::PCRE2::version_number:init'
The issue lies here.
module Regex::PCRE2
@re : LibPCRE2::Code*
@jit : Bool
def self.version : String
String.new(24) do |pointer|
size = LibPCRE2.config(LibPCRE2::CONFIG_VERSION, pointer) ## %41 HERE!!
{size - 1, size - 1}
end
end
class_getter version_number : {Int32, Int32} = begin
version = self.version
dot = version.index('.') || raise RuntimeError.new("Invalid libpcre2 version") ## THE ERROR!!
space = version.index(' ', dot) || raise RuntimeError.new("Invalid libpcre2 version")
# PCRE2 versions can contain -RC{N} which would make `.to_i` fail unless strict is set to false
{version.byte_slice(0, dot).to_i, version.byte_slice(dot + 1, space - dot - 1).to_i(strict: false)}
end
You can see that the call is exactly the same as %41.
Even though size was set to 17, it likely fails because pointer was not properly set, leading to an incorrect string generation. However, what it is really doing is simply getting the pcre2 version.
So, let’s modify /usr/local/share/crystal/src/regex/pcre2.cr as follows:
version = "10.42 2022-12-11" # self.version
At least this way, pcre2 will no longer be necessary.
crystal build hw1.cr
There are certainly programs that do not need regular expressions. For example, programs that only perform numerical calculations. It might be useful if pcre2 is not linked when it is not needed.
Removing dependencies will always build times, but I wouldn’t imagine the time savings would be noticeable in this case. The Crystal PCRE bindings aren’t that big, so they don’t take that long to compile, and the library itself is already compiled, so removing it saves at most a few milliseconds of link time.
They are always curious to know what version of libpcre2 we are using. I have sent a pull request to protect our secret from them. Hopefully this issue will be fixed.