Skip to content

build: keep the linker options of a gem that is in libmruby.a - #7203

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:linker-attrs-library-objs
Aug 16, 2026
Merged

build: keep the linker options of a gem that is in libmruby.a#7203
matz merged 1 commit into
mruby:masterfrom
takumin:linker-attrs-library-objs

Conversation

@takumin

@takumin takumin commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

MRuby::Gem::List#linker_attrs drops every gem that builds a binary, and hands the rest to each link of libmruby.a. 5c130e8 added that for #5210, where a gem's options reached binaries the gem had nothing to do with. It reads bin? as "not a library gem", and a gem can be both.

build.libmruby_objs << @objs (lib/mruby/gem.rb:103) is unconditional, so a gem with a src/ or an mrblib/ puts objects in libmruby.a whatever else it builds. When such a gem also declares spec.bins, its objects are in the archive and its linker options are not on the line that links it, so anything but its own binary fails to link.

mruby-task is that gem as soon as MRB_TASK_BUILD_DEMO is set: the define gives it spec.bins (mrbgems/mruby-task/mrbgem.rake:52), and it carries src/ and a ports/<port>/ HAL whose glib port is what needs the search_package libraries. build_config/glib_hal_test.rb sets both, and mrbtest cannot be linked there:

$ MRUBY_CONFIG=glib_hal_test rake -m test
ld: mrbgems/mruby-task/ports/glib/task_hal.c:132: undefined reference to `g_main_loop_quit'
ld: mrbgems/mruby-task/ports/glib/task_hal.c:134: undefined reference to `g_thread_join'
ld: mrbgems/mruby-task/ports/glib/task_hal.c:138: undefined reference to `g_source_destroy'
...
rake aborted!
Tasks: TOP => bin/mrbtest => build/host/bin/mrbtest
gcc -fsanitize=address,undefined -o "build/host/bin/mrbtest" .../driver.o .../vformat.o .../mrbtest.o .../mrbtest.a "build/host/lib/libmruby.a"  -lm

The change

Drop a binary gem only when it contributes no object. A gem that put nothing in libmruby.a still has options that belong to its own binary alone, which is what #5210 asked for; one that did needs them wherever libmruby.a is linked.

The second line of the diff follows from the first. tasks/bin.rake:10 asks for linker_attrs(gem) when it links a gem's own binary, and a gem that now stays in the library set would be appended to it a second time, so its options would be doubled on that line. Append it only when it is not already there.

Verified

build_config/glib_hal_test.rb, gcc 13.3.0, glib 2.80.0, from a clean tree. That config opens an anonymous MRuby::Build on master, which is why the paths below say build/host. #7200 gives it a name and is stacked on this PR: with the two together its rake test is green, bintest included.

$ MRUBY_CONFIG=glib_hal_test rake -m test    # exit 0
bintest - Command Binary Test
  Total: 0     OK: 0    KO: 0    Crash: 0    Skip: 0
mrbtest - Embeddable Ruby Test
  Total: 832   OK: 831  KO: 0    Crash: 0    Skip: 1
gcc -fsanitize=address,undefined -o "build/host/bin/mrbtest" .../driver.o .../vformat.o .../mrbtest.o .../mrbtest.a "build/host/lib/libmruby.a" -L/usr/lib/x86_64-linux-gnu -lglib-2.0 -L/usr/lib/x86_64-linux-gnu -lgthread-2.0 -pthread -lglib-2.0 -lm

Those libraries appear twice because the gem calls search_package twice, before and after this commit alike.

build/host/bin/mruby_task_demo is linked with the same line as before, which is what the include? guard is for:

gcc -fsanitize=address,undefined -o "build/host/bin/mruby_task_demo" .../mruby_task_demo.o "build/host/lib/libmruby.a" -L/usr/lib/x86_64-linux-gnu -lglib-2.0 -L/usr/lib/x86_64-linux-gnu -lgthread-2.0 -pthread -lglib-2.0 -lm

Nothing in the standard gemboxes moves

Every core binary gem ships a tools/ and no src/ or mrblib/, so @objs is empty for all of them (:62, :96, :97) and the set linker_attrs returns is the one it returned before. The default build links bin/mruby and bin/mirb with byte-identical command lines, taken by removing them and rebuilding with rake --verbose on each side:

# both before and after
gcc  -o "build/host/bin/mirb" .../mirb.o .../mirb_buffer.o .../mirb_completion.o .../mirb_editor.o .../mirb_highlight.o .../mirb_history.o .../mirb_term.o "build/host/lib/libmruby.a"  -lm
gcc  -o "build/host/bin/mruby" .../mruby.o "build/host/lib/libmruby.a"  -lm
$ rake -m test                               # exit 0
  Total: 2089  OK: 2041  KO: 0  Crash: 0  Skip: 48

#5210 behaves as it did

Its own example: a gem carrying only a tools/foo/main.c and spec.linker.library_paths << "/usr/lib/foo", built beside mruby-bin-mirb. The path stays off every line but its own, and appears once in the whole run:

$ MRUBY_CONFIG=issue5210.rb rake --verbose > log    # exit 0
$ grep -c /usr/lib/foo log
1
$ grep -o 'gcc.*bin/foo".*' log
gcc -L/usr/lib/foo -o "build/issue5210/bin/foo" .../main.o "build/issue5210/lib/libmruby.a"  -lm
$ grep -o 'gcc.*bin/mirb".*' log
gcc  -o "build/issue5210/bin/mirb" .../mirb.o ... "build/issue5210/lib/libmruby.a"  -lm
the gem and the config used
# mruby-foo/mrbgem.rake
MRuby::Gem::Specification.new('mruby-foo') do |spec|
  spec.license = 'MIT'
  spec.author  = 'mruby developers'
  spec.summary = 'example'
  spec.bins << "foo"
  spec.linker.library_paths << "/usr/lib/foo"
end
// mruby-foo/tools/foo/main.c
int main(void) { return 0; }
# issue5210.rb
MRuby::Build.new('issue5210') do |conf|
  conf.toolchain :gcc

  conf.gem gemdir: "/path/to/mruby-foo"
  conf.gem core: "mruby-bin-mirb"
  conf.gem core: "mruby-bin-mrbc"
  conf.gem core: "mruby-compiler"
end

Environment

Details
OS Ubuntu 24.04.4 LTS
Kernel Linux 7.0.0-28-generic x86_64
CPU AMD Ryzen 9 5950X, 16 cores / 32 threads
C compiler gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
binutils GNU ld (GNU Binutils) 2.47.20260726
glib 2.80.0
CRuby running rake ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM

The line each build gives src/string.c, read off rake --verbose with -MMD -c, the -I list and -o dropped. glib_hal_test calls enable_debug, which appends -g3 -O0 after the gcc toolchain's own -g -O3; the other two do not:

# default
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -DHAVE_MRUBY_REGEXP_GEM -DMRB_USE_SET -DHAVE_MRUBY_IO_GEM -DMRB_USE_RATIONAL -DMRB_USE_COMPLEX -DMRB_USE_BIGINT -DMRB_USE_DEBUG_HOOK src/string.c

# glib_hal_test
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings -fsanitize=address,undefined -g3 -O0 -DMRB_TASK_BUILD_DEMO -DMRB_DEBUG -DMRB_USE_TASK_SCHEDULER src/string.c

# issue5210
gcc -std=gnu99 -g -O3 -Wall -Wundef -Werror-implicit-function-declaration -Wwrite-strings src/string.c

`MRuby::Gem::List#linker_attrs` drops every gem that builds a binary,
and hands the rest to each link of libmruby.a.  5c130e8 added that for
mruby#5210, where a gem's options reached binaries the gem had nothing to do
with.  It reads `bin?` as "not a library gem", and a gem can be both.

