Project

General

Profile

Actions

Bug #14816

closed

Extension build failure on a system with musl libc

Added by akamch (Anatoly Kamchatnov) almost 6 years ago. Updated over 5 years ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 2.6.0preview2 (2018-05-31 trunk 63539) [x86_64-linux]
[ruby-core:87364]

Description

Some extensions fail to build on a Linux with musl (Void Linux). Build of unf_ext is an example.
isinf() and isnan() are defined as macros in musl: https://git.musl-libc.org/cgit/musl/tree/include/math.h
https://github.com/gliderlabs/docker-alpine/issues/261 "Cannot build native extensions for unf_ext gem" seems to be a related issue.

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

    current directory: /home/rev/.gem/ruby/2.6.0/gems/unf_ext-0.0.7.5/ext/unf_ext
/home/rev/.rbenv/versions/2.6.0-preview2/bin/ruby -r ./siteconf20180603-29655-wgzu56.rb extconf.rb
checking for -lstdc++... yes
creating Makefile

current directory: /home/rev/.gem/ruby/2.6.0/gems/unf_ext-0.0.7.5/ext/unf_ext
make "DESTDIR=" clean

current directory: /home/rev/.gem/ruby/2.6.0/gems/unf_ext-0.0.7.5/ext/unf_ext
make "DESTDIR="
compiling unf.cc
cc1plus: warning: command line option '-Wimplicit-int' is valid for C/ObjC but not for C++
cc1plus: warning: command line option '-Wdeclaration-after-statement' is valid for C/ObjC but not for C++
cc1plus: warning: command line option '-Wimplicit-function-declaration' is valid for C/ObjC but not for C++
In file included from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/defines.h:153:0,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/ruby.h:29,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby.h:33,
                 from unf.cc:3:
/home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/missing.h:172:29: error: 'int isinf(double)' conflicts with a previous declaration
 RUBY_EXTERN int isinf(double);
                             ^
In file included from /usr/include/c++/7.3/math.h:36:0,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/missing.h:23,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/defines.h:153,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/ruby.h:29,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby.h:33,
                 from unf.cc:3:
/usr/include/c++/7.3/cmath:599:3: note: previous declaration 'constexpr bool std::isinf(double)'
   isinf(double __x)
   ^~~~~
In file included from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/defines.h:153:0,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/ruby.h:29,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby.h:33,
                 from unf.cc:3:
/home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/missing.h:179:29: error: 'int isnan(double)' conflicts with a previous declaration
 RUBY_EXTERN int isnan(double);
                             ^
In file included from /usr/include/c++/7.3/math.h:36:0,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/missing.h:23,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/defines.h:153,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/ruby.h:29,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby.h:33,
                 from unf.cc:3:
/usr/include/c++/7.3/cmath:626:3: note: previous declaration 'constexpr bool std::isnan(double)'
   isnan(double __x)
   ^~~~~
cc1plus: warning: unrecognized command line option '-Wno-cast-function-type'
cc1plus: warning: unrecognized command line option '-Wno-self-assign'
cc1plus: warning: unrecognized command line option '-Wno-constant-logical-operand'
cc1plus: warning: unrecognized command line option '-Wno-parentheses-equality'
make: *** [Makefile:211: unf.o] Error 1

make failed, exit code 2

Updated by shyouhei (Shyouhei Urabe) almost 6 years ago

akamch (Anatoly Kamchatnov) wrote:

isinf() and isnan() are defined as macros in musl: https://git.musl-libc.org/cgit/musl/tree/include/math.h

Yes, and the corresponding missing.h lines are this:

#ifndef isnan
# ifndef HAVE_ISNAN
RUBY_EXTERN int isnan(double);
# endif
#endif

So the conflicting function is actually guarded by #ifdef. I guess this is not our fault?

Updated by akamch (Anatoly Kamchatnov) almost 6 years ago

I guess this is not our fault?

Not entirely. Most likely it's nobody's fault but you can always blame autoconf :) Alpine's author thinks that "the configure script does not detect isnan/isinf as macros, call ruby devs". Autoconf's doc says

