mieki256's diary



2018/03/19(月) [n年前の日記]

#1 [ruby][mruby] mrubyはスクリプトソースのファイル分割ができるのだろうか

ここ数日、mruby について色々試してたけど。ふと、気づいてしまった。mruby は require が無いけれど、それはつまり、スクリプトソースのファイル分割すらできない、ということだったりするのでは…。

例えばゲームのプログラムを作る時…。 てな感じでファイルを分けたりするけれど。まさかこういうことすらできなくて、全部 main.rb に、ずらずらと書かなきゃいけない、みたいな状態になるのだろうかと。昔のベーマガで掲載されてた、1ページに収まるゲームプログラムを書け、的な縛りプレイを思い出す…。21世紀にもなって、趣味で自発的にやるならともかく、強制されちゃうのはちょっと。

例えばコレが love2d (Lua) だったら、main.lua とは別に、フォルダを作ってソースを入れておいて、それをライブラリとして扱って、
local sti = require "sti"
てな感じで使えるようになるのだけど…。

Ruby は Lua 未満なの?

mruby-requireというmrbgemがあるらしい。 :

ググってみたら、mruby-require という mrbgem があるらしい。

_iij/mruby-require: "require" and "load" for mruby
_mattn/mruby-require

コレを使えばファイル分割もできるようになるのではないか。

しかし、Windows10 x64 + mruby 1.4.0 では、ビルドが通ったり通らなかったりでハマった…。

Visual Studio 2015 Community の場合。 :

まずは、Visual Studio 2015 Community を使ってビルドを試した。

_iij/mruby-require の場合。

build_config.rb に、以下を追加。
MRuby::Build.new do |conf|
  ...
  conf.gem :github => 'iij/mruby-require'
end

「開発者コマンドプロンプト for VS2015」を開いて、ビルドすると、エラーが出てきた。
> ruby ./minirake

(in D:/Ruby/mruby/mruby-1.4.0)

CC build/mrbgems/mruby-require/src/require.c -> build/host/mrbgems/mruby-require/src/require.obj
require.c
D:\Ruby\mruby\mruby-1.4.0\build\mrbgems\mruby-require\src\require.c(6): fatal error C1083: include ファイルを開けません。'unistd.h':No such file or directory
require.c
D:\Ruby\mruby\mruby-1.4.0\build\mrbgems\mruby-require\src\require.c(6): fatal error C1083: include ファイルを開けません。'unistd.h':No such file or directory

rake aborted!

Command Failed: [cl.exe /c /nologo /W3 /we4013 /Zi /MD /O2 /D_CRT_SECURE_NO_WARNINGS /DMRB_STACK_EXTEND_DOUBLING /DMRB_DEBUG /DMRBGEM_MRUBY_REQUIRE_VERSION=0.0.0 /I"D:\Ruby\mruby\mruby-1.4.0\include" /I"D:\Ruby\mruby\mruby-1.4.0\src" /I"D:\Ruby\mruby\mruby-1.4.0\mrbgems\mruby-io\include" /Fo"D:\Ruby\mruby\mruby-1.4.0\build\host\mrbgems\mruby-require\src\require.obj" "D:\Ruby\mruby\mruby-1.4.0\build\mrbgems\mruby-require\src\require.c"]
調べてみたら、Visual C (C++?) に unistd.h なんてものは無いらしい。

ならばと、 _mattn/mruby-require を試してみたけど。

build_config.rb に、以下を追加。mattn/mruby-require は一番最後に記述しないといかんらしい。
MRuby::Build.new do |conf|
  ...
  conf.gem :core => 'mruby-bin-mrbc'
  conf.gem :github => 'mattn/mruby-require'
end

ビルド。
> ruby ./minirake

...

Build summary:

================================================
      Config Name: host
 Output Directory: build/host
         Binaries: mrbc
    Included Gems:
             mruby-sprintf - standard Kernel#sprintf method
             mruby-print - standard print/puts/p
             mruby-math - standard Math module
