evm と Emacs 25.1 とモジュール

Oct 9, 2016  

Emacs 25.1 から elisp 以外の言語で書かれたモジュールを読み込めるようになったらしい。

Emacs can now load shared/dynamic libraries (modules). A dynamic Emacs module is a shared library that provides additional functionality for use in Emacs Lisp programs, just like a package written in Emacs Lisp would.

試してみる、ついでに evm を試したりする。

--with-module がないとモジュールが動かないところではまったりした。

evm で Emacs 25.1 を試す

ひとつの環境に複数のバージョンの Emacs を管理できる evm というツールがあるらしい。 いきなり 25.1 にするのは怖いので evm を入れてみる。

https://github.com/rejeep/evm

readme の通り入れてみた。

$ curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash
$ export PATH="$HOME/.evm/bin:$PATH"

バージョンがあるか確認する。

% evm list | grep 25.1
emacs-25.1
emacs-25.1-travis

あった。

evm はデフォルトで /usr/local/evm にファイルを作るようだったので設定を変更。

% evm config path ~/evm
% mkdir evm
% evm install emacs-25.1

configure で失敗するらしい。

checking for libXaw... configure: error: No X toolkit could be found.
If you are sure you want Emacs compiled without an X toolkit, pass
  --with-x-toolkit=no
to configure.  Otherwise, install the development libraries for the toolkit
that you want to use (e.g. Gtk+) and re-run configure.
Failed! See logs above for error.

この先いろんなライブラリが不足していると言われそうなのでとりあえず、 依存パッケージを落としておく。 これがいいわけではないとおもう。

$ sudo apt-get build-dep emacs24

うまくコンパイルできた。 バイナリで着ている。

$ evm bin emacs-25.1
/home/yourhome/evm/emacs-25.1/bin/emacs
$ evm use emacs-25.1

Emacs が 25.1 で起動できるようになった。

module を動作させるところまで

ここを参考にしてモジュールを作ってみる。 http://diobla.info/blog-archive/modules-tut.html

emacs-module.h を使うらしい。さっき evm に入って中に含まれるのでこれをつかう。

$ cp /home/yourhome/evm/tmp/emacs-25.1/src/emacs-module.h ~/include/

サンプルの mymod.c をコンパイルして mymod.so を作って、 (require 'mymod) したが下記のエラーがでて読み込まれなかった。

Debugger entered--Lisp error: (file-error "Cannot open load file" "そのようなファイルやディレクトリはありません" "mymod")

.el, .elc しか読まれないようになっていそうだった。 よく見たら Emacs のコンパイルで configure の時に --with-modules が必要そうだ。

--with-modules          compile with dynamic modules support

evm でオプションを有効にしたレシピを作ってみた。

% diff -u ~/.evm/recipes/{emacs-25.1,emacs-25.1-with-modules}.rb
--- /home/yomogi/.evm/recipes/emacs-25.1.rb     2016-10-09 15:14:06.824971490 +0900
+++ /home/yomogi/.evm/recipes/emacs-25.1-with-modules.rb        2016-10-10 10:46:54.497478153 +0900
@@ -1,4 +1,4 @@
-recipe 'emacs-25.1' do
+recipe 'emacs-25.1-with-modules' do
   tar_xz 'http://ftpmirror.gnu.org/emacs/emacs-25.1.tar.gz'

   osx do
@@ -12,6 +12,8 @@
     option '--without-gif'
   end

+  option '--with-modules'
+
   install do
     configure
     make 'install'

ちゃんとたされたか確認してコンパイルして、設定。

% evm list | grep emacs-25.1-with-modules
emacs-25.1-with-modules
% evm install emacs-25.1-with-modules
% evm use emacs-25.1-with-modules

起動して動作確認

% emacs -Q -L path/to/module
(require 'mymod)
mymod
(mymod-test)
42

うまく行った。

モジュールを作ってみる

サンプルは、固定値を表示するだけの関数だったので、引数を取るような plusone を作ってみる。モジュール名は tekito にする。 どのような関数が用意されているかは emacs-module.h, emacs-module.c をみながら考えた。

#include <emacs-module.h>

int plugin_is_GPL_compatible;


const int PLUSONE_MAX_ARGS_NUM = 1;
const int PLUSONE_MIN_ARGS_NUM = 1;
const char * const PLUSONE_DOC_STRING = "doc";

static
emacs_value
plusone
(
 emacs_env * env,
 const int nargs,
 const emacs_value const args[],
 void * data
 )
{
  int val;

  val = env->extract_integer(env, args[0]);

  return env->make_integer (env, val + 1);
}

// fset を呼び出して、 name で Sfun が呼ばれるようにする関数
// (fset '{{name}} Sfun) をする
static void
fset
(
 emacs_env * env,
 const char * const name,
 const emacs_value func_obj
 )
{
  static const int FSET_ARGS_NUM = 2;
  emacs_value func_name = env->intern (env, "fset");
  emacs_value func_args[] = { env->intern (env, name), func_obj };

  env->funcall(
                env,
                func_name,
                FSET_ARGS_NUM,
                func_args
               );
}

// provide を呼び出す関数
// (provide '{{feature}}) をする
static void
provide
(
 emacs_env *env,
 const char * const feature
 )
{
  static const int PROVIDE_ARGS_NUM = 1;
  emacs_value func_name = env->intern (env, "provide");
  emacs_value func_args[] = { env->intern (env, feature) };

  env->funcall (
                env,
                func_name,
                PROVIDE_ARGS_NUM,
                func_args
                );
}

int
emacs_module_init
(
 struct emacs_runtime *ert
 )
{
  emacs_env * env = ert->get_environment (ert);
  emacs_value fun =
    env->make_function(
                       env,
                       PLUSONE_MIN_ARGS_NUM,
                       PLUSONE_MAX_ARGS_NUM,
                       (emacs_value (*)())plusone,
                       PLUSONE_DOC_STRING,
                       NULL // よくわかっていない
  );

  fset(env, "plusone", fun);
  provide (env, "tekito");

  return 0;
}

Makefile はこんな感じ

LIBRARY    = $(HOME)/include
CC      = gcc
LD      = gcc
CFLAGS  = -ggdb3 -Wall
LDFLAGS =

all: tekito.so

%.so: %.o
	$(LD) -shared $(LDFLAGS) -o $@ $<

%.o: %.c
	$(CC) $(CFLAGS) -I$(LIBRARY) -fPIC -c $<

Emacs で動作を試してみた。うまく動いた。

emacs -Q -L "${HOME}/Programs/sandbox/20161010/"

scratch バッファでいろいろ。

(require 'tekito)
tekito
(plusone 15)
16
(require 'tekito)
tekito
(plusone 5)
6
(plusone -2000)
-1999

まとめ

  • Emacs 25.1 でモジュールを使うときには、 Emacs をビルドするときに --with-modules のオプションがいる。
  • C と C++ 以外で使えるのかはよくわからなかった。

その他

  • 英語を読むとき昔使っていた firefox のがうまく動かないので、結局 chrome の weblio プラグインにした。