isinf
isnan
    The C99 standard says that isinf and isnan are macros. On some systems just macros are available (e.g., HP-UX and Solaris 10), on some systems both macros and functions (e.g., glibc 2.3.2), and on some systems only functions (e.g., IRIX 6 and Solaris 9). In some cases these functions are declared in nonstandard headers like <sunmath.h> and defined in non-default libraries like -lm or -lsunmath.

    The C99 isinf and isnan macros work correctly with long double arguments, but pre-C99 systems that use functions typically assume double arguments. On such a system, isinf incorrectly returns true for a finite long double argument that is outside the range of double.

    The best workaround for these issues is to use gnulib modules isinf and isnan (see Gnulib). But a lighter weight solution involves code like the following.

              #include <math.h>
              
              #ifndef isnan
              # define isnan(x) \
                  (sizeof (x) == sizeof (long double) ? isnan_ld (x) \
                   : sizeof (x) == sizeof (double) ? isnan_d (x) \
                   : isnan_f (x))
              static inline int isnan_f  (float       x) { return x != x; }
              static inline int isnan_d  (double      x) { return x != x; }
              static inline int isnan_ld (long double x) { return x != x; }
              #endif
              
              #ifndef isinf
              # define isinf(x) \
                  (sizeof (x) == sizeof (long double) ? isinf_ld (x) \
                   : sizeof (x) == sizeof (double) ? isinf_d (x) \
                   : isinf_f (x))
              static inline int isinf_f  (float       x)
              { return !isnan (x) && isnan (x - x); }
              static inline int isinf_d  (double      x)
              { return !isnan (x) && isnan (x - x); }
              static inline int isinf_ld (long double x)
              { return !isnan (x) && isnan (x - x); }
              #endif

https://www.gnu.org/software/autoconf/manual/autoconf.html#Function-Portability

Looks like musl is not unlike HP-UX and Solaris 10 in that regard. The question is where one should fix this: some header or ruby routines around extconf.rb, gem in question, anywhere else? That I don't know, but the first option still seems plausible to me.

Updated by shevegen (Robert A. Heiler) almost 6 years ago

I think ngoto is knowing quite a bit about Solaris so perhaps if he has
time he could comment.

I also understand you wanting to use Ruby even on exotic combinations
like musl + void. The original rack author also uses void. :)

Updated by akamch (Anatoly Kamchatnov) almost 6 years ago

I also understand you wanting to use Ruby even on exotic combinations like musl + void.

Indeed, yet one can also easily envision many a failed build of Ruby codebases inside the musl-only Alpine-based Docker containers. Void is almost irrelevant here.

The original rack author also uses void. :)

And does hell of a job maintaining that fine distribution.

Updated by shyouhei (Shyouhei Urabe) almost 6 years ago

akamch (Anatoly Kamchatnov) wrote:

I guess this is not our fault?

Not entirely. Most likely it's nobody's fault but you can always blame autoconf

Highly skeptical. Can you build ruby from source and show us your config.log then?

Like I showed before the RUBY_EXTERN int isnan(double); line is effective only if (1) isnan is not a macro, and (2) isnan is not provided as a function. "The best workaround" that the autoconf says does not work on your system because in case it would, ours must also.

Updated by shyouhei (Shyouhei Urabe) almost 6 years ago

Let me directly point out what is actually to be blamed:

akamch (Anatoly Kamchatnov) wrote:

In file included from /usr/include/c++/7.3/math.h:36:0,
from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/missing.h:23,
from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/defines.h:153,
from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/ruby.h:29,
from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby.h:33,
from unf.cc:3:
/usr/include/c++/7.3/cmath:626:3: note: previous declaration 'constexpr bool std::isnan(double)'
isnan(double __x)
^~~~~

This is wrong. Your C++ header file does define a function named isnan. You have to include C's one (the one you referred) instead.

Updated by shyouhei (Shyouhei Urabe) almost 6 years ago

Proposed workaround, not tested though.

From 01839b509c1bb914337124ac3d1f644b05ef90d8 Mon Sep 17 00:00:00 2001
From: "Urabe, Shyouhei" <shyouhei@ruby-lang.org>
Date: Tue, 5 Jun 2018 10:26:06 +0900
Subject: [PATCH] C++11 is so bad it introduces a nightmare.

TL;DR see https://developers.redhat.com/blog/2016/02/29/why-cstdlib-is-more-complicated-than-you-might-think/

 - `isnan` is something relatively new.  We need to provide one for
   those systems without it.  However:
 - X/Open defines `int isnan(double)`. Note the `int`.
 - C99 defines `isnan(x)` to be a macro.
 - C++11 nukes them all, undefines all the "masking macro"s, and
   define its own `bool isnan(double)`.  Note the `bool`.
 - In C++, `int isnan(double)` and `bool isnan(double)` are
   incompatible.
 - So the mess.

Signed-off-by: Urabe, Shyouhei <shyouhei@ruby-lang.org>
---
 include/ruby/missing.h | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/include/ruby/missing.h b/include/ruby/missing.h
index dc3fd502b5..8df917498e 100644
--- a/include/ruby/missing.h
+++ b/include/ruby/missing.h
@@ -168,6 +168,8 @@ RUBY_EXTERN const union bytesequence4_or_float rb_nan;
 #    include <ieeefp.h>
 #    endif
 #  define isinf(x) (!finite(x) && !isnan(x))
+#  elsif __cplusplus >= 201103L
+#    include <cmath> // it must include constexpr bool isinf(double);
 #  else
 RUBY_EXTERN int isinf(double);
 #  endif
@@ -176,7 +178,11 @@ RUBY_EXTERN int isinf(double);
 
 #ifndef isnan
 # ifndef HAVE_ISNAN
