Cygwin、NTEmacs環境へのel-getのインストールでエラーになる対処についての考察

以前から気になっていたEmacsのパッケージ管理についてちょろちょろ調べてみたところ、 現時点ではel-getが良さ気だったので、el-getを導入しようとして表題の通りエラーになった訳です。

いろいろ書く前に、まず私の環境は以下です。

el-get自体やインストール方法は下記リンクでまとめてくださっている方がいますのでそちらを。

で、遭遇したエラーは以下で、

install-info: No such file or directory for /home/.emacs.d/elisp/el-get/el-get/./el-get.info

下記リンクにある通りGNUWin32のinstall-infoを使うようにすれば良いのですが、 参考にさせて頂いたエントリは、どれもCygwinのinstall-infoを置き換える方法でした。

それだとCygwinからinstall-infoを使うときにまずいんじゃないかという気がしたので、 置き換えではなく、el-getの時だけGNUWin32版を使う対処方法を示しておきます。

GNUWin32版install-infoの配置と設定

私は、Emacsのインストール先のbinフォルダ内に配置することにしました。
適宜読み替えて下さい。

  1. el-get-install-infoという変数でinstall-infoのパスを保持しているので、以下のように設定

    (setq el-get-install-info (concat exec-directory "install-info"))

  2. 下記リンクの説明通り、Binariesのzipを落として解凍し、install-info.exeをEmacsのbinフォルダに配置

  3. Dependenciesのzipに入っているdllをC:\WINDOWS\system32に配置。

これで、Cygwin内の環境は変わらず、el-getからはGNUWin32版が使われる感じになります。

shell-quote-argumentの挙動変更

下記リンクで指摘されているshell-quote-argumentの設定は、ずばり自分もパクって設定していました。 私の場合はlgrepを結局使っていなかったので、この機会にコメントアウトしました。

で、上手くいくかと思いきや、今度は以下のエラーが。

install-info: Invalid argument for c\:/home/.emacs.d/elisp/el-get/el-get/./el-get.info

結論から言うと、私の環境では以下の設定をすることで上手くいきました。

(defadvice el-get-build (around use-quote4windows activate)
  (flet ( (w32-shell-dos-semantics () t))
    ad-do-it))

その理由を、未熟なりにソースを追った結果と、その考察を備忘録も兼ねて以下に記述します。 もし同じ事象でお困りの方は参考にしてみて下さい。

ここから考察(長いっす)

当該エラーを出しているel-getの処理を追いかけてみると、下記リンクの方も仰っている通り、 el-get-start-process-list関数の中で、call-process-shell-commandによりinstall-infoが実行されており、 そこでエラーとなっていました。

install-infoの引数に対して、確かにshell-quote-argument関数を実行していました。 shell-quote-argumentについて、今まであまり理解しないまま使っていたので、 この機会に挙動を調べると、動作環境により以下のようになるようです。

;; Unix環境
(shell-quote-argument "c:/TEMP/el-get test/el-get.info") ; => "c\\:/TEMP/el-get\\ test/el-get.info"
;; Windows環境
(shell-quote-argument "c:/TEMP/el-get test/el-get.info") ; => "\"c:/TEMP/el-get test/el-get.info\""

で、Unix環境かWindows環境かの判断は以下のようになっています。

