Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PROF-9926] Fix rpath for linking to libdatadog when loading from extension dir (cherry-pick from 1.x-stable) #3706

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions ext/datadog_profiling_native_extension/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,14 @@ def add_compiler_flag(flag)
skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported::COMPILER_ATOMIC_MISSING)
end

# See comments on the helper method being used for why we need to additionally set this.
# See comments on the helper methods being used for why we need to additionally set this.
# The extremely excessive escaping around ORIGIN below seems to be correct and was determined after a lot of
# experimentation. We need to get these special characters across a lot of tools untouched...
$LDFLAGS += \
' -Wl,-rpath,$$$\\\\{ORIGIN\\}/' \
"#{Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_native_lib_folder}"
extra_relative_rpaths = [
Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_native_lib_folder,
*Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_ruby_extensions_folders,
]
extra_relative_rpaths.each { |folder| $LDFLAGS += " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder.to_str}" }
Logging.message("[datadog] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")

# Tag the native extension library with the Ruby version and Ruby platform.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,52 @@ def self.libdatadog_folder_relative_to_native_lib_folder(
Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(profiling_native_lib_folder)).to_s
end

# In https://github.com/DataDog/dd-trace-rb/pull/3582 we got a report of a customer for which the native extension
# only got installed into the extensions folder.
#
# But then this fix was not enough to fully get them moving because then they started to see the issue from
# https://github.com/DataDog/dd-trace-rb/issues/2067 / https://github.com/DataDog/dd-trace-rb/pull/2125 :
#
# > Profiling was requested but is not supported, profiling disabled: There was an error loading the profiling
# > native extension due to 'RuntimeError Failure to load datadog_profiling_native_extension.3.2.2_x86_64-linux
# > due to libdatadog_profiling.so: cannot open shared object file: No such file or directory
#
# The problem is that when loading the native extension from the extensions directory, the relative rpath we add
# with the #libdatadog_folder_relative_to_native_lib_folder helper above is not correct, we need to add a relative
# rpath to the extensions directory.
#
# So how do we find the full path where the native extension is placed?
# * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/bundler/runtime.rb#L166
# `extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"] + Dir["#{Gem.dir}/bundler/gems/extensions/*/*/*"]`
# we get that's in one of two fixed subdirectories of `Gem.dir`
# * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/rubygems/basic_specification.rb#L111-L115
# we get the structure of the subdirectory (platform/extension_api_version/gem_and_version)
#
# Thus, `Gem.dir` of `/var/app/current/vendor/bundle/ruby/3.2.0` becomes (for instance)
# `/var/app/current/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/datadog-2.0.0/` or
# `/var/app/current/vendor/bundle/ruby/3.2.0/bundler/gems/extensions/x86_64-linux/3.2.0/datadog-2.0.0/`
#
# We then compute the relative path between these folders and the libdatadog folder, and use that as a relative path.
def self.libdatadog_folder_relative_to_ruby_extensions_folders(
gem_dir: Gem.dir,
libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
)
return unless libdatadog_pkgconfig_folder

# For the purposes of calculating a folder relative to the other, we don't actually NEED to fill in the
# platform, extension_api_version and gem version. We're basically just after how many folders it is deep from
# the Gem.dir.
expected_ruby_extensions_folders = [
"#{gem_dir}/extensions/platform/extension_api_version/datadog_version/",
"#{gem_dir}/bundler/gems/extensions/platform/extension_api_version/datadog_version/",
]
libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"

expected_ruby_extensions_folders.map do |folder|
Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(folder)).to_s
end
end

# Used to check if profiler is supported, including user-visible clear messages explaining why their
# system may not be supported.
module Supported
Expand Down
41 changes: 41 additions & 0 deletions spec/datadog/profiling/native_extension_helpers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,47 @@
end
end

describe '.libdatadog_folder_relative_to_ruby_extensions_folders' do
context 'when libdatadog is available' do
before do
skip_if_profiling_not_supported(self)
if PlatformHelpers.mac? && Libdatadog.pkgconfig_folder.nil? && ENV['LIBDATADOG_VENDOR_OVERRIDE'].nil?
raise 'You have a libdatadog setup without macOS support. Did you forget to set LIBDATADOG_VENDOR_OVERRIDE?'
end
end

it 'returns a relative path to libdatadog folder from the ruby extensions folders' do
extensions_relative, bundler_extensions_relative =
described_class.libdatadog_folder_relative_to_ruby_extensions_folders

libdatadog_extension = RbConfig::CONFIG['SOEXT'] || raise('Missing SOEXT for current platform')
libdatadog = "libdatadog_profiling.#{libdatadog_extension}"

expect(extensions_relative).to start_with('../')
expect(bundler_extensions_relative).to start_with('../')

extensions_full =
"#{Gem.dir}/extensions/platform/extension_api_version/datadog_version/#{extensions_relative}/#{libdatadog}"
bundler_extensions_full =
"#{Gem.dir}/bundler/gems/extensions/platform/extension_api_version/datadog_version/" \
"#{bundler_extensions_relative}/#{libdatadog}"

expect(File.exist?(Pathname.new(extensions_full).cleanpath.to_s))
.to be(true), "Libdatadog not available in expected path: #{extensions_full.inspect}"
expect(File.exist?(Pathname.new(bundler_extensions_full).cleanpath.to_s))
.to be(true), "Libdatadog not available in expected path: #{bundler_extensions_full.inspect}"
end
end

context 'when libdatadog is unsupported' do
it do
expect(
described_class.libdatadog_folder_relative_to_ruby_extensions_folders(libdatadog_pkgconfig_folder: nil)
).to be nil
end
end
end

describe '::LIBDATADOG_VERSION' do
it 'must match the version restriction set on the gemspec' do
# This test is expected to break when the libdatadog version on the .gemspec is updated but we forget to update
Expand Down
Loading