...
             mruby-kernel-ext - Kernel module extension
             mruby-class-ext - class/module extension
             mruby-json
             mruby-sleep - 0.0.1
             mruby-bin-mrbc - mruby compiler executable
             mruby-require
================================================
...
一見通ったように見えるけど。

_iij/mruby-require: "require" and "load" for mruby で紹介されてるサンプルを動かそうとすると…。

a.rb
require "b"

b = Bclass.new
p b.method

b.rb
class Bclass
  def method
    "BBB"
  end
end

> mruby a.rb
trace (most recent call last):
        [0] a.rb:1
a.rb:1: cannot load such file -- b (LoadError)
エラーになる。

require "b" を require "./b" や "./b.rb" にしてみてもエラーになる。
> mruby a.rb
LocalJumpError: unexpected return
> mruby a.rb
LocalJumpError: unexpected return

load "./b.rb" にしてみたら、mruby が不正終了。

このままでは、1つの .rbファイルに全ての処理をずらずらと書くしかない…。

MinGW+MSYSならビルドできた。 :

Visual Studio 2015 Community では、 _iij/mruby-require をビルドできなかったけど。MinGW系を使ったらビルドできたりしないか、と思って MinGW + MSYS で試してみたら、そちらならビルドできた。

MSYS2ではダメだった。 :

MSYS2ではビルド時にエラーが出てしまった。なんで? MinGW + MSYS なら通るのに、どうして MSYS2 は通らないの?
mieki256@dorobune: MINGW32: ~/prg/mruby/mruby$ make
ruby ./minirake
(in /d/home/mieki256/prg/mruby/mruby)
LD build/host/bin/mirb.exe
D:/home/mieki256/prg/mruby/mruby/build/host/lib/libmruby.a(io.o): In function `fptr_finalize':
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:645: undefined reference to `_imp__closesocket@4'
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:646: undefined reference to `_imp__WSAGetLastError@0'
D:/home/mieki256/prg/mruby/mruby/build/host/lib/libmruby.a(io.o): In function `mrb_io_s_select':
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1138: undefined reference to `_imp__select@20'
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1160: undefined reference to `__WSAFDIsSet@8'
...
D:/home/mieki256/prg/mruby/mruby/build/host/lib/libmruby.a(io.o):D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1185: more undefined references to `__WSAFDIsSet@8' follow
collect2.exe: error: ld returned 1 exit status
D:/home/mieki256/prg/mruby/mruby/build/host/lib/libmruby.a(io.o): In function `fptr_finalize':
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:645: undefined reference to `_imp__closesocket@4'
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:646: undefined reference to `_imp__WSAGetLastError@0'
D:/home/mieki256/prg/mruby/mruby/build/host/lib/libmruby.a(io.o): In function `mrb_io_s_select':
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1138: undefined reference to `_imp__select@20'
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1160: undefined reference to `__WSAFDIsSet@8'
...
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1183: undefined reference to `__WSAFDIsSet@8'
D:/home/mieki256/prg/mruby/mruby/build/host/lib/libmruby.a(io.o):D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1185: more undefined references to `__WSAFDIsSet@8' follow
collect2.exe: error: ld returned 1 exit status
rake aborted!
Command Failed: [gcc  -o "/d/home/mieki256/prg/mruby/mruby/build/host/bin/mirb.exe" "/d/home/mieki256/prg/mruby/mruby/build/host/mrbgems/mruby-bin-mirb/tools/mirb/mirb.o" "/d/home/mieki256/prg/mruby/mruby/build/host/lib/libmruby.a"  -lm ]
rakefile:74:in `block (4 levels) in <top (required)>'
make: *** [Makefile:8: all] エラー 1

ググってみたら、以下の記事に遭遇。

_Windows ネイティブなmruby.exeをmsys2で作る - Qiita
_Cross-compilation for Windows using MinGW fails - Issue #3046 ・ mruby/mruby

build_config.rb に以下を追加、とあるが…。
  conf.cc.command = ENV['CC'] || 'i686-w64-mingw32-gcc'
  conf.linker.command = ENV['LD'] || 'i686-w64-mingw32-gcc'

試してみても、やはりエラーが。

