Skip to content

Conversation

@tobi
Copy link

@tobi tobi commented Jan 2, 2026

Summary

When brew is not in PATH, use_homebrew_yaml fails silently and Ruby compiles without libyaml/psych support.

This PR adds a fallback that downloads and builds libyaml from source when brew isn't in the path, similar to how OpenSSL is already handled.

Changes

  • Add build_package_yaml to install libyaml to $PREFIX_PATH/libyaml
  • Add fetch_libyaml_latest_version to get latest version from GitHub API (falls back to 0.2.5)
  • Add build_yaml_from_source as fallback in the yaml detection chain
  • Fix use_freebsd_yaml to return failure when not on FreeBSD (was silently succeeding)

After

$ PATH=/usr/bin:/bin ruby-build 3.4.1 /tmp/ruby
ruby-build: building libyaml 0.2.5 from source
==> Downloading yaml-0.2.5.tar.gz...
==> Installing yaml-0.2.5...
# ... continues with Ruby build ...
$ /tmp/ruby/bin/ruby -e "require 'yaml'"
# works!

When brew is not in PATH, use_homebrew_yaml fails silently and Ruby
compiles without libyaml/psych support. This adds a fallback that
downloads and builds libyaml from source, similar to how OpenSSL is
handled.

Changes:
- Add build_package_yaml to install libyaml to PREFIX_PATH/libyaml
- Add fetch_libyaml_latest_version to get latest version from GitHub
  API with fallback to 0.2.5
- Add build_yaml_from_source as fallback in the yaml detection chain
- Fix use_freebsd_yaml to return failure when not on FreeBSD
@eregon
Copy link
Member

eregon commented Jan 2, 2026

This builds libyaml on Linux even if it's already available on the system:

$ ruby-build ruby-4.0.0 ~/.rubies/ruby-4.0.0-libyaml
...
==> Downloading yaml-0.2.5.tar.gz...
...
==> Installing yaml-0.2.5...
-> ./configure "--prefix=$HOME/.rubies/ruby-4.0.0-libyaml/libyaml"
-> make -j 16
-> make install
==> Installed yaml-0.2.5 to /home/eregon/.rubies/ruby-4.0.0-libyaml
-> ./configure "--prefix=$HOME/.rubies/ruby-4.0.0-libyaml" --enable-shared "--with-libyaml-dir=$HOME/.rubies/ruby-4.0.0-libyaml/libyaml" --with-ext=openssl,psych,+

That would be a regression on Linux, e.g. OpenSSL is not built on Linux if already available on the system.

BTW in that scenario psych links to the system libyaml:

$ ldd ~/.rubies/ruby-4.0.0-libyaml/lib/ruby/4.0.0/x86_64-linux/psych.so
	linux-vdso.so.1 (0x00007f8472ac2000)
	libruby.so.4.0 => /home/eregon/.rubies/ruby-4.0.0-libyaml/lib/libruby.so.4.0 (0x00007f8472200000)
	libyaml-0.so.2 => /lib64/libyaml-0.so.2 (0x00007f84721c4000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f84720de000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f8471eeb000)
	libz.so.1 => /lib64/libz.so.1 (0x00007f8471ec7000)
	libcrypt.so.2 => /lib64/libcrypt.so.2 (0x00007f8471e91000)
	libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f8471e62000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f8472ac4000)

which e.g. could break if the versions differ.

Probably the simplest & safest for now is to build_yaml_from_source only on macOS.

Copy link
Member

@mislav mislav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thank you for the contribution.

On top of everything @eregon already pointed out, I would also like to note that within the ruby-build project, we prefer that the declaration of dependencies is explicit in the ruby definition itself, for example how we did with libyaml up to Ruby 2.1.0:

install_package "yaml-0.1.6" "http://pyyaml.org/download/libyaml/yaml-0.1.6.tar.gz#7da6971b4bd08a986dd2a61353bc422362bd0edcc67d7ebaac68c95f74182749" --if needs_yaml

Starting from Ruby 2.2, we stopped using the needs_yaml declaration in ruby formulae, although my memory is fuzzy and I'm not sure why. It might have something to do with the psych gem bundling its own libyaml at some point? /cc @hsbt d646595