`build.libmruby_objs << @objs` (lib/mruby/gem.rb:103) is unconditional,
so a gem with a src/ or an mrblib/ puts objects in libmruby.a whatever
else it builds.  When such a gem also declares `spec.bins`, its objects
are in the archive and its linker options are not on the line that links
it, so anything but its own binary fails to link.

`mruby-task` is that gem as soon as `MRB_TASK_BUILD_DEMO` is set: the
define gives it `spec.bins` (mrbgems/mruby-task/mrbgem.rake:52), and it
carries src/ and a ports/<port>/ HAL whose glib port is what needs the
`search_package` libraries.  build_config/glib_hal_test.rb sets both, and
mrbtest cannot be linked there:

    $ MRUBY_CONFIG=glib_hal_test rake -m test
    ld: mrbgems/mruby-task/ports/glib/task_hal.c:132: undefined
    reference to `g_main_loop_quit'
    ld: mrbgems/mruby-task/ports/glib/task_hal.c:134: undefined
    reference to `g_thread_join'
    ...
    rake aborted!
    Tasks: TOP => bin/mrbtest => build/host/bin/mrbtest

    gcc -fsanitize=address,undefined -o "build/host/bin/mrbtest"
      .../driver.o .../vformat.o .../mrbtest.o .../mrbtest.a
      "build/host/lib/libmruby.a"  -lm