github から mruby の最新版を git clone で入手して試してみたけど、やはりエラーが出る。

mruby-require を外すと、MSYS2 上でもビルドができる。どうやらその場合、mruby-io が組み込まれない状態になって、エラーも出なくなる模様。mruby-io が足を引っ張ってる。

_WIN32 ってなんだろう。 :

mruby/mrbgems/mruby-io/src/io.c でエラーが出てるみたいなので眺めてみたら、_WIN32 が定義されてる場合とそれ以外で処理が分けてあった。_WIN32 のほうは、closesocket() という関数を使っているけど…。MinGW + MSYS と、MSYS2 で、その関数があったりなかったりするのだろうか?

そもそも _WIN32 って、どの環境なら定義されてるのだろう。確認してみたり。

definecheck.c
#include <stdio.h>

int main(int argc, char *argv[])
{

#ifdef _WIN32
  printf("_WIN32\n");
#else
  printf("not _WIN32\n");
#endif
  
#ifdef _WIN64
  printf("_WIN64\n");
#else
  printf("not _WIN64\n");
#endif

  return 0;
}

Makefile
TARGETS = definecheck

all: $(TARGETS)

# SDL_PREFIX  = /mingw64
# SDL_PREFIX  = /mingw32
SDL_PREFIX  = /mingw

MRB_HEAD    = ../mruby/include
MRB_LIBS    = ../mruby/build/host/lib

# CG_LIBS     = 

CROSS_COMPILE = $(SDL_PREFIX)/bin/
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++

CFLAGS = -g -Wall
CXXFLAGS = -g -Wall

LIBS = -lpthread -lm
# LIBS        = -lm
LDFLAGS = -Wl,-rpath -static -static-libgcc -static-libstdc++

clean:
	rm -f *.o *.a *.exe *~ $(TARGETS)

$(TARGETS): $(TARGETS).o
	$(CXX) -o $@ $^ $(LIBS) $(LDFLAGS)

MinGW + MSYS で make した場合。
$ ./definecheck.exe
_WIN32
not _WIN64

MSYS2 MinGW 32-bit (32bit) で make した場合。
$ ./definecheck.exe
_WIN32
not _WIN64

MSYS2 MinGW 64-bit (64bit) で make した場合。
$ ./definecheck.exe
_WIN32
_WIN64

_WIN32 と _WIN64 の両方が定義される環境があるのは予想外だった…。排他じゃないのか…。

何にせよ、MinGW系ならどれも _WIN32 が定義された状態でコンパイルされるのだろう。

ということは、io.c の件の場所は、_WIN32 のほうの処理がコンパイルされるはずで…。

closesocket()の定義場所を探す。 :

MinGW の場合、MinGW/include/winsock.h で closescoket() が定義されてるように見えた。

MSYS2 MinGW 32-bit (32bit) の場合、mingw32/i686-w64-mingw32/include/winsock.h で定義されてる。/mingw32/include/ の中では無いのだな…。

MSYS2 MinGW 64-bit (64bit) の場合、mingw64/x86_64-w64-mingw32/include/winsock.h で定義されてる。これも、/mingw64/include の中では無い、と。

このあたりは関係があるのかないのか…。分からんなあ…。

mrbgem.rake が関係してるかも。 :

mruby/mrbgems/ に入ってる、mruby-io の mrbgem.rake を眺めてるうちに、以下の行が気になった。
  case RUBY_PLATFORM
  when /mingw|mswin/
何のCRubyを使っているかで、処理というか、定義を分けているような気がする。ここでは mingw か mswin だった場合を想定しているようだけど…。

MinGW + MSYS 上で調べてみたら、以下の結果になった。この場合は、mingw という文字列が入ってる。
$ which ruby
/c/Ruby/Ruby22/bin/ruby.exe

$ ruby --version
ruby 2.2.6p396 (2016-11-15 revision 56800) [i386-mingw32]

$ ruby -e "puts RUBY_PLATFORM"
i386-mingw32

MSYS2 MinGW 32-bit (32bit) で調べてみると、以下。
$ which ruby
/usr/bin/ruby

