この記事は「Qiita Advent Calendar 2019 DSLで自作ビルドツールを作ろう」の23日目の記事です。
23日目 EmacsプロジェクトをRumyでコンパイルしてみる試行 ~必要な機能は何か~
もうすこし大きなプロジェクトをビルドしてみたいと思います。いろいろと調べてみましたが、LLVMはあまりにも大きすぎて直ぐにできそうにありません。もうすこしステップバイステップでできるものはないか...ということで、これも大きなチャレンジではありますが、EmacsパッケージをRumyでコンパイルしてみることにしました。ここまで大きなプロジェクトであれば、Rumyでビルドしてみると今のRumyに何が足りないのかが見えてくるはずです。Emacsのソースコードとビルド環境は、以下からダウンロードすることができます。
http://ftp.jaist.ac.jp/pub/GNU/emacs/
curl -L https://mirror-hk.koddos.net/gnu/emacs/emacs-26.3.tar.gz | tar xz
ダウンロードして解凍したEmacsを、まずは./configure
からのmake
でMakefileを作ってみます。これで、Rumyへ移行対象となるビルドファイルを作ってみようという訳です。
cd emacs-26.3.tar.gz ./configure make
猛烈におおきなMakefileが出来上がりました。しかもMakefileが複数存在しておりこれは厄介です。少しずつ進めることにします。まずは一番元となるターゲットですが、make all
とすればemacsのバイナリが作れるようです。そして、バイナリを作るために複数のディレクトリを渡ってmakeを実行しており、階層ビルドが使われています。
Makefile
### The nt/ subdirectory gets built only for MinGW NTDIR= ... SUBDIR = $(NTDIR) lib lib-src src lisp all: ${SUBDIR} info lib lib-src lisp nt: Makefile $(MAKE) -C $@ all src: Makefile $(MAKE) -C $@ VCSWITNESS='$(VCSWITNESS)' all
このlib
, lib-src
, lisp
, src
というディレクトリのビルドがキモとなりそうですので、これらをまずはこれらをターゲットにした依存関係ファイルを作っていきます。RumyのExternal-Targetを使用しましょう。
build.rb
external_target "lib", "lib" external_target "lib-src", "lib-src" external_target "src", "src", ["lib-src"] external_target "lisp", "lisp" external_target "info", "info" make_target :all do global depends ["lib", "lib-src", "src", "lisp", "info"] end
しかしここまで作っておいてふと思いました。「おいおいこのサブディレクトリのRumyファイル全部移植しないとコンパイルできないのか?」これはちょっと面倒です。最終的にビルドテストをするまでにかなり時間がかかってしまいます。そこで、最初はサブディレクトリの呼び出しはMakeを使い、サブディレクトリを1つずつRumyに変換していきます。このため、トップディレクトリのRumyファイルからいきなりexternal_target
でサブディレクトリのRumyを呼ぶのではなく、サブディレクトリのMakefileを呼び出す仕組みと作っておきます。external_make
というコマンドを作りました。これはサブディレクトリRumyビルドではなく、Makeビルドを呼び出します。最初はMakeでビルドしておいて、少しずつRumyに移行する、という訳です。
external_make "lib", "lib" external_make "lib-src", "lib-src" external_make "src", "src" external_make "lisp", "lisp" external_make "info", "info" make_target :all do global depends ["lib", "lib-src", "src", "lisp", "info"] end
rumy-target.rb
## ## External Target Make call ## def external_make(lib_name, dir, depends = []) target = Target.new(lib_name) target.executes(["make -C #{dir}"]) target.depends(depends) $target_list[lib_name] = target end
まずはこれで一度ビルドが通りました。次に、大物そうなsrc
ディレクトリを変換していきます。とにかくやることは、Makefileに書いてあることをそのままRumyに移すことです。RumyはMakefileとほぼ同じ機能を持っているので、基本的にすべて移行できるはずです。Makefileの機能(notdir
やbasename
など)は、Rubyの機能(File.basename
など)を使って書き換えていきます。filter
やfilter-out
などの機能も、Rubyのmap
を使って書き換えていきました。
書きながら試行錯誤していると、やはりconfigure
からのMake
は上手くできていると思いました。依存関係のファイルと、各ソースコードから依存関係を抜き出している(?).d
ファイルが作られており、依存関係を上手く抽出しています。Rumyにはない機能で、これを移植するのはかなり骨が折れそうです。Makefileすごい。
deps.mk
の依存関係の記述も、とりあえずひたすら置換することでRumyの依存関係に書き換えていきました。もっと楽に移行できる機能を付けておけばよかった。executes
コマンドとか省略されて入ればデフォルトでgcc立ち上げるとかどうだろう?
deps.mk
... atimer.o: atimer.c atimer.h syssignal.h systime.h lisp.h blockinput.h \ globals.h ../lib/unistd.h msdos.h $(config_h) bidi.o: bidi.c buffer.h character.h dispextern.h msdos.h lisp.h \ globals.h $(config_h) buffer.o: buffer.c buffer.h region-cache.h commands.h window.h \ ...
build.rb
make_target "atimer.o" do executes ["#{CC} #{ALL_CFLAGS} -c #{name} #{name.sub(".o", ".c")}"] depends ["atimer.c", "atimer.h", "syssignal.h", "systime.h", "lisp.h", "blockinput.h", "globals.h", "../lib/unistd.h", "msdos.h", config_h] end make_target "bidi.o" do executes ["#{CC} #{ALL_CFLAGS} -c #{name} #{name.sub(".o", ".c")}"] depends ["bidi.c", "buffer.h", "character.h", "dispextern.h", "msdos.h", "lisp.h", "globals.h", config_h] end make_target "buffer.o" do executes ["#{CC} #{ALL_CFLAGS} -c #{name} #{name.sub(".o", ".c")}"] depends ["buffer.c", "buffer.h", "region-cache.h", "commands.h", "window.h", INTERVALS_H, "blockinput.h", "atimer.h", "systime.h", "character.h", "../lib/unistd.h", "indent.h", "keyboard.h", "coding.h", "keymap.h", "frame.h", "lisp.h", "globals.h", config_h] end
移行作業はまだまだ続く...