Skip to content

RCD 🤝 Rust (my experience) #74

Open
@ianks

Description

@ianks

I maintain a project called rb-sys that builds on top of the rake-compiler ecosystem, so building native extensions with Rust would have the same workflow as a C extension. I wanted make it easy to precompile Rust gems for multiple platforms in a repeatable and maintaintainable way. Naturally, I turned to RCD.

After chatting with @flavorjones a bit, he nudged me to document my experience using rake-compiler-dock in the rb-sys project (thanks Mike!).

Overall, I'm really happy with how everything has turned out. RCD is such an important gem for Ruby, and I'm grateful for all of the great work that has been put into it.

(ps: I apologize in advance for the massive brain dump, but I knew it was the only way I could document this stuff!)

How I used RCD to compile Rust extensions

Here's a brief overview of things I did to make Rust extensions work with RCD. It's kind of a brain dump, but I hope it's useful.

  • I created a bunch of platform-specific dockerfiles which all
    inherit FROM larskanis/rake-compiler-dock-mri-$PLAT:$VERSION.
  • Each dockerfile installs the Rust toolchain and any other dependencies.
  • There's a CI job that builds the docker images and pushes them to dockerhub after each release.
  • Since the rb_sys gem knows how to generate a compatible Makefile from an extconf.rb, users can just use RCD normally by specifying the RCD_IMAGE=rbsys/$RUBY_PLATFORM:$RB_SYS_VERSION environment variable.
  • I also made a GitHub Action to make this process a bit easier.

Stumbling blocks

I ran into a few issues while trying to get this to work. I'll try to document them here. Some of them are Rust specific, so take those as you will.

Bundler and rake-compiler-dock (user experience)

rake-compiler-dock doesn't ensure that bundle install is run for each RUBY_CC_VERSION. This means that any Rakefile that uses bundler/setup will fail with Bundler::GemNotFound (example here). For users of RCD, this is not an intuitive fix, as it's not obvious what the problem is. I've seen a few people run into this feel "actually a bit stuck" (their words).

Essentially, the manual fix is to do something like:

  1. Do the bundle yourself: rvm 3.1 && bundle install && rvm 3.0 && bundle install ...
  2. Be very cautious and make sure to not include bundler/setup or extra gems in your Rakefile.

IMO, neither options is ideal. I think it would be nice if RCD could run bundle install for each RUBY_CC_VERSION before invoking the commands. This would be an easy win and make RCD much more user friendly!

OS differences

Now this is a more general issue with the cross-platform ecosystem, and not RCD specific. However, I think it's worth mentioning.

Depending on which platform you are compiling for, you may have an entirely different OS / package-manager. Some are redhat, some are debian, and the versions seem to be inconsistent. This poses a challenge when installing dependencies. Typically, it's best practice to compile any gem native libs yourself (e.g. with miniportile), but for certain things (like LLVM, CMake, etc) this is not feasible. This puts us in a situation where were have differing versions of these deps depending on the platform. Adds some complexity to the build process.

CC, CXX, AR, LD and the like

This one is not a huge deal, but it has tripped me up a few times. When compiling a third party library, you often need to make sure that the CC, CXX, AR, LD and other environment variables are set correctly.

In the rb-sys dockerfile, I've hardcoded sane defaults for the Rust ecosystem like so:

ENV CC_arm_unknown_linux_gnueabihf="arm-linux-gnueabihf-gcc" \
    CXX_arm_unknown_linux_gnueabihf="arm-linux-gnueabihf-g++" \
    AR_arm_unknown_linux_gnueabihf="arm-linux-gnueabihf-ar"

IIRC, rake-compiler-dock does not do this automatically set CC/CXX for you. I wonder if it should?

Mounting cache directories

By default, rake-compiler-dock mounts the ./tmp directory so things are cached nicely. For Rust, I would also like to be able to cache the ./target directory. I finagled this once, but I don't remember how. I think it's possible, but it's not obvious how to do it. I wonder if RCD should support some type of configuration file to make this type of thing easier?

# .rake-compiler-dock.yml ????
global:
  extra_mounts: ["./target:/wherever/the/gem/is/target"]
  env:
    BAR: baz

x86_64-linux:
  image: larskanis/my-custom-mri-x86_64-linux:latest
  env:
    FOO: bar

Different compilers

This is probably biased for the Rust world, but it would be amazing Ruby were built using clang + lld for every platform (ideally, the same version of clang for each). This would fix so many headaches and edge cases.

Docker Woes

Again, not an RCD problem, but a more general grief... Docker for Mac is almost un-useably slow for me on M1 (and bug ridden). The slow feedback cycle is extra-painful when debugging build issues.

Using a remote DOCKER_HOST doesn't work either since directories cannot be mounted from the host. The only solution I've really found is ssh'ing into another machine. :/

I'm curious if anyone else has run into this, and if they know a better way?

Testing

Testing precompiled gems is a bit of a pain. To solve this, I've created a useful monstrosity to make this a bit easier. It's not ideal, but I'm able to run an entire test suite against a precompiled gem, which is nice.

[Here's the Gist][gist] if you're curious. Maybe we could extract some of this into something proper? It would be nice to have a golden path for testing precompiled gems, because as of now it's a bit of a lone-wolf situation.

Summary

Thanks to RCD, we have a way to reliably build cross-platform gems with Rust. Although there are a couple stumbling blocks for new users, hopefully we can collaborate to make it easier.

PS: Would love to integrate the rb-sys docker stuff as well, if interested.

❤️ Ian

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions