Python じゃなくて VisualStudio 側の問題のようなのだけど、setup.py でネイティブモジュールをビルドするときに顕在化したので。

こういう感じのネイティブモジュールを作った。

from distutils.core import setup, Extension


imgui_module = Extension('_swig_imgui',
        sources=[
            #'imgui_wrap.cxx',
            'imgui.i',
            'imgui/imgui.cpp',
            'imgui/imgui_draw.cpp',
            'imgui/imgui_demo.cpp',
            ],
        swig_opts=[
            #'-modern',
            '-c++',
            '-py3',
            ]
        )

setup (name = 'swig_imgui',
        version = '0.1',
        author      = "ousttrue",
        description = """imgui wrapper using swig""",
        ext_modules = [imgui_module],
        py_modules = ["swig_imgui"],
        )
> python setup.py build

とするとエラーになる。 Windows10(64bit)上の vs2017 + vcbuildtools の組み合わせの環境である。

cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -ID:\Anaconda3\include -ID:\Anaconda3\include /EHsc /Tpimgui_wrap.cpp /Fobuild\temp.win-amd64-3.6\Release\imgui_wrap.obj
error: command 'cl.exe' failed: No such file or directory

distutils が cl.exe を探すのに失敗しているようなのである。 lib/distutils 下を調べてみた。 どうやら lib/distutils/_msvccompiler.py で vcvarsall.bat から環境変数を得ることに失敗しているらしい。 実際、C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat の呼び出しが失敗していることを突き止めた。

Error in script usage. The correct usage is:
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat" [option]
or
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat" [option] store
or
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat" [option] [version number]
or
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat" [option] store [version number]
where [option] is: x86 | amd64 | arm | x86_amd64 | x86_arm | amd64_x86 | amd64_arm
where [version number] is either the full Windows 10 SDK version number or "8.1" to use the windows 8.1 SDK
:
The store parameter sets environment variables to support
store (rather than desktop) development.
:
For example:
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat" x86_amd64
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat" x86_arm store
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat" x86_amd64 10.0.10240.0
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat" x86_arm store 10.0.10240.0
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat" x64 8.1
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat" x64 store 8.1

コマンドラインから実行しても失敗していて、vcvarsall.bat に以下のコードがあるのだが、

:setup_buildsku
if not exist "%~dp0..\..\..\Microsoft Visual C++ Build Tools\vcbuildtools.bat" goto usage
set CurrentDir=%CD%
call "%~dp0..\..\..\Microsoft Visual C++ Build Tools\vcbuildtools.bat" %1 %2
cd /d %CurrentDir%
goto :eof

C:\Program Files (x86)\Microsoft Visual Studio\Shared\14.0\VC\vcvarsall.bat から C:\Program Files (x86)\Microsoft Visual C++ Build Tools\vcbuildtools.bat" への相対パスだと一致しないよなーと。 これが原因で cl.exe のパスが取れない。 setup.py にモンキーパッチを当てて cl.exe を発見できるようにしてみた。 以下を setup.py の先頭に追加。

monkey patch for _msvccompiler

import distutils.\_msvccompiler
import os
import subprocess
def \_get_vc_env(plat_spec):
if os.getenv("DISTUTILS_USE_SDK"):
return {
key.lower(): value
for key, value in os.environ.items()
}

    vcvarsall, vcruntime = distutils._msvccompiler._find_vcvarsall(plat_spec)
    if not vcvarsall:
        raise DistutilsPlatformError("Unable to find vcvarsall.bat")

    try:
        out = subprocess.check_output(
            'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),
            stderr=subprocess.STDOUT,
        ).decode('utf-16le', errors='replace')
        #######################################################################
        if out.startswith("Error in script usage"):
            out = subprocess.check_output(
                'cmd /u /c "{}" {} && set'.format("C:\\Program Files (x86)\\Microsoft Visual C++ Build Tools\\vcbuildtools.bat", plat_spec),
                stderr=subprocess.STDOUT,
            ).decode('utf-16le', errors='replace')
        #######################################################################
    except subprocess.CalledProcessError as exc:
        log.error(exc.output)
        raise DistutilsPlatformError("Error executing {}"
                .format(exc.cmd))

    env = {
        key.lower(): value
        for key, _, value in
        (line.partition('=') for line in out.splitlines())
        if key and value
    }

    if vcruntime:
        env['py_vcruntime_redist'] = vcruntime
    return env

distutils.\_msvccompiler.\_get_vc_env=\_get_vc_env

以前にもこんなことやったことあるような気がする・・・。