(or (eq system-type 'ms-dos)
    (and (eq system-type 'windows-nt) (w32-shell-dos-semantics)))

上記のs式が真の場合、Windows環境と判断するようです。
私の環境ではsystem-typeはwindows-ntでした。その場合、w32-shell-dos-semanticsの戻り値に依存し、 そのソースを見ると、幾つかの条件分岐により、戻り値が決定されていました。 その全てをここでは述べませんが、とりあえずexplicit-shell-file-nameの設定値が Windows特有のファイル名だと戻り値が真となり、上記s式も真となっていました。

私は、shell-file-name、explicit-shell-file-name、ともに"bash"にしており、 真とはならず、shell-quote-argumentはUnix環境の挙動をしていました。

call-process-shell-commandは、shell-file-nameを使ってコマンドを実行するので、 bashに対して、shell-quote-argumentがUnix環境の挙動をするのは正しいように見えます。

しかし、el-getの処理と同じようにcall-process-shell-commandを以下のように実行してみると、

(call-process-shell-command
 "c:/App/emacs/bin/install-info" nil t t
 (shell-quote-argument "c:/home/.emacs.d/elisp/el-get/el-get/./el-get.info")
 (shell-quote-argument "dir"))

確かに、同じエラーが返ってきました。
試しに、install-infoでなくlsコマンドで同じことをしてみると、

(call-process-shell-command
 "ls" nil t t
 (shell-quote-argument "c:/home/.emacs.d/elisp/el-get/el-get/./el-get.info")
 (shell-quote-argument "dir"))

今度は上手くいきました。
ということは、GNUWin32版のinstall-infoに渡す引数がUnix環境ではだめぽです。

コマンドプロンプトから試してみると、以下のようになりました。

C:\>bash -c 'ls c\\:/home/.emacs.d/elisp/el-get/el-get/./el-get.info dir'
c:/home/.emacs.d/elisp/el-get/el-get/./el-get.info  dir

C:\>bash -c 'ls "c:/home/.emacs.d/elisp/el-get/el-get/./el-get.info" "dir"'
c:/home/.emacs.d/elisp/el-get/el-get/./el-get.info  dir

C:\>bash -c 'c:/App/emacs/bin/install-info c\\:/home/.emacs.d/elisp/el-get/el-get/./el-get.info dir'
install-info: Invalid argument for c\:/home/.emacs.d/elisp/el-get/el-get/./el-get.info

C:\>bash -c 'c:/App/emacs/bin/install-info "c:/home/.emacs.d/elisp/el-get/el-get/./el-get.info" "dir"'

C:\>

Unixコマンドであるlsは、どちらのエスケープ方式でも受け付けますが、 GNUWin32版のinstall-infoはWindowsライクなエスケープでないといけないようです。

というわけで、el-getの処理でinstall-infoを実行するときには、 shell-quote-argumentはWindowsの挙動をしてもらわなければいけないので、 私の場合は、el-get-buildに以下のようにアドバイスをかますことでel-getのインストールに成功しました。

(defadvice el-get-build (around use-quote4windows activate)
  (flet ( (w32-shell-dos-semantics () t))
    ad-do-it))

まとめ

私の場合、el-getインストール時は以下のelispを評価

(setq el-get-dir "~/.emacs.d/elisp/el-get")
(setq el-get-install-info (concat exec-directory "install-info"))

(defadvice el-get-build (around use-quote4windows activate)
  (flet ( (w32-shell-dos-semantics () t))
    ad-do-it))

(url-retrieve
 "https://raw.github.com/dimitri/el-get/master/el-get-install.el"
 (lambda (s)
   (end-of-buffer)
   (eval-print-last-sexp)))

el-get向け設定として以下を設定ファイルに記述

(setq el-get-dir "~/.emacs.d/elisp/el-get")
(setq el-get-install-info (concat exec-directory "install-info"))

(defadvice el-get-build (around use-quote4windows activate)
  (flet ( (w32-shell-dos-semantics () t))
    ad-do-it))

(require 'el-get)
(el-get 'sync)

となりました。

不明点

上記の調査の中で、ひとつわからないことがありました。
call-process-shell-commandとシェルの機能を使う関数群(例えば、shell-command-to-string)は、 ソースを見ると、どちらも同じようにcall-process関数を使って、シェルを起動しています。

それなのに、以下はエラーが出力されますが、

(call-process-shell-command
 "c:/App/emacs/bin/install-info" nil t t
 (shell-quote-argument "c:/home/.emacs.d/elisp/el-get/el-get/./el-get.info")
 (shell-quote-argument "dir"))

以下はエラーが出力されません。

(shell-command-to-string
 (mapconcat 'identity
            (cons "c:/App/emacs/bin/install-info"
                  (mapcar 'shell-quote-argument
                          (list "c:/home/.emacs.d/elisp/el-get/el-get/./el-get.info" "dir")))
            " "))

ご存知の方がいらっしゃいましたら、ご教授頂けると幸いです。

リンク