From 6b3bf9b813bab646d17ed10767434a916ba4c11e Mon Sep 17 00:00:00 2001 From: Marco Costa Date: Mon, 24 Nov 2025 15:54:19 -0800 Subject: [PATCH] Create shared sources directory for `ext/` --- .../extconf.rb | 5 ++ ext/libdatadog_api/datadog_ruby_common.c | 80 ------------------- ext/libdatadog_api/datadog_ruby_common.h | 63 --------------- ext/libdatadog_api/extconf.rb | 5 ++ ext/libdatadog_extconf_helpers.rb | 20 +++++ .../datadog_ruby_common.c | 0 .../datadog_ruby_common.h | 0 spec/datadog/core/datadog_ruby_common_spec.rb | 17 ---- 8 files changed, 30 insertions(+), 160 deletions(-) delete mode 100644 ext/libdatadog_api/datadog_ruby_common.c delete mode 100644 ext/libdatadog_api/datadog_ruby_common.h rename ext/{datadog_profiling_native_extension => shared}/datadog_ruby_common.c (100%) rename ext/{datadog_profiling_native_extension => shared}/datadog_ruby_common.h (100%) delete mode 100644 spec/datadog/core/datadog_ruby_common_spec.rb diff --git a/ext/datadog_profiling_native_extension/extconf.rb b/ext/datadog_profiling_native_extension/extconf.rb index 35f0210405d..524ed0ba0ce 100644 --- a/ext/datadog_profiling_native_extension/extconf.rb +++ b/ext/datadog_profiling_native_extension/extconf.rb @@ -70,6 +70,11 @@ def skip_building_extension!(reason) # that may fail on an environment not properly setup for building Ruby extensions. require "mkmf" +Datadog::LibdatadogExtconfHelpers.add_shared_sources!( + extension_dir: __dir__, + shared_relative_dir: File.join("..", "shared") +) + Logging.message("[datadog] Using compiler:\n") xsystem("#{CONFIG["CC"]} -v") Logging.message("[datadog] End of compiler information\n") diff --git a/ext/libdatadog_api/datadog_ruby_common.c b/ext/libdatadog_api/datadog_ruby_common.c deleted file mode 100644 index ca402dca264..00000000000 --- a/ext/libdatadog_api/datadog_ruby_common.c +++ /dev/null @@ -1,80 +0,0 @@ -#include "datadog_ruby_common.h" - -// IMPORTANT: Currently this file is copy-pasted between extensions. Make sure to update all versions when doing any change! - -void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char* function_name) { - rb_exc_raise( - rb_exc_new_str( - rb_eTypeError, - rb_sprintf("wrong argument %"PRIsVALUE" for '%s' (expected a %s) at %s:%d:in `%s'", - rb_inspect(value), - value_name, - type_name, - file, - line, - function_name - ) - ) - ); -} - -VALUE datadog_gem_version(void) { - VALUE ddtrace_module = rb_const_get(rb_cObject, rb_intern("Datadog")); - ENFORCE_TYPE(ddtrace_module, T_MODULE); - VALUE version_module = rb_const_get(ddtrace_module, rb_intern("VERSION")); - ENFORCE_TYPE(version_module, T_MODULE); - VALUE version_string = rb_const_get(version_module, rb_intern("STRING")); - ENFORCE_TYPE(version_string, T_STRING); - return version_string; -} - -static VALUE log_failure_to_process_tag(VALUE err_details) { - return log_warning(rb_sprintf("Failed to convert tag: %"PRIsVALUE, err_details)); -} - -__attribute__((warn_unused_result)) -ddog_Vec_Tag convert_tags(VALUE tags_as_array) { - ENFORCE_TYPE(tags_as_array, T_ARRAY); - - long tags_count = RARRAY_LEN(tags_as_array); - ddog_Vec_Tag tags = ddog_Vec_Tag_new(); - - for (long i = 0; i < tags_count; i++) { - VALUE name_value_pair = rb_ary_entry(tags_as_array, i); - - if (!RB_TYPE_P(name_value_pair, T_ARRAY)) { - ddog_Vec_Tag_drop(tags); - ENFORCE_TYPE(name_value_pair, T_ARRAY); - } - - // Note: We can index the array without checking its size first because rb_ary_entry returns Qnil if out of bounds - VALUE tag_name = rb_ary_entry(name_value_pair, 0); - VALUE tag_value = rb_ary_entry(name_value_pair, 1); - - if (!(RB_TYPE_P(tag_name, T_STRING) && RB_TYPE_P(tag_value, T_STRING))) { - ddog_Vec_Tag_drop(tags); - ENFORCE_TYPE(tag_name, T_STRING); - ENFORCE_TYPE(tag_value, T_STRING); - } - - ddog_Vec_Tag_PushResult push_result = - ddog_Vec_Tag_push(&tags, char_slice_from_ruby_string(tag_name), char_slice_from_ruby_string(tag_value)); - - if (push_result.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) { - // libdatadog validates tags and may catch invalid tags that ddtrace didn't actually catch. - // We warn users about such tags, and then just ignore them. - - int exception_state; - rb_protect(log_failure_to_process_tag, get_error_details_and_drop(&push_result.err), &exception_state); - - // Since we are calling into Ruby code, it may raise an exception. Ensure that dynamically-allocated tags - // get cleaned before propagating the exception. - if (exception_state) { - ddog_Vec_Tag_drop(tags); - rb_jump_tag(exception_state); // "Re-raise" exception - } - } - } - - return tags; -} diff --git a/ext/libdatadog_api/datadog_ruby_common.h b/ext/libdatadog_api/datadog_ruby_common.h deleted file mode 100644 index d55c2bb479f..00000000000 --- a/ext/libdatadog_api/datadog_ruby_common.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -// IMPORTANT: Currently this file is copy-pasted between extensions. Make sure to update all versions when doing any change! - -#include -#include - -// Used to mark symbols to be exported to the outside of the extension. -// Consider very carefully before tagging a function with this. -#define DDTRACE_EXPORT __attribute__ ((visibility ("default"))) - -// Used to mark function arguments that are deliberately left unused -#ifdef __GNUC__ - #define DDTRACE_UNUSED __attribute__((unused)) -#else - #define DDTRACE_UNUSED -#endif - -#define ADD_QUOTES_HELPER(x) #x -#define ADD_QUOTES(x) ADD_QUOTES_HELPER(x) - -// Ruby has a Check_Type(value, type) that is roughly equivalent to this BUT Ruby's version is rather cryptic when it fails -// e.g. "wrong argument type nil (expected String)". This is a replacement that prints more information to help debugging. -#define ENFORCE_TYPE(value, type) \ - { if (RB_UNLIKELY(!RB_TYPE_P(value, type))) raise_unexpected_type(value, ADD_QUOTES(value), ADD_QUOTES(type), __FILE__, __LINE__, __func__); } - -#define ENFORCE_BOOLEAN(value) \ - { if (RB_UNLIKELY(value != Qtrue && value != Qfalse)) raise_unexpected_type(value, ADD_QUOTES(value), "true or false", __FILE__, __LINE__, __func__); } - -#define ENFORCE_TYPED_DATA(value, type) \ - { if (RB_UNLIKELY(!rb_typeddata_is_kind_of(value, type))) raise_unexpected_type(value, ADD_QUOTES(value), "TypedData of type " ADD_QUOTES(type), __FILE__, __LINE__, __func__); } - -NORETURN(void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char* function_name)); - -// Helper to retrieve Datadog::VERSION::STRING -VALUE datadog_gem_version(void); - -static inline ddog_CharSlice char_slice_from_ruby_string(VALUE string) { - ENFORCE_TYPE(string, T_STRING); - ddog_CharSlice char_slice = {.ptr = RSTRING_PTR(string), .len = RSTRING_LEN(string)}; - return char_slice; -} - -static inline VALUE log_warning(VALUE warning) { - VALUE datadog_module = rb_const_get(rb_cObject, rb_intern("Datadog")); - VALUE logger = rb_funcall(datadog_module, rb_intern("logger"), 0); - - return rb_funcall(logger, rb_intern("warn"), 1, warning); -} - -__attribute__((warn_unused_result)) -ddog_Vec_Tag convert_tags(VALUE tags_as_array); - -static inline VALUE ruby_string_from_error(const ddog_Error *error) { - ddog_CharSlice char_slice = ddog_Error_message(error); - return rb_str_new(char_slice.ptr, char_slice.len); -} - -static inline VALUE get_error_details_and_drop(ddog_Error *error) { - VALUE result = ruby_string_from_error(error); - ddog_Error_drop(error); - return result; -} diff --git a/ext/libdatadog_api/extconf.rb b/ext/libdatadog_api/extconf.rb index 41549a8ea8c..4b40cdad9b1 100644 --- a/ext/libdatadog_api/extconf.rb +++ b/ext/libdatadog_api/extconf.rb @@ -32,6 +32,11 @@ def skip_building_extension!(reason) require 'mkmf' +Datadog::LibdatadogExtconfHelpers.add_shared_sources!( + extension_dir: __dir__, + shared_relative_dir: File.join('..', 'shared') +) + # Because we can't control what compiler versions our customers use, shipping with -Werror by default is a no-go. # But we can enable it in CI, so that we quickly spot any new warnings that just got introduced. append_cflags '-Werror' if ENV['DATADOG_GEM_CI'] == 'true' diff --git a/ext/libdatadog_extconf_helpers.rb b/ext/libdatadog_extconf_helpers.rb index addc3aadbdc..0cf4025e262 100644 --- a/ext/libdatadog_extconf_helpers.rb +++ b/ext/libdatadog_extconf_helpers.rb @@ -12,6 +12,26 @@ module LibdatadogExtconfHelpers # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story. LIBDATADOG_VERSION = '~> 24.0.1.1.0' + # Include sources in the `shared` directory. We intentionally: + # * Keep every entry in $srcs to the basename so mkmf/VPATH can resolve them correctly. + # * Add the shared directory to $VPATH using $(srcdir) so the generated Makefile works no matter where it's run from. + # * Extend $INCFLAGS with the same path so headers resolve without absolute paths that would break relocation. + def self.add_shared_sources!(extension_dir:, shared_relative_dir:) + shared_absolute_dir = File.expand_path(shared_relative_dir, extension_dir) + shared_sources = Dir[File.join(shared_absolute_dir, '*.c')].map { |path| File.basename(path) } + extension_sources = Dir[File.join(extension_dir, '*.c')].map { |path| File.basename(path) } + + $srcs = extension_sources + shared_sources + $VPATH ||= [] + + shared_search_dir = File.join('$(srcdir)', shared_relative_dir) + $VPATH << shared_search_dir unless $VPATH.include?(shared_search_dir) + + $INCFLAGS ||= '' + include_token = " -I#{shared_search_dir}" + $INCFLAGS << include_token unless $INCFLAGS.include?(include_token) + end + # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and # libdatadog are moved after the extension gets compiled. # diff --git a/ext/datadog_profiling_native_extension/datadog_ruby_common.c b/ext/shared/datadog_ruby_common.c similarity index 100% rename from ext/datadog_profiling_native_extension/datadog_ruby_common.c rename to ext/shared/datadog_ruby_common.c diff --git a/ext/datadog_profiling_native_extension/datadog_ruby_common.h b/ext/shared/datadog_ruby_common.h similarity index 100% rename from ext/datadog_profiling_native_extension/datadog_ruby_common.h rename to ext/shared/datadog_ruby_common.h diff --git a/spec/datadog/core/datadog_ruby_common_spec.rb b/spec/datadog/core/datadog_ruby_common_spec.rb deleted file mode 100644 index 17c3e973135..00000000000 --- a/spec/datadog/core/datadog_ruby_common_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe 'datadog_ruby_common helpers' do - let(:profiling_folder) { File.join(__dir__, '../../../ext/datadog_profiling_native_extension') } - let(:libdatadog_api_folder) { File.join(__dir__, '../../../ext/libdatadog_api') } - - ['datadog_ruby_common.c', 'datadog_ruby_common.h'].each do |filename| - describe filename do - it 'is identical between profiling and libdatadog_api' do - profiling_content = File.read(File.join(profiling_folder, filename)) - libdatadog_api_content = File.read(File.join(libdatadog_api_folder, filename)) - - expect(profiling_content).to eq(libdatadog_api_content), "#{filename} files are not identical" - end - end - end -end