In any case, if we added the needs_yaml dependency back to Ruby definitions, we would need to make sure that libyaml would not be downloaded and compiled on systems that already have libyaml already present and discoverable in the build path. This last part is tricky since discovering system libraries isn't exactly straightforward. For example, we try to only download and build OpenSSL when it's not present on the system or when the system version is incompatible, but our detection approach for openssl is somewhat complex and cannot be easily extended to libyaml.

@mislav
Copy link
Member

mislav commented Jan 3, 2026

Based on https://github.com/ruby/psych/blob/master/ext/psych/extconf.rb, I would be down with something like the following.

First, adding this directive to modern (i.e. non EOL'd) Ruby definitions:

install_package "yaml-0.2.5" "https://github.com/yaml/libyaml/archive/refs/tags/0.2.5.tar.gz#SHA" yaml --if needs_yaml

Then, redefining needs_yaml to something that follows the logic:

  1. If --with-libyaml-dir was supplied, return 1;
  2. If use_homebrew_yaml was successful, return 1;
  3. If use_freebsd_yaml was successful, return 1;
  4. If compiling a simple C program that imports yaml.h succeeds, return 1;
  5. Otherwise, return 0.

And finally, defining a build_package_yaml that wraps build_package_standard:

build_package_yaml() {
  local YAML_PREFIX_PATH="${PREFIX_PATH}/libyaml"
  package_option ruby configure --with-libyaml-dir="$YAML_PREFIX_PATH"
  package_option yaml configure --disable-dependency-tracking
  capture_command ./bootstrap
  build_package_standard "$@"
}

@eregon
Copy link
Member

eregon commented Jan 3, 2026

Starting from Ruby 2.2, we stopped using the needs_yaml declaration in ruby formulae, although my memory is fuzzy and I'm not sure why. It might have something to do with the psych gem bundling its own libyaml at some point? /cc @hsbt d646595

Yes, psych used to vendor libyaml, but then more recently no longer does that and instead has the --with-libyaml-source-dir option.

@eregon
Copy link
Member

eregon commented Jan 4, 2026

When brew is not in PATH, use_homebrew_yaml fails silently and Ruby compiles without libyaml/psych support.

This should not happen, because of

ruby-build/bin/ruby-build

Lines 711 to 714 in 079b5af

# For Ruby 2.5+, fail the `make` step if any of these extensions were not compiled.
# Otherwise, the build would have succeeded, but Ruby would be useless at runtime.
# https://github.com/ruby/ruby/commit/b58a30e1c14e971adba4096104274d5d692492e9
package_option ruby configure --with-ext=openssl,psych,+

Did this break in CRuby or does this work as expected and ruby-build fails in that case?

@eregon
Copy link
Member

eregon commented Jan 4, 2026

For context, ruby-build in general does not install dependencies itself, instead they are documented here. The list is long so I'm unsure if just handling libyaml is a big help. ruby-build or CRuby should definitely fail early if libyaml is missing though.
OpenSSL is the exception because it's messy to install due to Ruby needing very specific OpenSSL versions and some package managers offering only a single version of OpenSSL.

@eregon
Copy link
Member

eregon commented Jan 4, 2026

Did this break in CRuby or does this work as expected and ruby-build fails in that case?

I tried on my machine by removing libyaml-devel and it fails as expected:

$ ruby-build ruby-4.0.0 ~/.rubies/ruby-4.0.0-test   
==> Downloading ruby-4.0.0.tar.gz...
...
==> Installing ruby-4.0.0...
-> ./configure "--prefix=$HOME/.rubies/ruby-4.0.0-test" --enable-shared --with-ext=openssl,psych,+
-> make -j 16
*** Following extensions are not compiled:
psych:
	Could not be configured. It will not be installed.
	Check /tmp/ruby-build.20260104173455.588508.nksbP5/ruby-4.0.0/ext/psych/mkmf.log for more details.

BUILD FAILED (Fedora Linux 41 on x86_64 using ruby-build 20251225-2-g079b5af5)

$ cat /tmp/ruby-build.20260104173455.588508.nksbP5/ruby-4.0.0/ext/psych/mkmf.log
...
conftest.c:3:10: fatal error: yaml.h: No such file or directory
    3 | #include <yaml.h>
      |          ^~~~~~~~

From that, I think keeping things as they are is best, it's already a clear error and AFAIK it's not hard to install libyaml (unlike OpenSSL which is messy due to Ruby needing very specific versions).
Also just solving libyaml means still missing e.g. readline gmp rust on macOS.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants