MSYS2 から Visual C++ (の環境変数を設定するバッチファイル)を叩く

2016年4月26日:「シェルでやる方法」を追記。

Windowsの開発環境には、環境変数を設定するバッチファイルが提供されていて、そのバッチファイルを実行すると PATH とかの環境変数が設定されるというパターンがたまにある。(スタートメニューに「環境変数を設定済みのシェルを起動する」ショートカットを登録するパターンの方がもっと多い気がするが)

MSYS2からこういう開発環境(というか、Visual C++)を叩きたい。もちろん、cmd.exe を使って

$ cmd //c "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat"

みたいなことをすれば vcvars64.bat (Visual C++用に環境変数を設定するバッチファイル)を実行すること自体はできるが、そこで設定された環境変数は呼び出し元のシェルに反映されない。

設定後の環境変数の内容を出力するには、 vcvars64.bat を実行後にMSYS2の env コマンドを実行すれば良い。

$ echo "\"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat\" && env" | xargs cmd //c

わざわざ echo + xargs を使っているのは、シェルの引数の変換規則(エスケープとか)がよく分からなくなったからだ。

env の出力結果を sed とかで加工すれば bash から source するのに適した形式になりそうだが、あまりシェルでいろいろやろうとすると訳が分からなくなりそうなので、スクリプト言語(Lua)を使うことにする。Luaは

$ pacman -S mingw-w64-x86_64-lua

でインストールできる。

で、書いたLuaスクリプトが以下だ。

#!cmd /c C:\\msys64\\mingw64\\bin\\lua.exe

-- 文字列をシェル用にエスケープする
local function escape(s)
  return (string.gsub(s, "[\\|&;()<> \t]", "\\%1"))
end

-- Windows での os.tmpname() はアレっぽい
local TMPDIR = os.getenv("TMP")
local tmpname = string.format("%s\\vc14-shell-%s.sh", TMPDIR, os.tmpname():match("([.%w_-]+)$"))

local f = io.open(tmpname, "w")
local p = assert(io.popen [[cmd /c "( "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat" && C:\msys64\usr\bin\env )"]])
for l in p:lines() do
  local name, value = l:match("^([%w_]+)=(.*)$")
  if name and name ~= "PWD" then
    f:write(string.format("export %s=%s\n", name, escape(value)))
  end
end
p:close()
f:write("export MSYSTEM=VC14\n")
f:close()

io.write(string.format("source %s\n", escape(tmpname)))

これを vcvars64-msvc14.lua みたいなファイル名で保存して、

$ $(./vcvars64-msvc14.lua)

と実行すれば、実行中のシェルで Visual C++ のコマンド(cl 等)が叩けるようになる。

shebang を #!cmd /c ... みたいな形式にしているのは、 #!/mingw64/bin/lua と書くと $(./vcvars64-msvc14.lua) と実行した時になぜか output is not a tty とかいうエラーが出たからだ(cmd /c で実行すれば出ない)。

cl.exe でファイルをコンパイルしてみた例:

$ cat test.cpp
#include <iostream>
#include <string>
class Hoge
{
public:
    virtual ~Hoge() {}
    explicit operator int() { return score(); }
    virtual int score() = 0;
};
class Piyo : public Hoge
{
public:
    virtual ~Piyo() {}
    virtual int score() override { return 334; }
    explicit operator std::string() {
        using namespace std::literals; /* C++14 */
        return "Hello world!"s;
    }
};
int main(int argc, char *argv[]) {
    Piyo piyo;
    // std::string message = piyo; -> an error
    std::cout << std::string(piyo) << std::endl;
    std::cout << static_cast<int>(piyo) << std::endl;
    std::cout << "__cplusplus=" << __cplusplus << std::endl;
    return 0;
}
$ cl //EHsc //nologo test.cpp
test.cpp
$ ./test.exe
Hello world!
334
__cplusplus=199711

シェルでやる方法(2016年4月26日追記)

Lua を使わないで頑張ってシェルで完結させた方が何かと都合がいいことを悟ったので、頑張ってシェルで描き直してみた。

あと、当初の処理だと MANPATH, INFOPATH, PKG_CONFIG_PATH が Windows 仕様になってしまうので、それらは上書きしないようにした。

.bash_profile あたりに

function vcvars64.bat() {
    source <(cmd /c "( \"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat\" && C:\msys64\usr\bin\env )" | grep -P "^(?!PWD=)(?!(MAN|INFO|PKG_CONFIG_)PATH=)\\w+=" | sed -e "s/[\\|&;()<> \t]/\\\&/g" | sed -re "s/^(.+)$/export \1/g")
}

と書いて $ vcvars64.bat を叩けば cl 等が使えるようになる。3行で済むので、他のバッチファイルに対応するシェル関数も容易に用意できる。多分かなり bash に依存している。

あと、 .bash_profile かどこかに

cmd /c "chcp 65001 > NUL"

と書いておけば、 MSBuildcsc の出力が UTF-8 になって文字化けしなくなる。


MSYS2 から Visual C++ (の環境変数を設定するバッチファイル)を叩く” に1件のフィードバックがあります

  1. hiiroakit

    > あと、 .bash_profile かどこかに
    > cmd /c “chcp 65001 > NUL”
    > と書いておけば、 MSBuild や csc の出力が UTF-8 になって文字化けしなくなる。

    上記の点につきまして、筆者様の投稿日以後に状況が変化していると思われます。
    筆者様においては既知の事項かと思われますが、
    当方が確認したところ以下の通りでしたので共有させていただきます。

    1. ~/.bash_profile に cmd /c “chcp 65001 > NUL” を書き加える
    2. C:\msys64\msys2_shell.cmd -mingw64 を実行する
    3. cmd.exe が立ち上がる (ここでは mintty で bash が立ち上がることが期待値と存じます)

コメントは停止中です。