Crystal installation using Linuxbrew is not working

Hello!

I try to follow the steps from this guide and install Crystal using brew on linux.
For this command brew install crystal-lang --with-llvm I get Error: invalid option: --with-llvm.
And if I use this one brew install crystal-lang it installs fine, although for the next step on hello world program I get this error.

Installation using snap works just fine though :ok_hand:

I can reproduce a similar issue on an empty ubuntu image with homebrew installed:

$ docker run --rm homebrew/ubuntu22.04
# brew install crystal
# crystal run 'puts %(hello world)'
/home/linuxbrew/.cache/crystal/crystal-run-eval.tmp: error while loading shared libraries: libgc.so.1: cannot open shared object file: No such file or directory

In your case, there is probably a libgc.so.1 available in your path, but it’s not from homebrew and an older version missing the GC_get_my_stackbottom symbol.

A workaround is to specify the homebrew lib directory explicitly:

export LD_LIBRARY_PATH=/home/linuxbrew/.linuxbrew/lib
1 Like

The homebrew package certainly used to work. It’s unclear why it doesn’t anymore.
In the formular there is even a test to ensure compilation works (homebrew-core/Formula/c/crystal.rb at 7778fbcc32cbbee53c6b75e2e352cfef8b3c00e4 · Homebrew/homebrew-core · GitHub).

Probably the homebrew formula should configure CRYSTAL_LIBRARY_RPATH to point to homebrew’s lib directory. That should bake the lookup paths into the binary so it can find them even if homebrew’s lib directory is not in ld’s lookup path.

You can do that manually with

export CRYSTAL_LIBRARY_RPATH=$(brew --prefix)/lib

Thanks for taking a look on this :pray:
Not sure if it’s related, but I see the different LLVM version bundled into the snap package.
15.0.7 instead of 17.0.6
image

The LLVM version is inconsequential for this porblem.

1 Like

I submitted a patch to the homebrew formula:

1 Like

If delay-loading is removed on Windows, that would mean deprecating CRYSTAL_LIBRARY_RPATH on Linux too. On the other hand I don’t know if a better solution exists (CRYSTAL_CONFIG_BUILD_OPTS…?)

Yeah I think we’ll need a feature to configure RPATH. And keeping the existing config variable should be good.

I went to see how runtim linking for binaries compiled with GCC from homebrew work. That should have the same need to find the libraries in homebrew lib.

$ brew install gcc
$ cat << EOF > test.c
unsigned GC_get_version(void);

int main() {
  GC_get_version();

  return 0;
}
EOF
$ gcc-13 test.c -o test -lgc
$ readelf -a test | grep -i linuxbrew
      [Requesting program interpreter: /home/linuxbrew/.linuxbrew/lib/ld.so]
 0x000000000000001d (RUNPATH)            Library runpath: [/home/linuxbrew/.linuxbrew/lib/gcc/current:/home/linuxbrew/.linuxbrew/lib]

And indeed, the runpath is configured accordingly.

It seems the GCC formular is essentially doing the same thing as my RPATH patch: configure an implicit rpath that points to the homebrew lib dir for all produced binaries.

Additionally, in binaries built by GCC, the program interpreter als points to ld.so from homebrew. Not sure what’s the specific purpose of that, though. Rpath is sufficient for library discovery. But I guess it generally makes sense to use homebrew’s linker as well.
I don’t think there’s a way to configure that in Crystal.
Maybe via forwarding --link-flags…?

So as long as Crystal is using Brew’s linker, isn’t there no need for us to explicitly use Brew’s directories as the RUNPATH? A similar thing happens on Termux and Crystal doesn’t need this config variable over there

1 Like

Oh, yeah that could be a great idea. I just checked this:

$ CC=$(brew --prefix)/bin/gcc-13 crystal build hello.cr
$ readelf -a empty | grep -i linuxbrew/lib/
      [Requesting program interpreter: /home/linuxbrew/.linuxbrew/lib/ld.so]
 0x000000000000001d (RUNPATH)            Library runpath: [/home/linuxbrew/.linuxbrew/lib/gcc/current:/home/linuxbrew/.linuxbrew/lib]

So this has exactly the same result as the C binary. No need for explicit runpath configuration.

However, there is currently no way to configure the compiler’s default linker. I suppose it should be pretty straightforward to introduce CRYSTAL_CONFIG_CC, though.

This would require adding gcc as a dependency of the Crystal formula. Currently it is not a dependency. Crystal just uses the default cc, which may come from a system package.

Thank you for looking into this issue. Unfortunately, I’m still encountering the same error after following the test commands.

$ docker run -it homebrew/ubuntu22.04
$ brew install crystal
$ crystal eval 'puts %(hello world)'
/home/linuxbrew/.cache/crystal/crystal-run-eval.tmp: error while loading shared libraries: libgc.so.1: cannot open shared object file: No such file or directory

Please let me know if I am missing something.

I’m also curious if the brew install crystal-lang --with-llvm should be fixed as well. I don’t see the --with-llvm option inside brew package. Is it obsolete for the current version?

I guess we’ll need a new revision for the updated formula to actually roll out.

Probably, yes.

Actually, CRYSTAL_CONFIG_LIBRARY_RPATH isn’t supported. I’m submitting a patch to the compiler.

I have created to PRs related to this:

With those two we should be able to configure the homebrew compiler to have both CC and LIBRARY_PATH set to the respective paths from homebrew.

Once again, I am not sure if we should do anything to RPATH from the compiler itself, since we know that is broken on Windows. Can you simply set CRYSTAL_CONFIG_CC="cc -Wl,-rpath,..."?

I suppose we could do that as well.
I explicitly made two separate PRs so we can merge only one or both.

In the current situation with CRYSTAL_LIBRARY_RPATH it makes sense to have a config value for that as well. But we can discuss the future of RPATH separately.

Progress update:

And bit more info dump:

A probable cause for this issue is when pkg-config is from homebrew and and cc is not. (please let me know if you have this issue but this is not the case)
pkg-config reports homebrew lib paths, so cc will pick up the libraries from homebrew while linking even when it doesn’t know anything about homebrew itself.
But it does not configure rpath to include the homebrew lib dir (which gcc from homebrew does). So the system loader will only see the system libraries. If there’s a discrepancy between homebrew and system libs, that’s where it crashes.

So, a trivial resultion would also be to ensure you have both cc and pkg-config either installed from homebrew, or both not installed from homebrew. (in the latter case you might experience issues with linking libraries installed via homebrew)

Thanks for sharing more insights on the topic.
Yes, indeed in my case the cc and pkg-config are used from different sources.

$ which cc
/usr/bin/cc
$ which pkg-config
/home/linuxbrew/.linuxbrew/bin/pkg-config

In that case I can try to reinstall cc using brew and check it that resolves the issue.