appsignal/appsignal-ruby

Extension linking does (not) work with Xcode 14 (macOS 12.6)

tombruijn opened this issue · 3 comments

The linker shipped with XCode Version 14.0 (14A309) currently does not break linking for Ruby apps on macOS 12.6.

# In this repo's root:
$ rake extension:install
checking for appsignal_start() in -lappsignal... yes
checking for appsignal-agent in /Users/tombruijn/appsignal/ruby/ext... yes
creating Makefile
compiling appsignal_extension.c
linking shared-object appsignal_extension.bundle
ld: warning: -undefined dynamic_lookup may not work with chained fixups

Xcode Version 13.4.1 (13F100), from macOS 12.5

$ man ld

     -undefined treatment
             Specifies how undefined symbols are to be treated. Options are: error, warning, suppress, or dynamic_lookup.  The default
             is error.

You can also have this older version of Xcode installed on macOS 12.6.

New Xcode Version 14.0 (14A309), from macOS 12.6

$ man ld

     -undefined treatment
             Specifies how undefined symbols are to be treated. Options are: error, warning, suppress, or dynamic_lookup.  The default is error. Note:
             dynamic_lookup that depends on lazy binding will not work with chained fixups.

See how it says "dynamic_lookup that depends on lazy binding will not work with chained fixups", the thing the new error is about.

"Workarounds"

Unnecessary for now, but may be useful for future macOS/Xcode updates

Related issues

We fixed this same issue for Elixir in this PR that explicitly tells the linker the symbols are undefined and set at runtime. We can't exactly use the same fix because we don't have a Makefile in the Ruby gem. It's generated by Ruby based on the ext/extconf.rb file.

Our Ruby gem doesn't set -Wl,-fatal_warnings like we do in our Elixir package. That means it doesn't fail to install the Ruby extension, just print a bunch of warnings. Especially when dynamic_lookup is removed.

This will become an issue when Apple removes dynamic_lookup behavior in what I assume is a future Xcode update. That's what the warning is for right? The "chained fixups" seem to be the linker default now.

Ruby sets the DLDFLAGS linker option -Wl,-undefined,dynamic_lookup upon require "mkmf". This may change in Ruby issue 19005 and future Ruby releases.

Going to put this issue on hold for now. Let us know on this issue or support@appsignal.com if you do run into this issue:

A workaround to this issue, based on commit 0a1ad80, is:
(I won't apply this workaround, because it seems to continue to work for now.)

diff --git ext/extconf.rb ext/extconf.rb
index 87696f8a..16566131 100644
--- ext/extconf.rb
+++ ext/extconf.rb
@@ -44,6 +44,34 @@ def install
       # to fail on start.
       $LDFLAGS += " -static-libgcc" # rubocop:disable Style/GlobalVars
       report["build"]["flags"]["LDFLAGS"] = $LDFLAGS # rubocop:disable Style/GlobalVars
+    elsif AGENT_PLATFORM == "darwin"
+      functions = %w[
+        _rb_add_event_hook
+        _rb_cObject
+        _rb_data_object_wrap
+        _rb_debug_rstring_null_ptr
+        _rb_define_class_under
+        _rb_define_method
+        _rb_define_module
+        _rb_define_singleton_method
+        _rb_eTypeError
+        _rb_enc_associate
+        _rb_fix2int
+        _rb_num2dbl
+        _rb_num2long
+        _rb_obj_classname
+        _rb_raise
+        _rb_str_new
+        _rb_unexpected_type
+        _rb_utf8_encoding
+      ].map do |function|
+        "-Wl,-U,#{function}"
+      end
+      # rubocop:disable Style/GlobalVars
+      $DLDFLAGS.slice! "-Wl,-undefined,dynamic_lookup"
+      $DLDFLAGS += " -undefined error #{functions.join(" ")}"
+      report["build"]["flags"]["DLDFLAGS"] = $DLDFLAGS
+      # rubocop:enable Style/GlobalVars
     end
     create_makefile "appsignal_extension"
     successful_installation

Alternative workaround with a symbols file:

diff --git ext/extconf.rb ext/extconf.rb
index 87696f8a..e0aab288 100644
--- ext/extconf.rb
+++ ext/extconf.rb
@@ -44,6 +44,12 @@ def install
       # to fail on start.
       $LDFLAGS += " -static-libgcc" # rubocop:disable Style/GlobalVars
       report["build"]["flags"]["LDFLAGS"] = $LDFLAGS # rubocop:disable Style/GlobalVars
+    elsif AGENT_PLATFORM == "darwin"
+      # rubocop:disable Style/GlobalVars
+      $DLDFLAGS.slice! "-Wl,-undefined,dynamic_lookup"
+      $DLDFLAGS += " -Wl,-undefined error -Wl,-exported_symbols_list #{File.join(__dir__, "symbols.sym")}"
+      report["build"]["flags"]["DLDFLAGS"] = $DLDFLAGS
+      # rubocop:enable Style/GlobalVars
     end
     create_makefile "appsignal_extension"
     successful_installation

diff --git ext/symbols.sym ext/symbols.sym
index e69de29b..e448e69d 100644
--- ext/symbols.sym
+++ ext/symbols.sym
@@ -0,0 +1,18 @@
+_rb_add_event_hook
+_rb_cObject
+_rb_data_object_wrap
+_rb_debug_rstring_null_ptr
+_rb_define_class_under
+_rb_define_method
+_rb_define_module
+_rb_define_singleton_method
+_rb_eTypeError
+_rb_enc_associate
+_rb_fix2int
+_rb_num2dbl
+_rb_num2long
+_rb_obj_classname
+_rb_raise
+_rb_str_new
+_rb_unexpected_type
+_rb_utf8_encoding

FYI.. https://issues.guix.gnu.org/issue/57849

Looks like using -Wl,-w will help.

Thanks for the suggestion! I see that -w will "Suppress all warning messages". I'd rather see the warning message, in case something breaks in a future macOS version.
People that install the AppSignal gem already don't see this warning, because it's wrapped and hidden by the Ruby gems installer. We're the only ones seeing this warning now when we run the rake extension:install task.