#!/usr/bin/env python3 import argparse import glob import os import subprocess import sys 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()