vagrant環境でtoxしたらハマった件

toxで地雷を踏んで一度死亡したので共有しておきます。
俺の屍を越えてゆけってやつですね。やったことはないが。

何が起こったか

ちょくちょく作っているwalkframeっていうライブラリがあるんですが、それの環境構築をvagrantでやってます。
開発しやすいようにプロジェクトディレクトリをこんな感じで共有しています(Vagrantfile)

config.vm.synced_folder "../", "/home/vagrant/walkframe/",
  create: true, owner: "vagrant", group: "vagrant"

そしてtoxでテスト環境を整えようということで、プロジェクト直下にtox.iniを置いて実行するわけですよ。
すると

(venv) [vagrant@local-walkframe walkframe]$ tox
py35 create: /home/vagrant/walkframe/.tox/py35
ERROR: invocation failed (exit code 1), logfile: /home/vagrant/walkframe/.tox/py35/log/py35-0.log
ERROR: actionid: py35
msg: getenv
cmdargs: ['/home/vagrant/venv/bin/python3.5', '-m', 'virtualenv', '--python', '/home/vagrant/venv/bin/python3.55']
env: {'HOME': '/home/vagrant', 'PATH': '/home/vagrant/walkframe/.tox/py35/bin:/home/vagrant/venv/bin:/usr/localsr/bin:/usr/local/sbin:/usr/sbin:/home/vagrant/.local/bin:/home/vagrant/bin', 'DJANGO_SETTINGS_MODULE': 'settin.pgsql', 'SSH_CLIENT': '10.0.2.2 12706 22', 'HISTSIZE': '1000', 'SELINUX_LEVEL_REQUESTED': '', 'LESSOPEN': '||//lesspipe.sh %s', '_': '/home/vagrant/venv/bin/tox', 'SSH_TTY': '/dev/pts/0', 'LANG': 'en_US.UTF-8', 'SSH_CONNE '10.0.2.2 12706 10.0.2.15 22', 'LOGNAME': 'vagrant', 'SELINUX_ROLE_REQUESTED': '', 'PS1': '(venv) [\\u@\\h \\W 'SHLVL': '1', 'VIRTUAL_ENV': '/home/vagrant/walkframe/.tox/py35', 'TERM': 'xterm', 'SHELL': '/bin/bash', 'PWD'e/vagrant/walkframe', 'XDG_SESSION_ID': '90', 'LS_COLORS': 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=0=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;3201;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01xz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:1;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;m=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;351;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;3501;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01a=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:', 'MAIL': '/var/spool/mail/vagrant', 'HISL': 'ignoredups', 'SELINUX_USE_CURRENT_RANGE': '', 'OLDPWD': '/home/vagrant', 'PYTHONHASHSEED': '1441372689', 'TIME_DIR': '/run/user/1000', 'HOSTNAME': 'local-walkframe', 'USER': 'vagrant'}
 
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/runpy.py", line 170, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/local/lib/python3.5/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/vagrant/venv/lib/python3.5/site-packages/virtualenv.py", line 2332, in <module>
    main()
  File "/home/vagrant/venv/lib/python3.5/site-packages/virtualenv.py", line 711, in main
    symlink=options.symlink)
  File "/home/vagrant/venv/lib/python3.5/site-packages/virtualenv.py", line 924, in create_environment
    site_packages=site_packages, clear=clear, symlink=symlink))
  File "/home/vagrant/venv/lib/python3.5/site-packages/virtualenv.py", line 1369, in install_python
    os.symlink(py_executable_base, full_pth)
OSError: [Errno 71] Protocol error: 'python3.5' -> '/home/vagrant/walkframe/.tox/py35/bin/python'
Already using interpreter /home/vagrant/venv/bin/python3.5
Using base prefix '/usr/local'
New python executable in /home/vagrant/walkframe/.tox/py35/bin/python3.5
Also creating executable in /home/vagrant/walkframe/.tox/py35/bin/python
 
ERROR: InvocationError: /home/vagrant/venv/bin/python3.5 -m virtualenv --python /home/vagrant/venv/bin/python3.(see /home/vagrant/walkframe/.tox/py35/log/py35-0.log)

落ちました。実際は赤いです。

原因

実はこの環境、Windows(Cyngwin)のホストにLinux(CentOS)のゲストが乗っかって成り立っています。

toxはPython環境を分けるため内部でvirtualenvを呼び出します。そして、virtualenvは内部で(デフォルトだと)symlinkを呼び出します。
toxコマンドを実行したカレントディレクトリはWindowsと共有しています。
つまりLinuxはWindowsのファイルシステムに対してvirtualenvのディレクトリ(シンボリックリンク)作成を試みて失敗するというわけです。virtualenvやるだけで落ちるので試してみてください。
LinuxとMacでは発生しませんでした。やっぱWindowsって●だわ

(正確に言うとvagrantではなくvirtualboxによる問題だと思います)

対処

少し調べたらスタックオーバーフロー(英語)さんで対処方法を見つけました。ふむふむ。
なるほど、 virtualenvで –always-copy オプションをつけるとシンボリックリンクじゃなくて実体がコピーされるのでエラーにならないと。
じゃあこれで解決って思うじゃないですか?
でもね、virtualenvのオプションtoxで渡せないんだよね( ´ー`)y-~~

How can I add virtualenv –always-copy options?
ISSUE上がってるみたいだけどステータス新規だ。早く対応してくれ。

というわけでtox.iniの「toxworkdir」変数でvirtualenvのディレクトリが生成されるパスを共有ディレクトリから外しましょう。

toxworkdir = /home/vagrant/

「/home/vagrant/」は共有ディレクトリではないため、エラーがでなくなります。

これで何も怖くない。月曜日だって怖くない。