Python, Venv and Cygwin

Update: since originally writing this post, Python has been updated to create Scripts/activate by default (see issue 22343).

A few posts back I went over using the venv module to generate Python virtual environments. Unfortunately there are a couple of complications if you use Cygwin.

Virtual environments under Windows

Assuming you are running Python 3.4 or greater, venv should be part of Python's standard library. Creating an environment works in the same way as Unix-like systems:

python -m venv example_env

There are however a few subtle differences, for example bin/ is called Scripts/. Environments are also activated/deactivated with either a batch or PowerShell script:

C:\Users\bob\Desktop> example_env\Scripts\activate.bat
(example_env) C:\Users\bob\Desktop> example_env\Scripts\deactivate.bat
C:\Users\bob\Desktop>

This works fine if you use cmd or PowerShell. However the bash script to activate the environment is not present. This is a problem if you want to use Cygwin.

$ source example_env/Scripts/activate
bash: example_env/Scripts/activate: No such file or directory

Setting environment variables manually

You can work around this problem by manually setting environment variables:

$ export VIRTUAL_ENV="/c/Users/bob/Desktop/example_env"
$ export PATH="${VIRTUAL_ENV}/Scripts:${PATH}"
$ unset PYTHONHOME

Optionally you can also add the environment to the prompt:

$ export PS1="(example_env) ${PS1}"
(example_env) $

Deactivating the environment is then just a case of reverting the environment variables you changed previously:

(example_env) $ export PS1='$ '
$ export PATH="$(echo $PATH | sed "s|${VIRTUAL_ENV}/Scripts:||")"
$ unset VIRTUAL_ENV

Although the method above works, it's not exactly ideal.

Copying the POSIX script with sed

The script templates for venv can be found in the Python lib/venv/scripts/ directory. The templates are rendered with the following replacements:

text = text.replace('__VENV_DIR__', context.env_dir)
text = text.replace('__VENV_NAME__', context.env_name)
text = text.replace('__VENV_PROMPT__', context.prompt)
text = text.replace('__VENV_BIN_NAME__', context.bin_name)
text = text.replace('__VENV_PYTHON__', context.env_exe)

Of the variables above, only __VENV_DIR__, __VENV_BIN_NAME__ and __VENV_PROMPT__ are used in the POSIX activate script. The following sed command can be used to replace the variables and copy the template:

sed -e 's|__VENV_DIR__|/c/Users/bob/Desktop/example_env|g' \
    -e 's|__VENV_PROMPT__|(example_env) |g' \
    -e 's|__VENV_BIN_NAME__|Scripts|g' \
  /c/Python36/lib/venv/scripts/posix/activate > /c/Users/bob/Desktop/example_env/Scripts/activate

Note: an absolute Cygwin path to the environment is used.

Once the activate script has been created you can activate/deactivate the environment like you would under any other POSIX system:

$ source /c/Users/bob/Desktop/example_env/Scripts/activate
(example_env) $ deactivate
$

Adding activate with Python

Alternatively a short python script can be used to generate the activate script:

$ ./cygwin_venv.py example_env/
Created: "C:\Users\bob\Desktop\example_env\Scripts\activate"

This script uses the ensure_directories and replace_variables functions from the venv module to generate the activate script in the correct place.

Note: the script could have used the setup_scripts function from venv. To do this you have to set os.name to posix to ensure you copy the correct files. This is pretty hacky, and the resulting bash scripts will have windows style line returns (CRLF), hence it was avoided.