From 67f137e201d2ddb0f5932a633c128044584ec3b3 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Sun, 17 May 2026 17:33:26 +0900 Subject: [PATCH] gem.rb: hal_pattern for external HAL provider override A gem can declare spec.hal_pattern = /\Ahal-.*-task\z/ to indicate that another gem whose name matches the pattern (and which depends on this gem for headers) replaces the built-in ports// HAL implementation. After all gems are set up, List#resolve_external_hal! drops the target's ports/* objs from its object list so the matching gem supplies the HAL symbols. Two or more matches is reported as a build error. This restores the pre-be6413f0d8 ability to maintain an out-of-tree HAL via add_dependency + naming convention, without reintroducing the "HAL information scattered across gems" problem: the parent gem still owns the scheduler, headers, and bundled posix/win ports; external HAL gems are an explicit, opt-in override. Declare /\Ahal-.*-task\z/ for mruby-task -- the same naming pattern used before be6413f0d8. ref #6825 Co-authored-by: Claude --- lib/mruby/gem.rb | 31 +++++++++++++++++++++++++++++-- mrbgems/mruby-task/mrbgem.rake | 4 ++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/mruby/gem.rb b/lib/mruby/gem.rb index 4ad15da60..8794763bc 100644 --- a/lib/mruby/gem.rb +++ b/lib/mruby/gem.rb @@ -25,6 +25,8 @@ module MRuby alias :author= :authors= attr_accessor :rbfiles, :objs + attr_reader :port_objs + attr_accessor :hal_pattern attr_writer :test_objs, :test_rbfiles attr_accessor :test_args, :test_preload @@ -63,11 +65,15 @@ module MRuby # Add platform-specific sources from the first matching # ports// directory. effective_ports is a fallback # chain: later names act as defaults for gems that don't ship - # a port for the earlier names. + # a port for the earlier names. These objs are tracked + # separately so List#resolve_external_hal! can drop them when + # an external HAL provider matching spec.hal_pattern is loaded. + @port_objs = [] build.effective_ports.each do |port| port_dir = "#{@dir}/ports/#{port}" if File.directory?(port_dir) - @objs += srcs_to_objs("ports/#{port}") + @port_objs = srcs_to_objs("ports/#{port}") + @objs += @port_objs break end end @@ -457,6 +463,27 @@ module MRuby self.each(&:setup) gemset = self.setup_dependencies(build).keys.sort end until gemset == gemset_prev + resolve_external_hal! + end + + # If a gem declares `spec.hal_pattern = /regex/`, any other gem + # whose name matches the regex is treated as the external HAL + # provider for that gem -- the target gem's ports/* sources are + # dropped from its object list (the matching gem itself supplies + # the implementation). Two or more matches is a build error. + def resolve_external_hal! + each do |target| + next unless target.hal_pattern + overriders = select { |g| g != target && g.name =~ target.hal_pattern } + next if overriders.empty? + if overriders.size > 1 + fail "Multiple gems match #{target.hal_pattern.inspect} as " \ + "HAL provider for '#{target.name}': " + + overriders.map(&:name).join(", ") + end + next if target.port_objs.nil? || target.port_objs.empty? + target.objs.reject! { |o| target.port_objs.include?(o) } + end end def setup_build diff --git a/mrbgems/mruby-task/mrbgem.rake b/mrbgems/mruby-task/mrbgem.rake index e753b3b41..681bd47f7 100644 --- a/mrbgems/mruby-task/mrbgem.rake +++ b/mrbgems/mruby-task/mrbgem.rake @@ -6,6 +6,10 @@ MRuby::Gem::Specification.new('mruby-task') do |spec| # Enable task scheduler globally (required for vm.c integration) spec.build.defines << 'MRB_USE_TASK_SCHEDULER' + # A gem named hal--task (declaring add_dependency 'mruby-task') + # replaces the built-in ports// HAL implementation. + spec.hal_pattern = /\Ahal-.*-task\z/ + if spec.for_windows? spec.linker.libraries << "winmm" end