Gauche:scm2exe

Gauche:scm2exe

※ 現在では build-standalone の機能も活用できます。 ただし、この方法はランタイムライブラリを含めてひとつの実行バイナリを形成するために大きな実行ファイルになるので適宜使い分けてください。


Schemeスクリプトでなくバイナリ実行可能ファイルが必要な場面に出会って10分くらいででっちあげたもの。

  scm2exe -o script script.scm

とかやると、Schemeスクリプトscript.scmと同等の動作をするバイナリファイル scriptができる。

(註:実行にはGaucheがインストールされていることが必要。 下のコメント欄の議論参照)


ソース

Shiro(2009/11/22 22:48:24 PST): Gauche 0.9以降では、Cの文字列からScheme スクリプトを読み込んで実行する、という処理をまとめてやってくれるScm_SimpleMain() というAPIがあるのでこれだけ簡単になります。

カスタマイズしたいのでもう少し細かいAPIが見たいという方は リポジトリから履歴をたどってみてください。

#!/usr/bin/env gosh
(use gauche.parseopt)
(use gauche.config)
(use util.match)
(use file.util)

(define p print)

(define (usage) (exit 0 "Usage: scm2exe [-o outfile] <script>"))

(define (main args)
  (let-args (cdr args) ([outfile "o=s"]
                        [help "h|help" => (cut usage)]
                        . args)
    (match args
      [(ifile)
       (let ([ofile (or outfile (path-swap-extension ifile "out"))]
             [cfile (generate-cprog ifile)])
         (compile cfile ofile)
         (sys-unlink cfile))]
      [_ (usage)])
    0))

(define (generate-cprog ifile)
  (receive (out tmpfile) (sys-mkstemp "tmp")
    (with-output-to-port out
      (lambda ()
        (p "#include <gauche.h>")
        (p "static const char *script = ")
        (dolist [line (file->string-list ifile)]
          (format #t "~s ~s\n" line "\n"))
        (p ";")
        (p "int main(int argc, const char **argv)")
        (p "{")
        (p "  Scm_Init(GAUCHE_SIGNATURE);")
        (p "  Scm_SimpleMain(argc, argv, script, 0);") ; this won't return.
        (p "  return 0;")  ; pretend to be a good citizen.
        (p "}")
        ))
    (close-output-port out)
    (rlet1 cfile #`",|tmpfile|.c" (sys-rename tmpfile cfile))))

(define (compile cfile ofile)
  (let* ((cc      (gauche-config "--cc"))
         (cflags  (gauche-config "-I"))
         (ldflags (gauche-config "-L"))
         (libs    (gauche-config "-l"))
         (cmd #`",cc ,cflags -o ,ofile ,ldflags ,cfile ,libs")
         )
    (print cmd)
    (sys-system cmd)))

コメント他

なぜか私の処理系ではこんなエラーが出るんです。

tmp01116a.c: In function `main':
tmp01116a.c:355: too many arguments to function `Scm_LoadFromPort'

メッセージから勘でいじると、

53c53
<         (p "  Scm_LoadFromPort(SCM_PORT(progport));")
---
>         (p "  Scm_LoadFromPort(SCM_PORT(progport), 0);")
77a78
> 

これで一応コンパイルから実行まで確認できましたけど、 関数の引数の違いって何故? こちらは 0.8.1 です。cut-sea:2004/11/28 04:00:33 PST

generate された C の code が欲しかったのでちびっといじってみました。cut-sea:2004/11/28 04:46:52 PST

--- scm2exe.org Sun Nov 28 21:30:30 2004
+++ scm2exe.scm Sun Nov 28 21:26:52 2004
@@ -6,11 +6,12 @@
 (define p print)
 
 (define (usage)
-  (p "Usage: scm2exe [-o outfile] <script>")
+  (p "Usage: scm2exe [-c csource] [-o outfile] <script>")
   (exit 0))
 
 (define (main args)
   (let-args (cdr args) ((outfile "o=s")
+                       (csource "c=s" #f)
                         (help "h|help" => (cut usage))
                         . args)
     (when (null? args) (usage))
@@ -21,7 +22,9 @@
                         #`",|ifile|.out")))
            (cfile (generate-cprog ifile)))
       (compile cfile ofile)
-      (sys-unlink cfile)
+      (if csource
+         (sys-rename cfile csource) 
+       (sys-unlink cfile))
       )))
 
 (define (generate-cprog ifile)
@@ -50,7 +53,7 @@
         (p "  SCM_DEFINE(usermod, \"*program-name*\", SCM_CAR(h));")
         (p "  progstr = SCM_MAKE_STR(script);")
         (p "  progport = Scm_MakeInputStringPort(SCM_STRING(progstr), TRUE);")
-        (p "  Scm_LoadFromPort(SCM_PORT(progport), 0);")
+        (p "  Scm_LoadFromPort(SCM_PORT(progport));")
         (p "  mainproc = Scm_SymbolValue(usermod, SCM_SYMBOL(SCM_INTERN(\"main\")));")
         (p "  if (SCM_PROCEDUREP(mainproc)) {")
         (p "    ScmObj r = Scm_Apply(mainproc, SCM_LIST1(h));")

バイナリ実行可能ファイルが必要な状況ってのがよく分かってませんが、 dynamic link になっているってことは Gauche が インストールされている環境でないと動かないですよね、コレ。

cut-sea@jini> ldd illust-lazy
illust-lazy:
         -lcrypt.0 => /usr/lib/libcrypt.so.0
         -lutil.6 => /usr/lib/libutil.so.6
         -lm.0 => /usr/lib/libm387.so.0
         -lm.0 => /usr/lib/libm.so.0
         -lgauche => /usr/local/lib/libgauche.so      <= Here
         -lc.12 => /usr/lib/libc.so.12

個人的な意見ですが、バイナリで渡す時って、 相手方の環境に元になる言語処理系が無いとか そんな場合の様な事を想像していたので、 static にライブラリを持つようにコンパイル出来た方が 良さげな気がしました。 以前の version の Gauche ってそうでしたよね。 object サイズは大きくなるけど。cut-sea:2004/11/28 15:27:44 PST

Shiro (2004/11/28 16:08:56 PST): そういう目的なら、libgaucheをスタティックリンクするだけじゃなくて ライブラリ等を同梱しないとだめですよね。そこまでやるなら自己解凍なアーカイブを 作ったって同じかなあという気がします。いずれそういうのも必要な場面が 出てくると思うので、その時に何か考えますが。RubyのExerbでしたっけ、そういうやつ。

それとは別に、バイナリである必要がある場面というのは、いくつかあります。 OS依存ですが。

コードを見ればわかるように、これはスクリプトがバイナリのふりをしている ようなものなので、「バイナリであること」以外には何もメリットはありません。 起動が速くなるわけでもないし、単体でインストール可能になるわけでもないです。 そういう目的ではないってことで。

More ...