Drop a binary gem only when it contributes no object.  A gem that put
nothing in libmruby.a still has options that belong to its own binary
alone, which is what mruby#5210 asked for, and one that did needs them
wherever libmruby.a is linked:

    $ MRUBY_CONFIG=glib_hal_test rake -m test    # exit 0
    bintest - Command Binary Test
      Total: 0     OK: 0    KO: 0    Crash: 0    Skip: 0
    mrbtest - Embeddable Ruby Test
      Total: 832   OK: 831  KO: 0    Crash: 0    Skip: 1

    gcc -fsanitize=address,undefined -o "build/host/bin/mrbtest"
      .../driver.o .../vformat.o .../mrbtest.o .../mrbtest.a
      "build/host/lib/libmruby.a" -L/usr/lib/x86_64-linux-gnu -lglib-2.0
      -L/usr/lib/x86_64-linux-gnu -lgthread-2.0 -pthread -lglib-2.0 -lm

Those libraries appear twice because the gem calls `search_package`
twice, before and after this commit alike.

The second line follows from the first.  tasks/bin.rake:10 asks for
`linker_attrs(gem)` when it links a gem's own binary, and a gem that now
stays in the library set would be appended to it a second time, so its
options would be doubled on that line.  Append it only when it is not
already there.  build/host/bin/mruby_task_demo is linked with the same
line as before:

    gcc -fsanitize=address,undefined -o "build/host/bin/mruby_task_demo"
      .../mruby_task_demo.o "build/host/lib/libmruby.a"
      -L/usr/lib/x86_64-linux-gnu -lglib-2.0
      -L/usr/lib/x86_64-linux-gnu -lgthread-2.0 -pthread -lglib-2.0 -lm

Nothing in the standard gemboxes moves.  Every core binary gem ships a
tools/ and no src/ or mrblib/, so `@objs` is empty for all of them
(:62, :96, :97) and the set `linker_attrs` returns is the one it
returned before.  The default build links bin/mruby and bin/mirb with
byte-identical command lines, and `rake -m test` is green:

    $ rake -m test                               # exit 0
      Total: 2089  OK: 2041  KO: 0  Crash: 0  Skip: 48

The example from mruby#5210 behaves as it did.  A gem carrying only a
tools/foo/main.c and `spec.linker.library_paths << "/usr/lib/foo"`,
built beside mruby-bin-mirb, keeps that path off every line but its own,
and it appears once in the whole run:

    $ MRUBY_CONFIG=issue5210.rb rake --verbose > log    # exit 0
    $ grep -c /usr/lib/foo log
    1
    $ grep -o 'gcc.*bin/foo".*' log
    gcc -L/usr/lib/foo -o "build/issue5210/bin/foo" .../main.o
      "build/issue5210/lib/libmruby.a"  -lm
    $ grep -o 'gcc.*bin/mirb".*' log
    gcc  -o "build/issue5210/bin/mirb" .../mirb.o ...
      "build/issue5210/lib/libmruby.a"  -lm
@takumin
takumin requested a review from matz as a code owner August 16, 2026 16:06
@github-actions github-actions Bot added the build label Aug 16, 2026
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ca3269e-7048-4203-8f52-dec58d7e9c51

📥 Commits

Reviewing files that changed from the base of the PR and between 9710e46 and de5545b.

📒 Files selected for processing (1)
  • lib/mruby/gem.rb

Included review availability: Your plan includes up to 8 reviews per rolling hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

List#linker_attrs now includes gems that contribute objects to libmruby.a, excludes only binary gems without objects, and avoids duplicate inclusion of an explicitly supplied gem.

Changes

Linker attribute selection

Layer / File(s) Summary
Select object-contributing gems
lib/mruby/gem.rb
List#linker_attrs retains non-binary gems and binary gems with objects, excludes binary gems without objects, and prevents duplicate inclusion of the optional gem argument.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to de554

The linker-option handling change is narrowly scoped and reported as verified without an identified merge-blocking issue; no actionable merge-blocking risk remains beyond normal checks and review.

Suggested labels: mrbgems

Suggested reviewers: matz, dearblue

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: retaining linker options for gems included in libmruby.a.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@matz
matz merged commit 37e0151 into mruby:master Aug 16, 2026
21 checks passed
@takumin
takumin deleted the linker-attrs-library-objs branch August 16, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants