CaskをWindowsで動作させる

2014/03/18現在、CaskはWindowsでの動作は保証していないようです。
実際、私の環境では動作しませんでした。
しかし、WindowsでもCaskをどうしても使いたかったので、試行錯誤の末、解決策を見つけました。

先に結論

こちらの拙作の拡張を使うことで可能になります。

とはいえ、拡張とか要らないんだよねって人もいると思いますので、 それに至った経緯を備忘録を兼ねてまとめてみようと思います。

まず、私の環境は以下です。

とりあえず、README通りにインストールを試みる

ちなみに、事前にUbuntuではインストールして動作させるのに成功していました。
その時のログは以下のような感じ。

/tmp$ curl -fsSkL https://raw.github.com/cask/cask/master/go | python
Cloning into '/home/hiroaki/.cask'...
remote: Reusing existing pack: 2919, done.
remote: Counting objects: 46, done.
・・・
Contacting host: melpa.milkbox.net:80
Saving file /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/archives/melpa/archive-contents...
Loading vc-svn...
Wrote /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/archives/melpa/archive-contents
・・・
Generating autoloads for s-pkg.el...
Generating autoloads for s-pkg.el...done
Generating autoloads for s.el...
Generating autoloads for s.el...done
Saving file /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/s-20131223.944/s-autoloads.el...
Wrote /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/s-20131223.944/s-autoloads.el
Checking /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/s-20131223.944...
Compiling /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/s-20131223.944/s-autoloads.el...
Compiling /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/s-20131223.944/s-pkg.el...
Wrote /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/s-20131223.944/s-pkg.elc
Compiling /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/s-20131223.944/s.el...
Wrote /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/s-20131223.944/s.elc
Done (Total of 2 files compiled, 1 skipped)
・・・
Contacting host: elpa.gnu.org:80
Saving file /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/archives/gnu/archive-contents...
Wrote /home/hiroaki/.emacs.d/.cask/24.3.1/bootstrap/archives/gnu/archive-contents
Successfully installed Cask!  Now, add the cask binary to your $PATH:
  export PATH="/home/hiroaki/.cask/bin:$PATH"
/tmp$ 

というわけで、Windowsでも同じようにしてみると、以下のエラーが。

~ # curl -fsSkL https://raw.github.com/cask/cask/master/go | python
Cloning into 'C:\home\.cask'...
Traceback (most recent call last):
  File "<stdin>", line 100, in <module>
  File "<stdin>", line 90, in main
  File "<stdin>", line 62, in bootstrap_cask
  File "C:\Dev\python\2.7\lib\subprocess.py", line 506, in check_call
    retcode = call(*popenargs, **kwargs)
  File "C:\Dev\python\2.7\lib\subprocess.py", line 493, in call
    return Popen(*popenargs, **kwargs).wait()
  File "C:\Dev\python\2.7\lib\subprocess.py", line 679, in __init__
    errread, errwrite)
  File "C:\Dev\python\2.7\lib\subprocess.py", line 896, in _execute_child
    startupinfo)
WindowsError: [Error 193] %1 は有効な Win32 アプリケーションではありません。
~ # 

Caskのリポジトリを検索してみると、既にissueが登録されていて、
さらに、この問題を解消できると思われるプルリクも出されていました。

修正されたgoファイルを使ってみる

上記のインストールの処理は、
「CaskのリポジトリにあるgoというPythonのファイルをダウンロードして実行する」ということなので、
このプルリクで修正されているgoファイルをダウンロードして実行してみました。

~ # python go
Cloning into 'C:\home\.cask'...
remote: Reusing existing pack: 2965, done.
remote: Total 2965 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (2965/2965), 815.17 KiB | 255 KiB/s, done.
Resolving deltas: 100% (1659/1659), done.
Traceback (most recent call last):
  File "C:\home\.cask\bin\cask", line 301, in <module>
    main()
  File "C:\home\.cask\bin\cask", line 291, in main
    exec_cask(sys.argv[1:])
  File "C:\home\.cask\bin\cask", line 253, in exec_cask
    emacs = get_cask_emacs()
  File "C:\home\.cask\bin\cask", line 220, in get_cask_emacs
    ensure_supported_emacs(emacs)
  File "C:\home\.cask\bin\cask", line 177, in ensure_supported_emacs
    if not is_supported_emacs(emacs):
  File "C:\home\.cask\bin\cask", line 162, in is_supported_emacs
    return get_emacs_version(emacs) >= MIN_EMACS_VERSION
  File "C:\home\.cask\bin\cask", line 146, in get_emacs_version
    raise ValueError('Could not determine the version of {0}'.format(emacs))
