Error of “RUBY_FUNCTION_NAME_STRING” when compiling Ruby
Tags: ruby, macports, autotools
Published: 2023-12-02 07:19:17

## TL;DR

An unintended behavior when using `ruby-install` when both MacPorts and Homebrew are installed, a.k.a. “three hours lost in my life”.

## Installation failure

It has been a few weeks since I last wrote some Ruby code, and my Ruby (installed with `ruby-install`) doesn’t work, because MacPorts were updated. This is pretty expected, and I went ahead to reinstall the version of Ruby. When I run `ruby-install` for that, I hit an compilation error:

```
compiling compile.c
In file included from compile.c:40:
./vm_callinfo.h:175:9: error: use of undeclared identifier 'RUBY_FUNCTION_NAME_STRING'
        rp(ci);
        ^
./internal.h:94:72: note: expanded from macro 'rp'
#define rp(obj) rb_obj_info_dump_loc((VALUE)(obj), __FILE__, __LINE__, RUBY_FUNCTION_NAME_STRING)

In file included from compile.c:40:
./vm_callinfo.h:216:16: error: use of undeclared identifier 'RUBY_FUNCTION_NAME_STRING'
    if (debug) rp(ci);
               ^
./internal.h:94:72: note: expanded from macro 'rp'
#define rp(obj) rb_obj_info_dump_loc((VALUE)(obj), __FILE__, __LINE__, RUBY_FUNCTION_NAME_STRING)
                                                                       ^
2 errors generated.
make: *** [compile.o] Error 1
!!! Compiling ruby 3.1.2 failed!
```

My quick reaction was to Google this error message. [Everyone](https://github.com/rbenv/ruby-build/discussions/1938) said that their issue were fixed by reinstall the Xcode CLT (Command Line Tool). Tried it, didn’t help; so I had to actually dig into the issue.

## Understanding the Ruby configuration

From the affected C code, `RUBY_FUNCTION_NAME_STRING` looks like a macro. I searched the source code for the macro, and found that it was defined [here](https://github.com/ruby/ruby/blob/4491bb740a9506d76391ac44bb2fe6e483fec952/configure.ac#L1684-L1701):

```
AC_CACHE_CHECK(for function name string predefined identifier,
    rb_cv_function_name_string,
    [AS_CASE(["$target_os"],[openbsd*],[
      rb_cv_function_name_string=__func__
     ],[
     rb_cv_function_name_string=no
      RUBY_WERROR_FLAG([
        for func in __func__ __FUNCTION__; do
            AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include <stdio.h>]],
					    [[puts($func);]])],
            [rb_cv_function_name_string=$func
            break])
        done
      ])])]
)
AS_IF([test "$rb_cv_function_name_string" != no], [
    AC_DEFINE_UNQUOTED(RUBY_FUNCTION_NAME_STRING, [$rb_cv_function_name_string])
])
```

This file (`configure.ac`) is a GNU Autoconf script. This section of the script sets the value of the macro:

 1. If compiling for OpenBSD, set it to `__func__`.
 2. Otherwise, check whether the compiler supports the magic `__func__`, by compiling a minimal C program with it. If it does, set the macro to `__func__`.
 3. Otherwise, do the same with `__FUNCTION__`.
 4. If none of the above works, then leave the macro unset.

Indeed the output of `ruby-install` (actually `./configure`) contains

> checking for function name string predefined identifier... no

So it did fall into the last branch, leaving the macro unset. I tried compiling the same piece of C code; my compiler do support `__func__`. My next thoughts were: anyway for me to force it skipping the detection and use `__func__` anyway?

`./configure --help` gives the answer: it supports `--enable-FLAG=VALUE` to override any feature detection. So I ran

```
./configure --enable-rb_cv_function_name_string=__func__
```

And indeed that line of log became

> checking for function name string predefined identifier... `__func__`

In addition, subsequent `make compiler.o` succeeded without complaint. So the workaround sounds simple: just pass this flag as a “configure option” to `ruby-install`, like

```sh
ruby-install ruby-3.1.2 -- --enable-rb_cv_function_name_string=__func__
```

right?

## Finding the next (real) broken thing

Nope, it didn’t work; the feature was set back to “no”. After scratching my head and reading the source code of `ruby-install`, I noticed that it supports a flag: `-D`/`--debug`, which prints the command it runs. I ran `ruby-install` again with that flag, and it printed

```
[DEBUG] ./configure --prefix=/Users/franklinyu/.rubies/ruby-3.1.2 --with-opt-dir=/Users/franklinyu/.local/opt/openssl@1.1:/Users/franklinyu/.local/opt/readline:/Users/franklinyu/.local/opt/libyaml:/Users/franklinyu/.local/opt/gdbm --enable-rb_cv_function_name_string=__func__
```

So it did have the `--enable-xxx` flags that I wanted; the `--prefix` looks legit. But the `--with-opt-dir` looks suspicious, because those paths didn’t actually exist on my system. I tried running this `./configure` command, and I could reproduce the problem of feature set to “no”. I tried running with only the `--prefix` flag, and the feature was set to `__func__` even without my `--enable-xxx` hack. So the root cause was actually the `--with-opt-dir`. Why was it set?

Reading the source code again, the relevant part is [this](https://github.com/postmodern/ruby-install/blob/638253aa325aa6a40e5546d37555d046e057158b/share/ruby-install/ruby/functions.sh#L30-L43):

```bash
case "$package_manager" in
    brew)
        opt_dir="$(brew --prefix readline):$(brew --prefix libyaml):$(brew --prefix gdbm)"
        openssl_dir="$(brew --prefix openssl@1.1)"
        ;;
    port)
        opt_dir="/opt/local"
        ;;
esac

run ./configure --prefix="$install_dir" \
        "${opt_dir:+--with-opt-dir="$opt_dir"}" \
        "${openssl_dir:+--with-openssl-dir="$openssl_dir"}" \
        "${configure_opts[@]}" || return $?
```

I didn’t set `--package_manager` for `ruby-install` because I did set `--no-install-deps`, and I thought it is sufficient. The problem was finally resolved by replacing `--no-install-deps` with `--package_manager port`. (Note: `ruby-install` doesn’t support the flag format `--key=val`.)

## Background

I use MacPorts to manage FOSS on my Mac. I switched from Homebrew to MacPorts because Homebrew doesn’t record whether a package is “explicitly installed” or pulled in as dependencies; also Homebrew doesn’t support “virtual packages”. However, MacPorts seems to be mainly (only?) for FOSS, and is not interested in distributing binaries compiled upstream. This is includes [Docker for Mac](https://docs.docker.com/desktop/install/mac-install/), [coconutBattery](https://www.coconut-flavour.com/coconutbattery/), Acrobat Reader, Vagrant, and VirtualBox. Managing them via Homebrew Cask allows clean uninstallation, which is important for me, so I’d keep them coexist for a while.