Common Lispでディストリビューションを調べる

Common Lispにはランタイムの環境情報を取ることできる。

基本的には *features* にリスト形式で入っており、OSごとに動作を変えるようなプログラムは#+win32 (some-function args ...) というノリで書く。

具体例として、ブラウザを開く関数定義として

(defparameter +format-string+
  #+(or win32 mswindows windows)
  "explorer ~S"
  #+(or macos darwin)
  "open ~S"
  #-(or win32 mswindows macos darwin windows)
  "xdg-open ~S")

(trivial-open-browserより拝借)

コードを見れば雰囲気は掴めると思うが一応説明しておくと、

  • #+hoge*features*hogeある場合に実行
  • #-hoge*features*hogeない場合に実行

という具合である。

ちなみに環境情報を取ってくる関数なども用意してあり、例えばOSやCPUのアーキテクチャなどを

* (software-type)
; => "Darwin" (macOSの場合)
; => "win32" (Windowsの場合)
; => "Linux" (Linuxの場合)

* (software-version)
; => "19.4.0"

* (machine-type)
; => "X86-64"

というような形で調べることができる。

実はちょっと問題がある

さて、上記のように見た感じだと基本的には問題が無さそうだが、実は一つ困ったことがある。

それは、OSがmacOSWindowsの場合は上記の通りであるが、Linuxの場合はLinuxと出力されることである。

(...別に問題ないのでは?)

まあそのような気持ちになるが、Linuxにも色々あるわけである。

そう、ディストリビューションである。

Ubuntuを始めとしたDebian系だったりCentOSを始めとしたRed Hat系と、まあ色々である。

とりあえずこの使っているディストリビューションが何系なのかをCommon Lispで確認しようと思う。

Common Lispからシェルコマンドを叩く

結論として、シェルコマンドを叩くことで解決する。

Linuxでは

$ cat /etc/*-release

=> 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

によって環境情報を取ってくることができる。

この出力の中で ID がどのディストリか、また ID_LIKE がどの系列のディストリであるかを示している。

ちなみにこの例の出力はUbuntuである(Dockerでやってみた)

さて、Common Lisp (SBCL)でシェルコマンドを叩くためには Quicklisp経由でtrivial-shell を使うか sb-ext:run-program を使う。

ここではtrivial-shellを用いる。

このエントリーを書いたあとに @hyotang666 さんから

  • trivial-shellは関数名をポータブルにしただけで引数や返り値は処理系依存
  • uiopは処理系がすでに提供しているのでquicklispで入れなくていい

の2点から trivial-shell より uiop:run-program を使うべきであると教えていただきました。

ご指摘ありがとうございました。

さて、コードは以下の通りとなる。

;; Quicklispでロード
(ql:quickload :trivial-shell)


;; 文字列を改行ごとに分割してリストへ格納する関数
(defun split (x str)
  (let ((pos (search x str))
        (size (length x)))
    (if pos
      (cons (subseq str 0 pos)
            (split x (subseq str (+ pos size))))
      (list str))))


;; uiopでコマンドを簡略化したもの
(defun system (cmd-str)
  (uiop:run-program cmd-str
                    :output :string))

;; OSがLiinuxである場合にディストリを取ってくる
(defun get-dist ()
  (let ((os-data (split (string #\Newline)
                        (system "cat /etc/*-release"))))
    (loop for i in os-data
          if (search "ID=" i)
          do (return (subseq i 3)))))


(defun get-os ()
  #+(or win32 mswindows windows) ; Windows
  "windows"
  #+(or macos darwin) ; macOS
  "darwin"
  #-(or win32 mswindows macos darwin windows) ;Linux
  (get-dist))

これによってCommon LispLinuxディストリビューションを調べることができる。

まとめ

今回はCommon LispLinuxのディストリを調べる方法をまとめた。

今回のこれの動機として、現在開発中のCommon Lisp用プロッターであるKaiのバックエンドにGRを使えるように拡張しようとしていた。

github.com

その際にGRのバイナリを利用する必要があったのだが、これがディストリビューションごとによって異なるのである。

GRのページ : https://gr-framework.org/c.html

今回ディストリを調べることによってディストリごとにバイナリのダウンロードクライアントを実装することができたので、晴れてこれからバックエンド拡張の実装に取り掛かることができる。

これからはひたすらCFFIで無限にFFIしていくことになりそうなので、気合入れて開発していこうと思う。

最後に、Kaiは現在すでにある程度使えるレベルになっているので、ぜひ触ってみて感想を欲しい。

ついでにスター押して欲しい。