Skip to content

Bundle libvips for one-step installation on common platforms #441

Description

@sandstrom

Background

Installing ruby-vips currently requires installing libvips separately on Linux and macOS:

# one of these
apt install libvips42
brew install vips

gem install ruby-vips

This complicates local setup, CI, containers, and deployment. It also allows the installed libvips version and features to vary between environments. In some cases Bundler succeeds, but require "vips" later fails because the shared library is missing.

Ideally, this would be sufficient on common platforms:

gem install ruby-vips

Windows already provides a similar experience through the gemspec's msys2_mingw_dependencies.

Why consider bundling libvips

Bundling could provide:

  • A simpler and more conventional gem installation.
  • Reproducible libvips versions and capabilities across development, CI, and production.
  • No requirement for root access or an additional package manager.
  • Fewer runtime failures caused by missing or incompatible shared libraries.
  • Better isolation when different applications need different libvips versions.

Because ruby-vips uses FFI rather than a compiled Ruby extension, binary packages would primarily vary by operating system, architecture, and libc. They would not necessarily need to be rebuilt for every Ruby version.

Possible approaches (in no particular order)

1. Platform-specific ruby-vips gems

Publish platform variants containing libvips and its runtime dependencies, following the native-gem model used by projects such as Nokogiri.

Initial platforms could include:

  • x86_64-linux-gnu
  • aarch64-linux-gnu
  • x86_64-darwin
  • arm64-darwin

Musl variants could be added separately.

ruby-vips could load the bundled library by an explicit path. The generic ruby platform gem could continue using a system installation.

2. A separate libvips binary gem

Publish libvips in a companion gem, with ruby-vips depending on the appropriate platform variant.

This would separate the bindings from binary packaging and could potentially be reused by other Ruby projects. The cost would be additional dependency and version-resolution complexity.

3. Download a pinned binary during installation

An installation extension could download a platform-specific archive, verify its checksum, and install it within the gem directory.

This would keep the gem itself smaller, but introduces network access during installation, offline-installation problems, and another artifact hosting and integrity mechanism.

4. Build libvips during gem installation

The source archive could be bundled or downloaded and compiled during installation, similar to the source fallback offered by some native gems.

This would support more platforms, but libvips and its optional codec dependencies make compilation relatively slow and complex. It may be better as a fallback than the default.

Existing build infrastructure such as https://github.com/lovell/sharp-libvips may also provide useful prior art for producing relocatable Linux and macOS builds.

Questions for the maintainer

Whichever approach is selected, some policy decisions would be needed:

  • Which platforms and minimum OS or glibc versions should be supported?
  • Which image formats and optional dependencies should the bundled build include?
  • How closely should ruby-vips releases track bundled libvips versions?
  • Should bundled libvips take precedence over a system installation?
  • How should users explicitly opt into a system-provided libvips?
  • How should licenses, checksums, build provenance, and security updates be handled?
  • What gem size is acceptable?

The platform-specific gem approach appears closest to established Ruby packaging practices, but this suggestion is intentionally open-ended.

The main goal is to make gem install ruby-vips sufficient on common Linux and macOS platforms while preserving a supported system-library option.

Examples from other gems

Approach 1: Platform-specific binary gems

  • nokogiri (source) ships precompiled Linux, macOS, and Windows gems containing its native dependencies. It retains source and system-library options.
  • sqlite3 (source) ships platform gems with SQLite statically linked. Its ruby platform gem compiles packaged SQLite source.
  • pg (source) ships platform gems containing libpq. Users can select the source gem to use a system installation.
  • grpc (source) publishes platform gems for Linux and macOS, plus a source gem.
  • google-protobuf (source) publishes platform gems containing its native UPB implementation.
  • sass-embedded (source) packages the Dart Sass executable in platform-specific gems.
  • commonmarker (source) publishes platform gems containing its Rust implementation, plus a source build.

Approach 2: Separate binary dependency gem

  • mini_racer (source) uses the companion libv8-node gem to distribute V8. libv8-node provides platform binaries and a source fallback.

Approach 3: Managed dependency download

  • selenium-webdriver (source) includes Selenium Manager, which detects and downloads the appropriate browser driver. This is not a shared-library example, but demonstrates automatic management of an external platform dependency.

Approach 4: Bundled source compiled during installation

  • rugged (source) builds its vendored libgit2 by default and supports --use-system-libraries.
  • ffi (source) builds its bundled libffi when the system version is missing or too old. Flags can force either version.
  • hiredis-client (source) builds and statically links its bundled hiredis source.
  • zstd-ruby (source) compiles its bundled Zstandard source.
  • brotli (source) compiles its bundled Brotli source.
  • sassc (source) historically bundled and compiled libsass. It is now end-of-life and has been replaced by sass-embedded.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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