$ ruby --version
ruby 2.4.0p0 (2016-12-24 revision 57164) [i386-msys]

$ ruby -e "puts RUBY_PLATFORM"
i386-msys

MSYS2 MinGW 64-bit (64bit) で調べてみると、以下。
$ which ruby
/usr/bin/ruby

$ ruby --version
ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-msys]

$ ruby -e "puts RUBY_PLATFORM"
x86_64-msys

つまり、MSYS2上で、MSYS2用の Ruby を動かした場合は、RUBY_PLATFORM に、mingw も mswin も含まれていない場面があるようで。

であれば、mruby-io の mrbgem.rake を修正してみたらどうなるだろう。
  case RUBY_PLATFORM
  when /mingw|mswin/
  case RUBY_PLATFORM
  when /mingw|mswin|msys/

ビルドしてみた。mruby-require を含めても、mruby-io のコンパイルでエラーが出なくなった。ビルドが最後まで通った。

ココかー。ココだったのか。この記述のせいで、MinGW + MSYS と、MSYS2 で、ビルドが通ったり通らなかったりしていたのだな。

Cプログラムにmrubyを組み込めなくなった。 :

MSYS2上でも mruby-require を組み込んだ形でビルドできるようになったはずなので、昨日書いた、SDL2を使うCプログラム + mruby を、mruby-require を使うこと前提の形に書き換えられるはず。

