Can't use Ruby sassc gem, fails at runtime with FFI

The ruby sassc gem bundles libsass as a C library and uses the ffi gem to talk to its bundled compiled version. When I build the gem, it spends a while building native extensions, but the end result is completely unusable. Trying to load the sassc gem fails with a stack trace that ends in the error

FFI::NotFoundError (Function 'libsass_version' not found in [/nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc/libsass.bundle])

(that referenced bundle file contains no symbols beyond dyld_stub_binder so I don’t know what’s going on there)

I saw this with a bundlerApp package but I can trivially reproduce this with the following line:

nix run '(with import <nixpkgs> {}; ruby.withPackages (ps: [ ps.sassc ]))' -c ruby -r sassc -e ''
Full output
Traceback (most recent call last):
	1: from /nix/store/9lywmsbfjcwjgdfvp6p9whx8gl3hyf71-ruby-2.6.4/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/nix/store/9lywmsbfjcwjgdfvp6p9whx8gl3hyf71-ruby-2.6.4/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- sassc (LoadError)
	13: from /nix/store/9lywmsbfjcwjgdfvp6p9whx8gl3hyf71-ruby-2.6.4/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:34:in `require'
	12: from /nix/store/9lywmsbfjcwjgdfvp6p9whx8gl3hyf71-ruby-2.6.4/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:130:in `rescue in require'
	11: from /nix/store/9lywmsbfjcwjgdfvp6p9whx8gl3hyf71-ruby-2.6.4/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:130:in `require'
	10: from /nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc.rb:31:in `<top (required)>'
	 9: from /nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc.rb:31:in `require_relative'
	 8: from /nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc/native.rb:5:in `<top (required)>'
	 7: from /nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc/native.rb:6:in `<module:SassC>'
	 6: from /nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc/native.rb:58:in `<module:Native>'
	 5: from /nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc/native.rb:58:in `require_relative'
	 4: from /nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc/native/native_context_api.rb:3:in `<top (required)>'
	 3: from /nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc/native/native_context_api.rb:4:in `<module:SassC>'
	 2: from /nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc/native/native_context_api.rb:5:in `<module:Native>'
	 1: from /nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc/native.rb:37:in `attach_function'
/nix/store/08x36k91iyvclxbm3yradcm8d1lsybyi-ruby2.6.4-ffi-1.10.0/lib/ruby/gems/2.6.0/gems/ffi-1.10.0/lib/ffi/library.rb:273:in `attach_function': Function 'libsass_version' not found in [/nix/store/x9x15c391y1kmzrcfi75llwlrbdhra2q-ruby2.6.4-sassc-2.2.0/lib/ruby/gems/2.6.0/gems/sassc-2.2.0/lib/sassc/libsass.bundle] (FFI::NotFoundError)

On the other hand, if I use bundler directly in a test, everything works fine.

> cat Gemfile
source 'https://rubygems.org' do
  gem 'sassc'
end
> bundler i --path vendor
[…output…]
> ruby -r bundler/setup -r sassc -e 'print SassC::VERSION'
2.2.0

In this situation vendor/ruby/2.6.0/gems/sassc-2.2.0/lib/sassc/libsass.bundle contains the full expected set of symbols.

Does anyone know what’s going on here? This is on macOS, if that makes a difference.

This is extremely bizarre. I got a nix-shell environment for the sassc gem, and if I build it with gemFlags=-V I find the resulting output:

linking shared-object sassc/libsass.bundle
ld: warning: ignoring file units.o, file was built for unsupported file format ( 0xDE 0xC0 0x17 0x0B 0x00 0x00 0x00 0x00 0x14 0x00 0x00 0x00 0xAC 0x9E 0x08 0x00 ) which is not the architecture being linked (x86_64): units.o
ld: warning: ignoring file fn_miscs.o, file was built for unsupported file format ( 0xDE 0xC0 0x17 0x0B 0x00 0x00 0x00 0x00 0x14 0x00 0x00 0x00 0x48 0x47 0x19 0x00 ) which is not the architecture being linked (x86_64): fn_miscs.o
ld: warning: ignoring file util.o, file was built for unsupported file format ( 0xDE 0xC0 0x17 0x0B 0x00 0x00 0x00 0x00 0x14 0x00 0x00 0x00 0xBC 0x92 0x12 0x00 ) which is not the architecture being linked (x86_64): util.o
…

Ultimately it ignores every single object file while linking.

After digging up the file util.o (which, oddly enough, ends up in the installed output at lib/ruby/gems/2.6.0/gems/sassc-2.2.0/ext/), file tells me it’s LLVM bitcode.

Looks like it’s LLVM bitcode because compilation uses -flto. But the link command is the same clang++ binary that compilation used, so why isn’t it handling this properly?

I just found an issue that claims LTO isn’t working on macOS at all. I’m guessing that’s the cause of all of this.

I guess this leaves me with the question: Given a bundlerApp or bundlerEnv derivation, how can I set the buildFlags setting on one of the contained gems?

Ah hah, I can apply the fix to nixpkgs’s default gem set and this will be inherited by any bundlerApp that declares the same gem. I’ll submit a PR shortly.

EDIT: https://github.com/NixOS/nixpkgs/pull/68766 includes the fix.