ValueError: Could not determine the version of emacs
Cask could not be bootstrapped. Try again later, or report an issue at https://github.com/cask/cask/issues
~ # 

どうも、Emacsのバージョンについて怒られているように見えます。
goファイルのソースを見てみると、Caskのリポジトリをgit cloneして、 その中のbin/caskをupgradeという引数で実行しているだけのようでした。
で、このcaskというファイルもPythonのソースファイルになっており、試行錯誤の末、 Emacsのバージョンを判別するための正規表現が、NTEmacsの出力するバージョン情報に CRが入っているためマッチしていないということがわかりました。

VERSION_RE = re.compile(r'^GNU Emacs (?P<version>\d+(?:\.\d+)*)$', re.MULTILINE)
#                                                             ↑\rが必要

caskファイルを修正して実行してみる

上記の正規表現を修正して、再度goファイルを実行したのですが、 既にgit cloneしたディレクトリがあるとcaskを実行する前に終了してしまいます。

~ # python go
Directory C:\home\.cask exists. Is Cask already installed?
~ # 

上述の通り、goファイルはgit cloneしてcask upgradeしているだけなので、 修正したcaskファイルを直接実行することにしました。

~ # c:/home/.cask/bin/cask upgrade
~ # Contacting host: melpa.milkbox.net:80

~ # 

すると、すぐ実行が終了してしまい、サーバにアクセスした後の処理が実行されていないように見えます。
emacsのプロセスがもう一つ生成されており、何か処理をし続けているようでした。
これも試行錯誤の末、以下のエントリにまとめた問題があることを発見しました。

NTEmacsのbatch/scriptモードでは、set-process-filterなどの非同期通信処理ができないみたい

package.elのダウンロード処理にcurlを使う

cask upgradeによりpackage.elの機能が呼び出され、その中でmelpaなどのリポジトリからデータやファイルを ダウンロードするためにプロセス通信が行われるため、上記の問題と同じ現象で処理が進まなくなってしまっているようです。
それを回避するために、ダウンロードを実行するurl-retrieve-synchronouslyという関数の挙動を、 curlコマンドを使うように変更しました。

cask upgradeの処理は、単に以下を実行しているだけで、

~ # emacs -Q --script $HOME/.cask/cask-cli.el -- upgrade

cask-cli.elの実行により、上記の問題が起きます。
なので、url-retrieve-synchronouslyの挙動変更を行って、cask-cli.elをロードするラッパーを用意して、
caskファイルはそのラッパーを実行するように変更しました。
caskファイルの変更内容は以下の感じ。

~ # diff .cask/bin/cask.bk .cask/bin/cask 
50c50
< VERSION_RE = re.compile(r'^GNU Emacs (?P<version>\d+(?:\.\d+)*)$', re.MULTILINE)
---
> VERSION_RE = re.compile(r'^GNU Emacs (?P<version>\d+(?:\.\d+)*)\r?$', re.MULTILINE)
254c254
<     cli = os.path.join(CASK_DIRECTORY, 'cask-cli.el')
---
>     cli = os.path.join(CASK_DIRECTORY, 'cask-cli-patch.el')
~ # 

cask-cli-patch.elの定義は以下を参照。

https://github.com/aki2o/caskxy/blob/master/contrib/cask-cli.el

改めてcask upgrade

~ # c:/home/.cask/bin/cask upgrade
~ # Contacting by cURL : http://melpa.milkbox.net/packages/archive-contents
Saving file c:/home/.emacs.d/.cask/24.2.1/bootstrap/archives/melpa/archive-contents...
Loading vc-svn...
Wrote c:/home/.emacs.d/.cask/24.2.1/bootstrap/archives/melpa/archive-contents
Contacting by cURL : http://elpa.gnu.org/packages/archive-contents
Saving file c:/home/.emacs.d/.cask/24.2.1/bootstrap/archives/gnu/archive-contents...
Wrote c:/home/.emacs.d/.cask/24.2.1/bootstrap/archives/gnu/archive-contents
Cannot update Cask because of dirty tree

~ # 

git cloneしたディレクトリのファイルに変更があると、upgradeでエラーになるようにされてました。
なので、別にディレクトリを作って、修正したファイルはそちらに配置し、~/.caskディレクトリは元の状態に戻しました。

~ # mkdir -p .cask.patch/bin
~ # cp .cask/bin/cask .cask.patch/bin/
~ # mv .cask/cask-cli-patch.el .cask.patch/
~ # 

