如何用ChezScheme生成单一可执行文件

自从Matthew Flatt的这个PR之后,用ChezScheme生成单一的可执行文件是非常简单的(在此之前,会需要临时文件)。但就我观察,似乎仍有很多人不知道怎么弄的样子,这里做个简单的示范,系统是Ubuntu 16.04,其他系统请自行调整代码。

首先编写一个类似如下的C程序(错误处理略),名字随意,比如main.c

#include "scheme.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>

int main(int argc, char *argv[])
{
  Sscheme_init(NULL);
  int self = open(argv[0], O_RDONLY);
  uint64_t offs[3];
  lseek(self, -24, SEEK_END);
  read(self, offs, 24);
  
  int p = open(argv[0], O_RDONLY);
  lseek(p, offs[0], SEEK_SET);
  Sregister_boot_file_fd("petite", p);
  
  int s = open(argv[0], O_RDONLY);
  lseek(s, offs[1], SEEK_SET);
  Sregister_boot_file_fd("scheme", s);
  
  lseek(self, offs[2], SEEK_SET);
  Sregister_boot_file_fd("self", self);

  Sbuild_heap(argv[0], NULL);

  Scall0(Stop_level_value(Sstring_to_symbol("main")));

  Sscheme_deinit();

  return 0;
}

编译,得到一个a.out(路径请按实际情况修改):

gcc main.c ChezScheme/ta6le/boot/ta6le/kernel.o -IChezScheme/ta6le/boot/ta6le/ -lpthread -lncurses -luuid -ldl -lm

然后编写一个打包用的Scheme程序(pack.ss),boot路径名请自行修改

(define (call/inputs files proc)
  (let rec ([files (reverse files)] [ports '()])
    (if (null? files)
    (apply proc ports)
    (let ([p (open-file-input-port (car files) (file-options) (buffer-mode block) #f)])
      (rec (cdr files) (cons p ports))
      (close-port p)))))

(define (pack a.out boot)
  (call/inputs
   (list a.out boot "ChezScheme/ta6le/boot/ta6le/petite.boot"
     "ChezScheme/ta6le/boot/ta6le/scheme.boot")
   (lambda (a.out boot petite scheme)
     (let ([s1 (file-length a.out)]
       [s2 (file-length petite)]
       [s3 (file-length scheme)]
       [s4 (file-length boot)]
       [shuffix (make-bytevector 24)])
       (bytevector-u64-native-set! shuffix 0 s1)
       (bytevector-u64-native-set! shuffix 8 (+ s1 s2))
       (bytevector-u64-native-set! shuffix 16 (+ s1 s2 s3))
       (let ([b1 (get-bytevector-all a.out)]
         [b2 (get-bytevector-all petite)]
         [b3 (get-bytevector-all scheme)]
         [b4 (get-bytevector-all boot)]) 
        (let ([p (open-file-output-port "main" (file-options no-fail) (buffer-mode block) #f)])
          (put-bytevector p b1)
          (put-bytevector p b2)
          (put-bytevector p b3)
          (put-bytevector p b4)
          (put-bytevector p shuffix)
          (close-port p)))))))
      

现在所有准备工作都完成了

随便写个简单的Scheme程序,文件名就叫foo.ss好了:

(define (main)
  (let loop ()
    (display "->")
    (let ([o (read)])
      (unless (eof-object? o)
        (display (eval o))
        (newline)
        (loop)))))

然后生成boot文件:

> (compile-file "foo.ss")
compiling foo.ss with output to foo.so
> (make-boot-file "foo.boot" '("petite") "foo.so")
> 

打包

> (load "pack.ss")
> (pack "a.out" "foo.boot")
> 

然后来试验一下吧:

$ ./main 
->(#%$closure-ref (let ([x #f]) (set! x "qww6") (lambda () x)) 0)
(qww6 . #<unbound object>)
->

就是这样了,当然自由发挥空间还有很多:例如如果不需要动态编译代码,那么scheme.boot可以不带,可以像Racket on Chez那样使用字符串hack等,这里不一一列举。

发布于 2018-10-29