CmderでMSYS2の環境を構築してみる
最近、『プログラマーの自己修養』という本を読んでいますが、この本のコード例は基本的にUNIXのものです。しかしすでに最高のCmderに慣れた自分はまたあの黒いBashを使わせるのは多分できかねないでしょう。
そしてこの記事が生まれました!
Cygwin、MinGW+MSYS、MinGW-w64、MSYS2 どっち?
もうタイトルでバレてました(笑)。
MSYS2はモダンなCygwinとMinGW-w64に基づいて発展してきたプロジェクトであり、その核心の部分はMSYSの書き換えたバーションというものです。そして、最も重要なことは、fullバージョンのCmderが含まれるGit for Windows 2.x自身はMSYS2のフォークです。ならばなぜGit for Windowsを直接使わないのですか?その原因は、オリジナルのMSYS2はArch LinuxのPacman(パッケージ管理システム)を移植されます。Pacmanがあれば、gitやgccツールチェーンなどのパッケージを簡単にインストールし、管理することが可能になります。
ちなみに、この節の見出しに列挙されたプロジェクトの関係はちょっと複雑すぎで、検討する価値があります。
私は最初調べる時、GCCのホームページによってMinGWのインストーラをダウンロードしましたが、そのインストーラはMinGWだけでなく、MSYSもインストールして、もう一つのGNU Coreutilesが導入されてしまいました(一つはMSYSのもの、一つはGit for Windowsのもの)。幸いすぐにより良いソリューションを見つけました・・・そう!MinGW-64です。MinGW-64はMinGWの64bitに非対応と両方の食い違いで始められたプロジェクトですが、64bitしか対応しないわけではありません、MinGW-64は実際32bitと64bitをサポートしています。
一方、MSYS2プロジェクトの目標は、最新のCygwinをきっちりと追跡することです。それに対して、MSYSは現在Cygwinに遅れずについていけることができません。こうして見ると、MSYS2がMinGW-w64を選んだのは明らかでしょう。
CmderでMSYS2を起動する方法?
まず、MSYS2を正常に起動する方法を見てみましょう。
MSYS2のインストール先にはmsys2_shell.cmd
という実行ファイルが存在し、同じフォルダでの3つの.exeファイルはそれぞれのオプションを持つmsys2_shell.cmd
のラッパーです。1
2
3msys2.exe --> msys2_shell.cmd -msys
mingw64.exe --> msys2_shell.cmd -mingw64
mingw32.exe --> msys2_shell.cmd -mingw32
続いて、msys2_shell.cmd
の肝心な部分はオプションに応じて環境変数MSYSTEM
を設定することです。その変数は後続のsource
全プロセスに直接影響を与え、/usr/bin内一部の実行ファイルのロジックさえも影響します。MSYSTEM
には、MSYS
、MINGW64
、MINGW32
の三つの値があり、それぞれの値は独立なサブシステムを対応されています。MSYS
は”mostly-POSIX-compliant”なエミュレートされた環境を提供するためのサブシステムです。同時に提供されたmsys-2.0.dll
はCygwinのcygwin1.dll
のようなランタイムライブラリーです。それに対して、MINGW64
とMINGW32
サブシステムは上述のMinGW-w64の環境と見なすといい。その上、MINGW64
はmingw-w64-x86_64-toolchainを、MINGW32
はmingw-w64-i686-toolchainを利用し、コンパイルしたネイティブのWindowsプログラムを提供しています。
Cmderは所詮ConEmuの上にいくつかの機能を加えて構築されたプロジェクトですから、MSYS2の起動問題は「どうやってConEmuで新しいtaskを追加する」になります。msys2_shell.cmd
はユーザーの渡されたオプションを判断して特定ターミナルエミュレーター(ConEmu、Mintty、ConsoleZなど)を起動するがありますが、幸いにConEmuに関するのロジックは特に複雑ではありません、核心コマンドはbash --login -i
だけです。それを踏まえて、以下のコマンドを別々Settings > Startup > Task タブの”Command”フィールドに書き込んで三つの新しいtaskを作りましょう。1
2
3set MSYSTEM=MSYS & cmd /c "%MSYS2_ROOT%\usr\bin\bash --login -i" -new_console:d:%USERPROFILE%
set MSYSTEM=MINGW64 & cmd /c "%MSYS2_ROOT%\usr\bin\bash --login -i" -new_console:d:%USERPROFILE%
set MSYSTEM=MINGW32 & cmd /c "%MSYS2_ROOT%\usr\bin\bash --login -i" -new_console:d:%USERPROFILE%
上述の通り、各コマンドは一つのサブシステムを対応されています。%MSYS2_ROOT%
はMSYS2のインストール先ですが、その環境変数はない場合OSにマニュアルで追加する必要があります。%MSYS2_ROOT%
を絶対パスに置き替えるのもおkです。
デフォルトTaskの設定とTask名前の指定も忘れないでね。
“Cmder Here”ショートカットが壊れた?
CmderレポジトリのReadmeにより、Cmder.exeを格納するフォルダで.\cmder.exe /REGISTER ALL
を実行するとWindowsのコンテキストメニューに”Cmder Here”ショートカットを追加することになります。実はこのショートカットで起動とCmder.exeをクリックで起動を比較すれば、ただ「当セッションで環境変数CMDER_START
が作成される」だけの違いです。本当のcd
操作は%CMDER_ROOT%/vendor/init.bat
のコードで実現させます。1
2
3
4
5:: This is either a env variable set by the user or the result of
:: cmder.exe setting this variable due to a commandline argument or a "cmder here"
if defined CMDER_START (
cd /d "%CMDER_START%"
)
上述のinit.bat
ファイルはネイティブWindowsターミナルcmder.exeのすべてを初期化するとユーザー体験を増加するためですから、MSYS2を利用するために作り出したtask達はそのbatファイルを実行しないのは当然のことです。だから次のステップは上述のコードのロジックをMSYS2のbashの初期化ところへ運ぶことです。
以下のコードをMSYS2のホームディレクトリにある.bashrc
ファイルに追加すれば、壊れた”Cmder Here”ショートカットは直せる。1
2
3
4
5# For "Cmder Here"
if [[ -n ${CMDER_START} ]]; then
echo "Changing into directory: ${CMDER_START}"
cd "${CMDER_START}"
fi
Cmder.exeをクリックで起動やタスクバーのショートカットで起動の場合は、CMDER_START
はセットしないので、ここのNullチェックは不可欠です。こうすれば/Startオプションをショートカットに付加するなどのことが避けられます。しかし、この方法はまだバグがあります。未知の原因で、パーティションのルートディレクトリ直下で”Cmder Here”を実行すると、CMDER_START
が持つパスの末尾はいつも余計のダブルクォートがついています。これはCmder自身の問題のようですから、しばらくおいておきます。
Cmder側のインジェクション
Cmderにはcmder_exinit
ファイルがあります。ディレクトリは%CMDER_ROOT%/vendor/
。用法はこのファイルを適切な拡張子をつけて、/etc/profile.d/
に移動することです。そうすると、MSYS2のシェルはこのファイルを”source”られる。
以下はcmder_exinit
の主な機能です。
- 環境変数
CMDER_ROOT
を作ってCmderに関するディレクトリを環境変数PATH
に追加する。 %CMDER_ROOT%/config/profile.d/
内すべてのファイルと%CMDER_ROOT%/config/user-profile.sh
を”source”する。
そして、%CMDER_ROOT%/bin/
内のバイナリをbashに利用することができる。
もしCmderの”ポータブル”の開発理念に従って、先の節でカスタマイズコードを追加すべき場所は.bashrc
ではなく、user-profile.sh
になります。そうしないと、Cmder全体を別のマシンに移行する時にカスタマイズ内容はなくなります。もちろん、どっちは違うとは言えません、個人的な好みだけです。
もう一つ、git-prompt.sh
ファイルも/etc/profile.d/
へ移動する必要があります。このファイルはGit for Windowsのもので、Gitのブランチ名を表示するとコマンドを補完するためです。Cmderはfull-versionならば、ファイルは%CMDER_ROOT%/vendor/git-for-windows/etc/profile.d/
にある。Cmderはmini-versoinの際、ファイルはまだGit for WindowsのGitHubレポジトリからダウンロードできる。ちなみに、いま言及しているgit-prompt.sh
は本体ではありません。機能を実際に動作させるファイルはgitインストール先でのgit-prompt.sh
とgit-completion.bash
です。上述のgit-prompt.sh
は主に環境変数PS1をセットし、git-prompt.sh
とgit-completion.bash
を”source”します。
git-prompt.sh
のロジックは以下の通り。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22PS1='\[\033]0;$MSYSTEM:${PWD//[^[:ascii:]]/?}\007\]' # set window title
PS1="$PS1"'\[\033[32m\]' # change to green
PS1="$PS1"'\u@\h ' # user@host<space>
PS1="$PS1"'\[\033[33m\]' # change to brownish yellow
PS1="$PS1"'\w' # current working directory
if test -z "$WINELOADERNOEXEC"
then
GIT_EXEC_PATH="$(git --exec-path 2>/dev/null)"
COMPLETION_PATH="${GIT_EXEC_PATH%/libexec/git-core}"
COMPLETION_PATH="${COMPLETION_PATH%/lib/git-core}"
COMPLETION_PATH="$COMPLETION_PATH/share/git/completion"
if test -f "$COMPLETION_PATH/git-prompt.sh"
then
. "$COMPLETION_PATH/git-completion.bash"
. "$COMPLETION_PATH/git-prompt.sh"
PS1="$PS1"'\[\033[36m\]' # change color to cyan
PS1="$PS1"'`__git_ps1`' # bash function
fi
fi
PS1="$PS1"'\[\033[0m\]' # change color
PS1="$PS1"'\n' # new line
PS1="$PS1"'λ ' # prompt: always λ
AliasとPATHをカスタマイズ
Cmder自身は特製のalias機能をcmd.exeに提供しています(詳細はCmderのconfigフォルダでのuser-aliases.cmd
を参考にしてください)。同様に、その特製aliasプリセットを利用したいならコードのロジックを.bashrc
やuser-profile.sh
へ運ぶしかないです。なお、WindowsとLinuxのディレクトリ記述方法やスクリプト文法は違うので、特別扱いの必要があります。
自分が使っているコードは以下の通り。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25alias ll='ls -alh --time-style=long-iso --show-control-chars -F --color $*'
alias e.='explorer .'
alias showpath=" tr ':' '\n' <<< \"\$PATH\" "
# Include Extra Paths
ExtraPaths=(
"${NODE_PATH}"
"${APPDATA}\npm"
)
for currPath in ${ExtraPaths[@]}
do
# Convert path from Windows to Unix format
if [ "$currPath" != "" ] ; then
case "$currPath" in *\\*) currPath="$(cygpath -u "$currPath")";; esac
fi
if [ "$currPath" != "" ] ; then
# Remove any trailing '/'
currPath=$(echo $currPath | sed 's:/*$::')
echo "Adding extra path \"${currPath}\"."
PATH=${currPath}:${PATH}
fi
export PATH
done
unset ExtraPaths