2014-03-01

Windowsのel-getで非同期インストールがエラーになる

el-getをインストールしたので、Wanderlustをel-getでインストールしようとしたらエラーになりました。

次のエラーメッセージが出ました。(gnupack emacs-24.3-20130503 / el-get version 5.1.1dce781)

el-get install apel
el-get: Package apel installed.
el-get: git submodule update ok
el-get: el-get-build apel: c:/app/emacs-24.3-20130503/bin/emacs -batch -q -no-site-file -l APEL-MK -f compile-apel prefix site-lisp site-lisp ok.
el-get: el-get-build apel: c:/app/emacs-24.3-20130503/bin/emacs -batch -q -no-site-file -l APEL-MK -f install-apel prefix site-lisp site-lisp ok.
apel failed to install: (error process byte-compile no longer connected to pipe; closed it) [2 times]
el-get-installation-failed: process byte-compile no longer connected to pipe; closed it

(el-get 'sync 'wanderlust)を評価して同期でインストールしようとするとこのエラーは出ないようなので(別のエラーは出るw)、非同期の時だけの問題のようです。

原因

結論から言うと shell-file-name が "cmdproxy.exe" になっていないことが原因です。
el-getはwindows-ntで実行しているとき、shell-file-nameを強制的に "cmdproxy.exe" に変更してからプロセス呼び出しを行うようになっています。
ただ、この処理がel-get-build関数内にしかないのが問題です。

el-get-build関数は非同期の時、ビルドの完了を待たずに終了します。el-get-build関数内ででいくら

(let* (...略...
       (shell-file-name (or (and (eq system-type 'windows-nt)
                                 (executable-find "cmdproxy.exe"))
                            shell-file-name))
       ...略...)
  ...処理の本体...

のようなコードを書いても、非同期で実行される後続のプロセス呼び出しには適用されません。

shell-file-name が"cmdproxy.exe"でないと、 shell-quote-argument がWindowsのドライブレターを含むパスを正しくクォートできません。
"C:/home/hoge.el"みたいなパスを"C\:/home/hoge.el"のようにしてしまいます。
結果、サブプロセスで呼ばれるemacsは引数で渡されたファイル名を開けずに異常終了します。
そうすると、上のエラーのように、プロセスとパイプがつながらないよ!ということになります。

対策

el-getがサブプロセスを起動するときは必ず shell-file-name が"cmdproxy.exe"になるようにします。

まず、 el-get で使用する shell-file-name を次のようにして決めます。

;;; el-getが使用するshell-file-nameを決める。
;;; el-get-build内(el-get version 5.1.1dce781)で似たような判定をしているが、
;;; その関数内だけでは不十分。
(setq my-el-get-shell-file-name
   (or (and (eq system-type 'windows-nt)
            (executable-find "cmdproxy.exe"))
       shell-file-name))

そして、 el-get-start-process-list 関数を実行するときは、必ずその決めた shell-file-name を使うようにします。

;;; プロセスを呼び出す前に my-el-get-shell-file-name を適用する。
;;;
;;; el-get-build内(el-get version 5.1.1dce781)で同じような処理をしているが、
;;; それでは不十分。非同期インストールの時はel-get-build関数は処理の完了を
;;; 待たずに終わってしまうので。
(defadvice el-get-start-process-list (around my-el-get-start-process-list--modify-shell-file-name activate)
  (let ((shell-file-name my-el-get-shell-file-name))
    ad-do-it))

el-get-start-process-list 関数内でコマンドライン引数に shell-quote-argument が適用されています。
shell-quote-argument は shell-file-name の値によって動作が変わるので、
この関数の中を実行しているときだけ shell-file-name が"cmdproxy.exe"になっていればOKです。

(setq el-get-verbose t) で詳細を表示するようにして、 :args 部分のパスに問題が無ければOKです。