+#  if __cplusplus >= 201103L
+#    include <cmath> // it must include constexpr bool isnan(double);
+#  else
 RUBY_EXTERN int isnan(double);
+#  endif
 # endif
 #endif
 
-- 
2.17.1

Updated by akamch (Anatoly Kamchatnov) almost 6 years ago

shyouhei (Shyouhei Urabe) wrote:

Let me directly point out what is actually to be blamed

Works best for me! Thank you very much for getting to the root of the problem, there's much to reflect upon.

The patch does away with the isnan error, but the other one still remains:

compiling unf.cc
cc1plus: warning: command line option '-Wimplicit-int' is valid for C/ObjC but not for C++
cc1plus: warning: command line option '-Wdeclaration-after-statement' is valid for C/ObjC but not for C++
cc1plus: warning: command line option '-Wimplicit-function-declaration' is valid for C/ObjC but not for C++
In file included from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/defines.h:153:0,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/ruby.h:29,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby.h:33,
                 from unf.cc:3:
/home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/missing.h:174:29: error: 'int isinf(double)' conflicts with a previous declaration
 RUBY_EXTERN int isinf(double);
                             ^
In file included from /usr/include/c++/7.3/math.h:36:0,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/missing.h:23,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/defines.h:153,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby/ruby.h:29,
                 from /home/rev/.rbenv/versions/2.6.0-preview2/include/ruby-2.6.0/ruby.h:33,
                 from unf.cc:3:
/usr/include/c++/7.3/cmath:599:3: note: previous declaration 'constexpr bool std::isinf(double)'
   isinf(double __x)
   ^~~~~
cc1plus: warning: unrecognized command line option '-Wno-cast-function-type'
cc1plus: warning: unrecognized command line option '-Wno-self-assign'
cc1plus: warning: unrecognized command line option '-Wno-constant-logical-operand'
cc1plus: warning: unrecognized command line option '-Wno-parentheses-equality'
make: *** [Makefile:211: unf.o] Error 1

make failed, exit code 2

Updated by shyouhei (Shyouhei Urabe) almost 6 years ago

akamch (Anatoly Kamchatnov) wrote:

The patch does away with the isnan error, but the other one still remains:

Ah sorry. Embarrassing typo :(. Try applying this patch over the previous one.

diff --git a/include/ruby/missing.h b/include/ruby/missing.h
index 8df917498e..2d3852c131 100644
--- a/include/ruby/missing.h
+++ b/include/ruby/missing.h
@@ -168,7 +168,7 @@ RUBY_EXTERN const union bytesequence4_or_float rb_nan;
 #    include <ieeefp.h>
 #    endif
 #  define isinf(x) (!finite(x) && !isnan(x))
-#  elsif __cplusplus >= 201103L
+#  elif __cplusplus >= 201103L
 #    include <cmath> // it must include constexpr bool isinf(double);
 #  else
 RUBY_EXTERN int isinf(double);

Updated by akamch (Anatoly Kamchatnov) almost 6 years ago

Works great, many thanks, Shyouhei!

The wider question on what could/should have been done to avoid these ifdef dances is definitely not Ruby's to answer.

Actions #11

Updated by shyouhei (Shyouhei Urabe) almost 6 years ago

  • Status changed from Open to Closed

Applied in changeset trunk|r63571.


int isnan(double) is a POSIXism

  • isnan is something relatively new. We need to provide one for
    those systems without it. However:
  • X/Open defines int isnan(double). Note the int.
  • C99 defines isnan(x) to be a macro.
  • C++11 nukes them all, undefines all the "masking macro"s, and
    defines its own bool isnan(double). Note the bool.
  • In C++, int isnan(double) and bool isnan(double) are
    incompatible.
  • So the mess.

[Bug #14816][ruby-core:87364]
further reading: https://developers.redhat.com/blog/2016/02/29/why-cstdlib-is-more-complicated-than-you-might-think/

Actions #12

Updated by shyouhei (Shyouhei Urabe) almost 6 years ago

  • Backport changed from 2.3: UNKNOWN, 2.4: UNKNOWN, 2.5: UNKNOWN to 2.3: REQUIRED, 2.4: REQUIRED, 2.5: REQUIRED

Updated by usa (Usaku NAKAMURA) over 5 years ago

  • Backport changed from 2.3: REQUIRED, 2.4: REQUIRED, 2.5: REQUIRED to 2.3: REQUIRED, 2.4: DONE, 2.5: REQUIRED

ruby_2_4 r64126 merged revision(s) 63571,63572.

Updated by nagachika (Tomoyuki Chikanaga) over 5 years ago

  • Backport changed from 2.3: REQUIRED, 2.4: DONE, 2.5: REQUIRED to 2.3: REQUIRED, 2.4: DONE, 2.5: DONE

ruby_2_5 r64434 merged revision(s) 63571,63572.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0