python-zulip-api/tools/provision
Debayan Ganguly 6ac2165bf1
provision: Replace virtualenv with python native venv.
- Replace virtualenv with python 3's native venv feature. The venv used is native to
  python3.5+, so there's no need for a separate dependency.
- Remove redundant activation script. An activation script is required
  to use the pip and python in the virtual environment, but because we're
  calling the pip inside the venv, we don't need one.

Fixes #625.
2021-02-24 18:20:49 -08:00

121 lines
5.3 KiB
Python
Executable file

#!/usr/bin/env python3
import os
import sys
import argparse
import subprocess
import glob
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
ZULIP_BOTS_DIR = os.path.join(CURRENT_DIR, '..', 'zulip_bots')
sys.path.append(ZULIP_BOTS_DIR)
red = '\033[91m'
green = '\033[92m'
end_format = '\033[0m'
bold = '\033[1m'
def main():
usage = """./tools/provision
Creates a Python virtualenv. Its Python version is equal to
the Python version this command is executed with."""
parser = argparse.ArgumentParser(usage=usage)
parser.add_argument('--python-interpreter', '-p',
metavar='PATH_TO_PYTHON_INTERPRETER',
default=os.path.abspath(sys.executable),
help='Path to the Python interpreter to use when provisioning.')
parser.add_argument('--force', '-f', action='store_true',
help='create venv even with outdated Python version.')
options = parser.parse_args()
base_dir = os.path.abspath(os.path.join(__file__, '..', '..'))
py_version_output = subprocess.check_output([options.python_interpreter, '--version'],
stderr=subprocess.STDOUT, universal_newlines=True)
# The output has the format "Python 1.2.3"
py_version_list = py_version_output.split()[1].split('.')
py_version = tuple(int(num) for num in py_version_list[0:2])
venv_name = 'zulip-api-py{}-venv'.format(py_version[0])
if py_version <= (3, 1) and (not options.force):
print(red + "Provision failed: Cannot create venv with outdated Python version ({}).\n"
"Maybe try `python3 tools/provision`."
.format(py_version_output.strip()) + end_format)
sys.exit(1)
venv_dir = os.path.join(base_dir, venv_name)
if not os.path.isdir(venv_dir):
try:
return_code = subprocess.call([options.python_interpreter, '-m', 'venv', venv_dir])
except OSError:
print("{red}Installation with venv failed. Probable errors are: "
"You are on Ubuntu and you haven't installed python3-venv,"
"or you are running an unsupported python version"
"or python is not installed properly{end_format}"
.format(red=red, end_format=end_format))
sys.exit(1)
raise
else:
# subprocess.call returns 0 if a script executed successfully
if return_code:
raise OSError("The command `{} -m venv {}` failed. Virtualenv not created!"
.format(options.python_interpreter, venv_dir))
print("New virtualenv created.")
else:
print("Virtualenv already exists.")
if os.path.isdir(os.path.join(venv_dir, 'Scripts')):
# POSIX compatibility layer and Linux environment emulation for Windows
# venv uses /Scripts instead of /bin on Windows cmd and Power Shell.
# Read https://docs.python.org/3/library/venv.html
venv_exec_dir = 'Scripts'
else:
venv_exec_dir = 'bin'
# On OS X, ensure we use the virtualenv version of the python binary for
# future subprocesses instead of the version that this script was launched with. See
# https://stackoverflow.com/questions/26323852/whats-the-meaning-of-pyvenv-launcher-environment-variable
if '__PYVENV_LAUNCHER__' in os.environ:
del os.environ['__PYVENV_LAUNCHER__']
# In order to install all required packages for the venv, `pip` needs to be executed by
# the venv's Python interpreter. `--prefix venv_dir` ensures that all modules are installed
# in the right place.
def install_dependencies(requirements_filename):
pip_path = os.path.join(venv_dir, venv_exec_dir, 'pip')
# We first install a modern version of pip that supports --prefix
subprocess.call([pip_path, 'install', 'pip>=9.0'])
if subprocess.call([pip_path, 'install', '--prefix', venv_dir, '-r',
os.path.join(base_dir, requirements_filename)]):
raise OSError("The command `pip install -r {}` failed. Dependencies not installed!"
.format(os.path.join(base_dir, requirements_filename)))
install_dependencies('requirements.txt')
# Install all requirements for all bots. get_bot_paths()
# has requirements that must be satisfied prior to calling
# it by setup().
current_dir = os.path.dirname(os.path.abspath(__file__))
bots_dir = os.path.join(current_dir, "..", "zulip_bots", "zulip_bots", "bots")
req_paths = glob.glob(bots_dir + "/*/requirements.txt")
for req_path in req_paths:
path_split = req_path.split(os.path.sep)[-5:]
relative_path = os.path.join(*path_split)
install_dependencies(relative_path)
print(green + 'Success!' + end_format)
activate_command = os.path.join(base_dir,
venv_dir,
venv_exec_dir,
'activate')
# We make the path look like a Unix path, because most Windows users
# are likely to be running in a bash shell.
activate_command = activate_command.replace(os.sep, '/')
print('\nRun the following to enter into the virtualenv:\n')
print(bold + ' source ' + activate_command + end_format + "\n")
if __name__ == '__main__':
main()