matsadler / magnus

Ruby bindings for Rust. Write Ruby extension gems in Rust, or call Ruby from Rust.
https://docs.rs/magnus/latest/magnus/
MIT License
679 stars 34 forks source link

Alpine: cannot load such file -- `rb_sys/mkmf` #124

Open Stranger6667 opened 2 days ago

Stranger6667 commented 2 days ago

Hey!

In css-inline I have the following extconf.rb that follows the examples in the magnus repo:

require "mkmf"
require "rb_sys/mkmf"

create_rust_makefile("css_inline/css_inline")

This works on x86 / glibc but doesn't on musl:

Building native extensions. This could take a while...
ERROR:  Error installing css_inline:
    ERROR: Failed to build gem native extension.

    current directory: /bundle/gems/css_inline-0.14.1/ext/css_inline
/usr/local/bin/ruby extconf.rb
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include=${opt-dir}/include
    --without-opt-include
    --with-opt-lib=${opt-dir}/lib
    --without-opt-lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=/usr/local/bin/$(RUBY_BASE_NAME)
<internal:/usr/local/lib/ruby/site_ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:136:in `require': cannot load such file -- rb_sys/mkmf (LoadError)
    from <internal:/usr/local/lib/ruby/site_ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:136:in `require'
    from extconf.rb:2:in `<main>'

extconf failed, exit code 1

Gem files will remain installed in /bundle/gems/css_inline-0.14.1 for inspection.
Results logged to /bundle/extensions/aarch64-linux-musl/3.3.0/css_inline-0.14.1/gem_make.out

So, there is an error:

<internal:/usr/local/lib/ruby/site_ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:136:in `require': cannot load such file -- rb_sys/mkmf (LoadError)

Ref: original issue in css-inline

Steps to reproduce

1. Dockerfile

FROM public.ecr.aws/docker/library/ruby:3.3-alpine3.19 AS runner

ENV APP_HOME="/app"
ENV BUNDLE_BIN="/bundle/bin"
ENV DEBIAN_FRONTEND="noninteractive"
ENV BUNDLE_BUILD__SASSC="--disable-march-tune-native"
ENV BUNDLE_JOBS="5"
ENV BUNDLE_PATH="/bundle"
ENV GEM_HOME="/bundle"
ENV BUNDLE_GEMFILE="$APP_HOME/Gemfile"
ENV PATH="${BUNDLE_BIN}:${PATH}"

WORKDIR $APP_HOME

# Update Gems first...only re-do this when bundler version changes.
# If we need to force a gem update we'll have to `--no-cache` or add a file we can copy in
RUN gem update --system && gem install bundler:2.5.21

RUN apk add --update --no-cache \
    alpine-sdk \
    curl \
    bash \
    gcompat

2. Shell

docker run -it $(docker build -q .) bash

3. Command

gem install css_inline

I am not sure at what layer the problem comes from, but would appreciate any clues.

Thanks

matsadler commented 1 day ago

In your gemspec you've set ext/css_inline/extconf.rb as the config for the extension. RubyGems recognises the 'extconf' file name, and will run your extconf.rb to produce a makefile, and then execute the makefile to compile the extension.

In ext/css_inline/extconf.rb you require mkmf and rb_sys/mkmf (mkmf is short for 'make makefile'). mkmf is part of the ruby standard library, but rb_sys/mkmf is from the rb_sys gem. Back to your gemspec, you list rb_sys as a development dependency. Development dependencies aren't installed along with gem install css_inline, so when installing your gem on a system that doesn't already have rb_sys you get the error cannot load such file -- rb_sys/mkmf (LoadError)

If you changed spec.extensions = ["ext/css_inline/extconf.rb"] to spec.extensions = ["ext/css_inline/Cargo.toml"] RubyGems would instead invoke cargo directly to compile the extension. With this extconf.rb is no longer needed. The downside is this requires RubyGems 3.3.26 or greater. Alternately you could change spec.add_development_dependency "rb_sys", "~> 0.9" to spec.add_dependency "rb_sys", "~> 0.9"` so that rb_sys will also be installed along with your gem.

You'll see some gems with both an extconf.rb and spec.extensions set to the Cargo.toml. This allows the gem to be installed by end users without rb_sys, but also allows using rb_sys/extconf in development for the integration with Rake via rake/extensiontask.

It looks like once the missing rb_sys issue is resolved you'd run in to your extension's Cargo.toml using a relative path to the css-inline crate, and that path isn't packaged with the gem, so the cargo build fails. Removing the path and setting a version so that the dependency is pulled from crates.io should fix that.