From f82c3b1c874a61696d64c49efff819dcbad8cebe Mon Sep 17 00:00:00 2001 From: Kouji Takao Date: Sat, 28 Mar 2026 02:21:18 +0900 Subject: [PATCH] feat: add smalruby3 launcher command (rsdl replacement) Build a native smalruby3 binary that wraps SDL_main + ruby_init, solving the macOS main thread requirement for SDL2. Users can run scripts with `smalruby3 script.rb` instead of `rsdl`. - ext/smalruby3_launcher/: C source and extconf.rb (based on rsdl) - gemspec: add launcher as second extension - .standard.yml: exclude extconf.rb from lint (mkmf globals) Closes #403 Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/rules/ruby/rsdl.md | 54 ------------------- .gitmodules | 4 -- ruby/rsdl | 1 - ruby/smalruby3/.gitignore | 7 +++ ruby/smalruby3/.standard.yml | 2 + ruby/smalruby3/exe/smalruby3 | 18 +++++++ .../ext/smalruby3_launcher/Makefile.in | 26 +++++++++ .../ext/smalruby3_launcher/extconf.rb | 48 +++++++++++++++++ .../smalruby3_launcher.c.in | 38 +++++++++++++ ruby/smalruby3/smalruby3.gemspec | 6 ++- 10 files changed, 143 insertions(+), 61 deletions(-) delete mode 100644 .claude/rules/ruby/rsdl.md delete mode 160000 ruby/rsdl create mode 100644 ruby/smalruby3/.standard.yml create mode 100755 ruby/smalruby3/exe/smalruby3 create mode 100644 ruby/smalruby3/ext/smalruby3_launcher/Makefile.in create mode 100644 ruby/smalruby3/ext/smalruby3_launcher/extconf.rb create mode 100644 ruby/smalruby3/ext/smalruby3_launcher/smalruby3_launcher.c.in diff --git a/.claude/rules/ruby/rsdl.md b/.claude/rules/ruby/rsdl.md deleted file mode 100644 index 7a636845c8a..00000000000 --- a/.claude/rules/ruby/rsdl.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -paths: - - "ruby/rsdl/**" ---- - -# rsdl Development - -## Overview - -rsdl は macOS で Ruby + SDL2 を使う際に必要なコマンド。GC/メインスレッド問題を回避するラッパー。 -smalruby/rsdl は knu/rsdl の fork で、Ruby 4.0 対応を行っている。 - -## Git Submodule - -`ruby/rsdl` は git submodule として管理されている。 - -``` -origin: https://github.com/smalruby/rsdl.git (fork) -upstream: https://github.com/knu/rsdl.git (オリジナル) -``` - -### ブランチ構成 - -| ブランチ | 内容 | -|---|---| -| `master` | upstream の master と同期 | -| `smalruby/ruby-4.0-support` | ERB.new API 修正(Ruby 4.0 対応) | - -### PR 作成ルール - -1. **まず origin (smalruby/rsdl) に PR を作成**して動作確認 -2. 動作確認後、**upstream (knu/rsdl) にも PR を作成** -3. upstream への PR を想定して、**機能ごとに細かくブランチ/PR を分ける** - -## ビルド - -```bash -cd ruby/rsdl -rbenv local 4.0.2 # or 3.3.9, 3.4.9 -ruby extconf.rb && make -``` - -## smalruby3 gem からの使い方 - -```bash -cd ruby/smalruby3 -../rsdl/rsdl -I../ruby-sdl2 -I../ruby-sdl2/lib -Ilib examples/01_move.rb -``` - -## 主な変更点(upstream との差分) - -- `extconf.rb`: `ERB.new(str, nil, '%')` → `ERB.new(str, trim_mode: '%')` -- `extconf.rb`: `open(file, 'w')` → `File.open(file, 'w')` -- `extconf.rb`: `file_in.result` → `file_in.result(binding)` diff --git a/.gitmodules b/.gitmodules index dec251f9eaa..5980f4e8ebf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,7 +2,3 @@ path = ruby/ruby-sdl2 url = https://github.com/smalruby/ruby-sdl2.git branch = smalruby/add-read-pixels -[submodule "ruby/rsdl"] - path = ruby/rsdl - url = https://github.com/smalruby/rsdl.git - branch = smalruby/ruby-4.0-support diff --git a/ruby/rsdl b/ruby/rsdl deleted file mode 160000 index 21829ff07ad..00000000000 --- a/ruby/rsdl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 21829ff07adee4045ea64c75bb68be5748f58d6d diff --git a/ruby/smalruby3/.gitignore b/ruby/smalruby3/.gitignore index dcc56be6f38..cb44f426ef9 100644 --- a/ruby/smalruby3/.gitignore +++ b/ruby/smalruby3/.gitignore @@ -7,5 +7,12 @@ lib/smalruby3/smalruby3_imageutil.bundle lib/smalruby3/smalruby3_imageutil.so *.gem +# Launcher build artifacts +ext/smalruby3_launcher/smalruby3 +ext/smalruby3_launcher/smalruby3_launcher.c +ext/smalruby3_launcher/smalruby3_launcher.o +ext/smalruby3_launcher/Makefile +ext/smalruby3_launcher/mkmf.log + # Docker marker .built diff --git a/ruby/smalruby3/.standard.yml b/ruby/smalruby3/.standard.yml new file mode 100644 index 00000000000..9b48f2b4e0b --- /dev/null +++ b/ruby/smalruby3/.standard.yml @@ -0,0 +1,2 @@ +ignore: + - "ext/**/extconf.rb" diff --git a/ruby/smalruby3/exe/smalruby3 b/ruby/smalruby3/exe/smalruby3 new file mode 100755 index 00000000000..30d22b2ae4a --- /dev/null +++ b/ruby/smalruby3/exe/smalruby3 @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +# smalruby3 launcher wrapper — exec's the native SDL2 binary. +# The native binary (smalruby3-bin) is installed to Ruby's bindir +# by ext/smalruby3_launcher during gem install. + +require "rbconfig" + +bindir = RbConfig::CONFIG["bindir"] +binary = File.join(bindir, "smalruby3-bin") + +# Fallback: development mode (built locally in ext/) +unless File.exist?(binary) + binary = File.join(File.dirname(__FILE__, 2), + "ext", "smalruby3_launcher", "smalruby3") +end + +exec binary, *ARGV diff --git a/ruby/smalruby3/ext/smalruby3_launcher/Makefile.in b/ruby/smalruby3/ext/smalruby3_launcher/Makefile.in new file mode 100644 index 00000000000..e0c583975c0 --- /dev/null +++ b/ruby/smalruby3/ext/smalruby3_launcher/Makefile.in @@ -0,0 +1,26 @@ +CC = <%= config['CC'] %> + +CFLAGS = <%= config['CFLAGS'] %> +LIBS = <%= config['LIBS'] %> +LDFLAGS = <%= config['LDFLAGS'] %> +LIBPATH = <%= config['LIBPATH'] %> +LIBRUBYARG = <%= config['LIBRUBYARG'] %> +EXEEXT = <%= config['EXEEXT'] %> + +PROGRAM = smalruby3$(EXEEXT) + +OBJS = smalruby3_launcher.o + +.c.o: + $(CC) $(CFLAGS) -c $< + +all: $(PROGRAM) + +clean: + <%= config['RMALL'] %> $(PROGRAM) $(OBJS) + +install: + <%= config['INSTALL'] %> $(PROGRAM) <%= config['bindir'] %>/smalruby3-bin + +$(PROGRAM): $(OBJS) + $(CC) $(OBJS) $(LIBPATH) $(LDFLAGS) $(LIBRUBYARG) $(LIBS) -o $@ diff --git a/ruby/smalruby3/ext/smalruby3_launcher/extconf.rb b/ruby/smalruby3/ext/smalruby3_launcher/extconf.rb new file mode 100644 index 00000000000..8abd608a67b --- /dev/null +++ b/ruby/smalruby3/ext/smalruby3_launcher/extconf.rb @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Build the smalruby3 launcher binary (SDL2 main thread wrapper). +# Based on rsdl's extconf.rb. + +require "mkmf" +require "erb" + +# Source directory where .in templates live +srcdir = File.dirname(File.expand_path(__FILE__)) + +dir_config("sdl") +sdlconfig = with_config("sdl-config", "sdl-config") + +config = {} +config["arch"] = RbConfig::CONFIG["arch"] +config["INSTALL"] = RbConfig::CONFIG["INSTALL"] +config["RMALL"] = RbConfig::CONFIG["RMALL"] || "rm -fr" +config["CC"] = RbConfig::CONFIG["CC"] +config["CFLAGS"] = RbConfig::CONFIG["CFLAGS"] +config["CFLAGS"] += " -I\"#{$hdrdir}\"" if $hdrdir +config["CFLAGS"] += " -I\"#{$arch_hdrdir}\"" if $arch_hdrdir +config["CFLAGS"] += " " + `"#{sdlconfig}" --cflags` if sdlconfig && !sdlconfig.empty? +config["LDFLAGS"] = RbConfig::CONFIG["LDFLAGS"] +config["LIBS"] = RbConfig::CONFIG["LIBS"] +config["LIBS"] += " " + `"#{sdlconfig}" --libs` if sdlconfig && !sdlconfig.empty? +config["LIBPATH"] = RbConfig.expand(libpathflag) +config["LIBRUBYARG"] = RbConfig::CONFIG["LIBRUBYARG"] +config["EXEEXT"] = RbConfig::CONFIG["EXEEXT"] +config["bindir"] = RbConfig::CONFIG["bindir"] +# gem_dir: where the gem is installed (for install target) +config["gem_dir"] = srcdir + +headers = [] +headers << "#define HAVE_RUBY_SYSINIT 1" if have_func("ruby_sysinit") +headers << "#define HAVE_RUBY_RUN_NODE 1" if have_func("ruby_run_node") +config["COMMON_HEADERS"] = ([(COMMON_HEADERS || "")] + headers).join("\n") + +%w[Makefile smalruby3_launcher.c].each do |file| + # Read template from source directory + template_path = File.join(srcdir, file + ".in") + template = ERB.new(File.read(template_path), trim_mode: "%") + message "creating %s\n" % file + File.open(file, "w") do |f| + f.print template.result(binding) + end +end diff --git a/ruby/smalruby3/ext/smalruby3_launcher/smalruby3_launcher.c.in b/ruby/smalruby3/ext/smalruby3_launcher/smalruby3_launcher.c.in new file mode 100644 index 00000000000..32ebfa8edba --- /dev/null +++ b/ruby/smalruby3/ext/smalruby3_launcher/smalruby3_launcher.c.in @@ -0,0 +1,38 @@ +/* smalruby3 launcher — SDL2 main thread wrapper for macOS */ +/* Based on rsdl (https://github.com/knu/rsdl) */ + +#include +<%= config['COMMON_HEADERS'] %> +#include +#include "SDL_main.h" +#ifdef HAVE_LOCALE_H +#include +#endif + +#ifdef RUBY_GLOBAL_SETUP +RUBY_GLOBAL_SETUP +#endif + +int main(int argc, char **argv) +{ +#ifdef HAVE_LOCALE_H + setlocale(LC_CTYPE, ""); +#endif + +#ifdef HAVE_RUBY_SYSINIT + ruby_sysinit(&argc, &argv); +#endif + { +#ifdef RUBY_INIT_STACK + RUBY_INIT_STACK; +#endif + ruby_init(); +#ifdef HAVE_RUBY_RUN_NODE + return ruby_run_node(ruby_options(argc, argv)); +#else + ruby_options(argc, argv); + ruby_run(); + return 0; +#endif + } +} diff --git a/ruby/smalruby3/smalruby3.gemspec b/ruby/smalruby3/smalruby3.gemspec index 97127901c59..1c2bd91fefc 100644 --- a/ruby/smalruby3/smalruby3.gemspec +++ b/ruby/smalruby3/smalruby3.gemspec @@ -16,9 +16,11 @@ Gem::Specification.new do |spec| spec.license = "MIT" spec.required_ruby_version = ">= 3.3" - spec.files = Dir["lib/**/*.rb", "lib/**/*.json", "ext/**/*.{rb,rs,toml}", "assets/**/*", "LICENSE", "README.md"] + spec.files = Dir["lib/**/*.rb", "lib/**/*.json", "ext/**/*.{rb,rs,toml,in,c.in}", "exe/*", "assets/**/*", "LICENSE", "README.md"] + spec.bindir = "exe" + spec.executables = ["smalruby3"] spec.require_paths = ["lib"] - spec.extensions = ["ext/smalruby3_imageutil/extconf.rb"] + spec.extensions = ["ext/smalruby3_imageutil/extconf.rb", "ext/smalruby3_launcher/extconf.rb"] spec.add_dependency "ruby-sdl2", "~> 0.3" spec.add_dependency "rb_sys", "~> 0.9"