と思ったが、ビルドが通らなくなってしまった。
$ make
/mingw32/bin/gcc -g -Wall -I/mingw32/include/SDL2 -Dmain=SDL_main -I../mruby/include   -c -o glmrubysdl2.o glmrubysdl2.c
/mingw32/bin/g++ -o glmrubysdl2 glmrubysdl2.o -L../mruby/build/host/lib -lmruby -lm -L/mingw32/lib -lmingw32 -lSDL2main -lSDL2 -lSDL2_image -lpng -lz -ljpeg -ltiff -lwebp -llzma -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid -lopengl32 -lglu32 -lpthread -Wl,-rpath,/mingw32/lib -static -static-libgcc -static-libstdc++
../mruby/build/host/lib\libmruby.a(io.o): In function `fptr_finalize':
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:653: undefined reference to `_imp__closesocket@4'
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:654: undefined reference to `_imp__WSAGetLastError@0'
../mruby/build/host/lib\libmruby.a(io.o): In function `mrb_io_s_select':
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1146: undefined reference to `_imp__select@20'
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1168: undefined reference to `__WSAFDIsSet@8'
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1169: undefined reference to `__WSAFDIsSet@8'
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1179: undefined reference to `__WSAFDIsSet@8'
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1181: undefined reference to `__WSAFDIsSet@8'
D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1191: undefined reference to `__WSAFDIsSet@8'
../mruby/build/host/lib\libmruby.a(io.o):D:/home/mieki256/prg/mruby/mruby/mrbgems/mruby-io/src/io.c:1193: more undefined references to `__WSAFDIsSet@8' follow
collect2.exe: error: ld returned 1 exit status
make: *** [Makefile:42: glmrubysdl2] エラー 1
なんだかまた似たようなエラーが。closesocket なんて知らねえよと言われてる…。

ググったら以下の記事に遭遇。

_MinGWにてSocket通信を行うには - ハードリカーエンジニア
Winsockを使うためには、 #include にてインクルードしてあげれば良いのです…が、他にもコンパイル時に ws2_32.lib もリンカに設定してあげないといけません。設定するにはコンパイル時に下記のようなコマンドを入力します。

gcc -lwsock32 main.c -lws2_32

MinGWにてSocket通信を行うには - ハードリカーエンジニア より


そういうものなのか…。Makefile を修正。

Makefile
TARGETS = glmrubysdl2

all: $(TARGETS)

SDL_PREFIX  = /mingw32
# SDL_PREFIX  = /mingw

MRB_HEAD    = ../mruby/include
MRB_LIBS    = ../mruby/build/host/lib

# SDL_CONFIG  = $(SDL_PREFIX)/bin/sdl2-config
SDL_CONFIG  = sdl2-config
CG_LIBS     = 

CROSS_COMPILE = $(SDL_PREFIX)/bin/
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++

# SDLCFLAGS   = `$(SDL_CONFIG) --cflags`
SDLCFLAGS = -I$(SDL_PREFIX)/include/SDL2 -Dmain=SDL_main
CFLAGS = -g -Wall $(SDLCFLAGS) -I$(MRB_HEAD)
CXXFLAGS = -g -Wall $(SDLCFLAGS) -I$(MRB_HEAD)

# SDLLIBS     = `$(SDL_CONFIG) --libs`
# SDLLIBS     = -L$(SDL_PREFIX)/lib -lmingw32 -lSDL2main -lSDL2 -mwindows
SDLLIBS = -L$(SDL_PREFIX)/lib -lmingw32 -lSDL2main -lSDL2

# LDFLAGS     = -Wl,-rpath,$(SDL_PREFIX)/lib  -static -static-libgcc -static-libstdc++ -mwindows
LDFLAGS = -Wl,-rpath,$(SDL_PREFIX)/lib -static -static-libgcc -static-libstdc++

# LIBS = -L$(MRB_LIBS) -lmruby -lm $(SDLLIBS) -lopengl32 -lglu32 -lm -lSDL2_image
LIBS = -L$(MRB_LIBS) -lmruby -lm $(SDLLIBS) \
  -lSDL2_image -lpng -lz -ljpeg -ltiff -lwebp -llzma \
  -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm \
  -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid \
  -lopengl32 -lglu32 -lpthread \
  -lwsock32 -lws2_32

clean:
	rm -f *.o *.a *~ $(TARGETS)

$(TARGETS): $(TARGETS).o
	$(CXX) -o $@ $^ $(LIBS) $(LDFLAGS)
LIBS に、-lwsock32 -lws2_32 を追加してみた。

ビルドが通った。

いよいよ、mrubyスクリプトの、main.rb を修正。main.rb から、testsprite.rb というファイルを require で読み込むようにしてみる。

main.rb
#!ruby

require "testsprite"

class Scene
  def initialize
    @sprs = []
    num = 128
    ang = 0
    ang_d = 360.0 / num
    num.times do
      rad = ang * Math::PI / 180.0
      r = rand * 4.0 + 1.0
      dx = r * Math.cos(rad)
      dy = r * Math.sin(rad)
      s = TestSprite.new(320, 240, "sample.png", dx, dy)
      @sprs.push(s)
      ang += ang_d
    end
  end

  def update
    @sprs.each { |s| s.update }
  end

  def draw
    @sprs.each { |s| s.draw }
  end
end

Scene.new

testsprite.rb
#!ruby

class TestSprite < Sprite
  def initialize(x, y, imagefile, dx, dy)
    super(x, y, imagefile)
    @bx = x
    @by = y
    @dx = dx
    @dy = dy
  end

  def update
    wh = width / 2
    hh = height / 2
    @bx += @dx
    @by += @dy
    @dx *= -1 if (@bx <= wh or @bx >= 640 - wh)
    @dy *= -1 if (@by <= hh or @by >= 480 - hh)
    @x = (@bx - wh).to_i
    @y = (@by - hh).to_i
  end
end

出来上がった .exe を実行してみた。画像が表示された。

つまり、mruby-require を使えば、mrubyスクリプトソースをファイル分割して扱うことも可能、と分かった。まあ、MSYS2上で作業するなら、ビルド設定の記述を修正しないといけないあたりがアレではあるけど。何にせよ、良かった。Ruby は Lua 未満、という状態にならずに済みそう。

しかし…。最初、「testsprite」を「testprite」と間違って打ってしまって、真っ黒なウインドウが表示されるだけの状態になって悩んでしまった。やはり、mruby 側でどんなエラーが発生しているのか、エラー情報を取得して出力する何かが必要になりそうだなと…。そういう機能は絶対ありそうだけど、何をどう調べたらいいのやら。

以上です。

過去ログ表示

Prev - 2018/03 -
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project