From 004fe0b142f168560e9b103fe38ac9ffcc115a60 Mon Sep 17 00:00:00 2001 From: dearblue Date: Thu, 11 Sep 2025 22:53:03 +0900 Subject: [PATCH 1/2] Set up all GEMS before mruby core tasks definition Until now, GEMs dependent on GEMs described in the build configuration file were loaded and set up after mruby core tasks were defined. This caused an issue where, if C++ exceptions were enabled later by a dependent GEM, the necessary tasks for mruby core were not defined. fixed https://github.com/mruby/mruby/issues/6615 --- Rakefile | 5 +++++ lib/mruby/gem.rb | 17 ++++++++++++++++- tasks/mrbgems.rake | 2 -- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index 4422f9688..5d8ba4945 100644 --- a/Rakefile +++ b/Rakefile @@ -17,6 +17,11 @@ require "mruby/build" MRUBY_CONFIG = MRuby::Build.mruby_config_path load MRUBY_CONFIG +# set up all gems +MRuby.each_target do + gems.setup(self) if enable_gems? +end + # load basic rules MRuby.each_target do |build| build.define_rules diff --git a/lib/mruby/gem.rb b/lib/mruby/gem.rb index 619621e7b..a11c9feda 100644 --- a/lib/mruby/gem.rb +++ b/lib/mruby/gem.rb @@ -401,7 +401,16 @@ module MRuby end end - def generate_gem_table build + def setup(build) + gemset = nil + begin + gemset_prev = gemset + self.each(&:setup) + gemset = self.setup_dependencies(build).keys.sort + end until gemset == gemset_prev + end + + def setup_dependencies(build) gem_table = each_with_object({}) { |spec, h| h[spec.name] = spec } default_gems = {} @@ -424,6 +433,12 @@ module MRuby end end + gem_table + end + + def generate_gem_table(build) + gem_table = setup_dependencies(build) + each do |g| g.dependencies.each do |dep| name = dep[:gem] diff --git a/tasks/mrbgems.rake b/tasks/mrbgems.rake index 272d476e6..291bea615 100644 --- a/tasks/mrbgems.rake +++ b/tasks/mrbgems.rake @@ -2,8 +2,6 @@ MRuby.each_target do active_gems_txt = "#{build_dir}/mrbgems/active_gems.txt" if enable_gems? - # set up all gems - gems.each(&:setup) gems.check self # loader all gems From db1578c123b33fee4754d54e24cb0a234df5adf3 Mon Sep 17 00:00:00 2001 From: dearblue Date: Thu, 11 Sep 2025 22:53:06 +0900 Subject: [PATCH 2/2] Separating the build setup portion from the GEMS setup block The issue resolved by the preceding patch was solely the C++ exception task within the mruby core. This patch aims to resolve a similar sequencing issue that also exists in GEMS. In practice, `mruby-compiler` is sometimes loaded via dependencies rather than being explicitly specified in the build configuration file. In such cases, when `mruby-compiler/mrbgem.rake` is loaded, it is not yet determined whether C++ exceptions will be used. Consequently, even if it later becomes clear that `core/codegen-cxx.cxx` and `core/y.tab-cxx.cxx` are required, the system could not handle this. To resolve this issue, we introduce the `MRuby::Gem::Specification#build_settings` method as a mechanism for lazily evaluating build setup. However, for backward compatibility, the commands are cloned twice in `gem.setup` and `gem.setup_build`. This is because many existing GEMS configure commands directly within the setup block. ref. https://github.com/mruby/mruby/issues/6615 --- doc/guides/mrbgems.md | 22 +++++++++++++++++++++ lib/mruby/gem.rb | 31 ++++++++++++++++++++++++++---- mrbgems/mruby-compiler/mrbgem.rake | 16 ++++++++------- tasks/mrbgems.rake | 1 + 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/doc/guides/mrbgems.md b/doc/guides/mrbgems.md index 5c63ffb71..d7104821e 100644 --- a/doc/guides/mrbgems.md +++ b/doc/guides/mrbgems.md @@ -310,6 +310,28 @@ For example: when B depends on C and A depends on B, A will get include paths ex Exported `include_paths` are automatically appended to GEM local `include_paths` by rake. You can use `spec.export_include_paths` accessor if you want more complex build. +### Settings for GEM build commands/tasks + +When the block argument passed to `MRuby::Gem::Specification.new` is executed, +the GEM build commands/tasks for the `MRuby::Build` instance may not yet be finalized. +In most cases, modifying the GEM build commands/tasks within the block passed to +`MRuby::Gem::Specification.new` is not a problem. + +However, you may need to perform GEM build commands/tasks after the GEM build +commands/tasks for the `MRuby::Build` instance have been finalized. +In such cases, you can achieve this by passing a block argument to +`MRuby::Gem::Specification#build_settings` within the block passed to +`MRuby::Gem::Specification.new`. + +```ruby +spec.build_settings do + spec.cc.flags << "-any_flags" +end +``` + +**NOTE**: Using the `build_settings` method will cause GEM's all build command settings +directly written in the block passed to `MRuby::Gem::Specification.new` to be ignored. + ## C Extension mruby can be extended with C. This is possible by using the C API to diff --git a/lib/mruby/gem.rb b/lib/mruby/gem.rb index a11c9feda..1e9a3d919 100644 --- a/lib/mruby/gem.rb +++ b/lib/mruby/gem.rb @@ -53,10 +53,8 @@ module MRuby return if defined?(@bins) # return if already set up MRuby::Gem.current = self - MRuby::Build::COMMANDS.each do |command| - instance_variable_set("@#{command}", @build.send(command).clone) - end - @linker.run_attrs.each(&:clear) + reset_commands # for backward compatibility, reset the commands from the beginning. + @build_settings = nil @rbfiles = Dir.glob("#{@dir}/mrblib/**/*.rb").sort @objs = srcs_to_objs("src") @@ -192,6 +190,19 @@ module MRuby end end + def build_settings(&blk) + @build_settings = blk + end + + def setup_build + if @build_settings + # by this point, build.cc or other commands may have been modified. + # therefore, reset the commands again before calling build_settings. + reset_commands + @build_settings.call(self) + end + end + def define_gem_init_builder file "#{build_dir}/gem_init.c" => [build.mrbcfile, __FILE__] + [rbfiles].flatten do |t| mkdir_p build_dir @@ -303,6 +314,13 @@ module MRuby self end + + private def reset_commands + MRuby::Build::COMMANDS.each do |command| + instance_variable_set("@#{command}", @build.send(command).clone) + end + @linker.run_attrs.each(&:clear) + end end # Specification class Version @@ -410,6 +428,11 @@ module MRuby end until gemset == gemset_prev end + def setup_build + each(&:setup_build) + self + end + def setup_dependencies(build) gem_table = each_with_object({}) { |spec, h| h[spec.name] = spec } diff --git a/mrbgems/mruby-compiler/mrbgem.rake b/mrbgems/mruby-compiler/mrbgem.rake index c5e1bd539..bd83c8bc4 100644 --- a/mrbgems/mruby-compiler/mrbgem.rake +++ b/mrbgems/mruby-compiler/mrbgem.rake @@ -3,15 +3,17 @@ MRuby::Gem::Specification.new 'mruby-compiler' do |spec| spec.author = 'mruby developers' spec.summary = 'mruby compiler library' - objs = %w[codegen y.tab].map do |name| - src = "#{dir}/core/#{name}.c" - if build.cxx_exception_enabled? - build.compile_as_cxx(src) - else - objfile(src.pathmap("#{build_dir}/core/%n")) + spec.build_settings do + objs = %w[codegen y.tab].map do |name| + src = "#{dir}/core/#{name}.c" + if build.cxx_exception_enabled? + build.compile_as_cxx(src) + else + objfile(src.pathmap("#{build_dir}/core/%n")) + end end + build.libmruby_core_objs << objs end - build.libmruby_core_objs << objs end dir = __dir__ diff --git a/tasks/mrbgems.rake b/tasks/mrbgems.rake index 291bea615..55a49c61d 100644 --- a/tasks/mrbgems.rake +++ b/tasks/mrbgems.rake @@ -2,6 +2,7 @@ MRuby.each_target do active_gems_txt = "#{build_dir}/mrbgems/active_gems.txt" if enable_gems? + gems.setup_build gems.check self # loader all gems