修正した方のcaskファイルでcask upgrade

~ # c:/home/.cask.patch/bin/cask upgrade
~ # Contacting by cURL : http://melpa.milkbox.net/packages/archive-contents
Saving file c:/home/.emacs.d/.cask/24.2.1/bootstrap/archives/melpa/archive-contents...
Loading vc-svn...
Wrote c:/home/.emacs.d/.cask/24.2.1/bootstrap/archives/melpa/archive-contents
・・・
Generating autoloads for s-pkg.el...
Generating autoloads for s-pkg.el...done
Generating autoloads for s.el...
Generating autoloads for s.el...done
Saving file c:/home/.emacs.d/.cask/24.2.1/bootstrap/s-20131223.944/s-autoloads.el...
Wrote c:/home/.emacs.d/.cask/24.2.1/bootstrap/s-20131223.944/s-autoloads.el
Checking c:/home/.emacs.d/.cask/24.2.1/bootstrap/s-20131223.944...
Compiling c:/home/.emacs.d/.cask/24.2.1/bootstrap/s-20131223.944/s-autoloads.el...
Compiling c:/home/.emacs.d/.cask/24.2.1/bootstrap/s-20131223.944/s-pkg.el...
Wrote c:/home/.emacs.d/.cask/24.2.1/bootstrap/s-20131223.944/s-pkg.elc
Compiling c:/home/.emacs.d/.cask/24.2.1/bootstrap/s-20131223.944/s.el...
Wrote c:/home/.emacs.d/.cask/24.2.1/bootstrap/s-20131223.944/s.elc
Done (Total of 2 files compiled, 1 skipped)
・・・
Contacting by cURL : http://melpa.milkbox.net/packages/archive-contents
Saving file c:/home/.emacs.d/.cask/24.2.1/bootstrap/archives/melpa/archive-contents...
Wrote c:/home/.emacs.d/.cask/24.2.1/bootstrap/archives/melpa/archive-contents
Contacting by cURL : http://elpa.gnu.org/packages/archive-contents
Saving file c:/home/.emacs.d/.cask/24.2.1/bootstrap/archives/gnu/archive-contents...
Wrote c:/home/.emacs.d/.cask/24.2.1/bootstrap/archives/gnu/archive-contents

~ # 

やったよ。インストールできた。長かった・・・。

と喜びも束の間・・・

試しに、テストを実行してみたところ、

/cygdrive/c/MyWork/caskxy # c:/home/.cask.patch/bin/cask exec emacs -Q --batch -L . -l test/run-test.el -f batch-expectations
Traceback (most recent call last):
  File "c:/home/.cask.patch/bin/cask", line 301, in <module>
    main()
  File "c:/home/.cask.patch/bin/cask", line 293, in main
    exit_error(error)
  File "c:/home/.cask.patch/bin/cask", line 271, in exit_error
    print('{0}{1}: error: {2}'.format(executable, command, error),
UnicodeDecodeError: 'ascii' codec can't decode byte 0x82 in position 15: invalid start byte
/cygdrive/c/MyWork/caskxy # 

調べると以下のリンクが見つかりましたが、私の環境ではasciiがutf8になっただけでエラーは解消されませんでした。

Python スクリプト実行時に UnicodeDecodeError が出る場合の対処方法 - Over&Out その後

パス形式の問題も存在

ちなみに、caskにパスを通して実行すると、以下のようにパスが見つからないと言われます。

/cygdrive/y/MyWork/caskxy # cask exec emacs -Q --batch -L . -l test/run-test.el -f batch-expectations
C:\Dev\python\2.7\python.exe: can't open file '/cygdrive/c/home/.cask.patch/bin/cask': [Errno 2] No such file or directory
/cygdrive/y/MyWork/caskxy # 

PythonがActivePythonなので、Cygwinのパス形式が認識できないのだと思われます。
Cygwinに入っているpythonを使えばcaskは見つかりますが、
それだと、NTEmacsCygwinのパス形式を認識できないため、-lとかで指定するパスが見つからないと言われます。
goファイルを修正しているプルリクには、batファイルが用意されているので、
それを使えば、コマンドプロンプトからなら、いけるのかも知れませんが、
他の作業はCygwinのシェルを使っているので、Caskだけコマンドプロンプトというのは・・・でした。

という感じで、のたうちまわった末にひらめいた

Pythonを使ってるから、こんな面倒くさい事になってんじゃん!
Pythonの部分って、ラッパーとしての役割ぐらいしかないんだし、Elispで代替すりゃ良いんじゃね!?
となって、拡張を作りましたとさ。