testing/vcttesting/environment.py (242 lines of code) (raw):

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import errno import os import shutil import subprocess import sys HERE = os.path.abspath(os.path.dirname(__file__)) ROOT = os.path.normpath(os.path.join(HERE, "..", "..")) CREATE_VIRTUALENV = os.path.join(ROOT, "testing", "create-virtualenv") SITECUSTOMIZE = b""" import os if os.environ.get('CODE_COVERAGE', False): import uuid import coverage covpath = os.path.join(os.environ['COVERAGE_DIR'], 'data', 'coverage.%s' % uuid.uuid1()) cov = coverage.Coverage(data_file=covpath, auto_data=True, branch=True) cov._warn_no_data = False cov._warn_unimported_source = False cov.start() """ def create_virtualenv(name=None, python="python"): path = os.path.join(ROOT, "venv") env = dict(os.environ) env["PYTHON_VERSION"] = python if name: path = os.path.join(path, name) try: os.makedirs(os.path.dirname(path)) except OSError as e: if e.errno != errno.EEXIST: raise if os.name == "nt": bin_dir = os.path.join(path, "Scripts") pip = os.path.join(bin_dir, "pip.exe") python = os.path.join(bin_dir, python + ".exe") activate = os.path.join(bin_dir, "activate") else: bin_dir = os.path.join(path, "bin") pip = os.path.join(bin_dir, "pip") python = os.path.join(bin_dir, python) activate = os.path.join(bin_dir, "activate") res = { "path": path, "bin_dir": bin_dir, "pip": pip, "python": python, "activate": activate, "activate_this": os.path.join(bin_dir, "activate_this.py"), } env["ROOT"] = ROOT env["VENV"] = path if not os.path.exists(res["pip"]): subprocess.check_call([CREATE_VIRTUALENV, path], env=env) # Install a sitecustomize.py that starts code coverage if an environment # variable is set. with open(os.path.join(bin_dir, "sitecustomize.py"), "wb") as fh: fh.write(SITECUSTOMIZE) return res def activate_virtualenv(venv): """Activate a virtualenv in the current Python process.""" with open(venv["activate_this"]) as f: exec(f.read(), dict(__file__=venv["activate_this"])) def process_pip_requirements(venv, requirements): args = [ venv["pip"], "install", "--upgrade", "--require-hashes", "-r", os.path.join(ROOT, requirements), ] subprocess.check_call(args) def install_editable(venv, relpath, extra_env=None): args = [ venv["pip"], "install", "--no-deps", "--editable", os.path.join(ROOT, relpath), ] env = dict(os.environ) env.update(extra_env or {}) subprocess.check_call(args, env=env) def install_mercurials(venv, hg="hg"): """Install supported Mercurial versions in a central location.""" VERSIONS = [ "6.1.4", "6.2.3", "6.3.2", "6.4.3", "6.5.2", "6.6.3", "6.7.4", "6.8.2", "6.9.5", ] hg_dir = os.path.join("/app", "venv", "hg") mercurials = os.path.join(venv["path"], "mercurials") # Setting HGRCPATH to an empty value stops the global and user hgrc from # being loaded. These could interfere with behavior we expect from # vanilla Mercurial. hg_env = dict(os.environ) hg_env["HGRCPATH"] = "" # Ensure a Mercurial clone is present and up to date. if not os.path.isdir(hg_dir): print("cloning Mercurial repository to %s" % hg_dir) subprocess.check_call( [hg, "clone", "https://repo.mercurial-scm.org/hg", hg_dir], cwd="/", env=hg_env, ) subprocess.check_call([hg, "pull"], cwd=hg_dir, env=hg_env) try: os.makedirs(mercurials) except OSError as e: if e.errno != errno.EEXIST: raise # Remove old versions. for p in os.listdir(mercurials): if p in (".", ".."): continue if p not in VERSIONS: print("removing old, unsupported Mercurial version: %s" % p) shutil.rmtree(os.path.join(mercurials, p)) for v in VERSIONS: dest = os.path.join(mercurials, v) if os.path.exists(dest): continue print("installing Mercurial %s to %s" % (v, dest)) try: subprocess.check_output( [hg, "update", v], cwd=hg_dir, env=hg_env, stderr=subprocess.STDOUT ) # We don't care about support files, which only slow down # installation. So install-bin is a suitable target. subprocess.check_output( [ "make", "install-bin", "PREFIX=%s" % dest, "PYTHON=%s" % venv["python"], ], cwd=hg_dir, env=hg_env, stderr=subprocess.STDOUT, ) subprocess.check_output( [hg, "--config", "extensions.purge=", "purge", "--all"], cwd=hg_dir, env=hg_env, stderr=subprocess.STDOUT, ) except subprocess.CalledProcessError as e: print("error installing: %s" % e.output) raise Exception("could not install Mercurial") def docker_client(): """Attempt to obtain a Docker client. Returns a client on success. None on failure. """ from .docker import ( Docker, params_from_env, ) docker_url, tls = params_from_env(os.environ) d = Docker(docker_url, tls=tls) return d if d.is_alive() else None def create_docs(): """Create environment used for building docs.""" venv = create_virtualenv("docs") process_pip_requirements(venv, "docs-requirements.txt") install_editable(venv, "hghooks") install_editable(venv, "pylib/Bugsy") install_editable(venv, "pylib/mozhg") install_editable(venv, "pylib/mozhginfo") install_editable(venv, "pylib/mozautomation") install_editable(venv, "testing") return venv def create_hgdev(): """Create an environment used for hacking on Mercurial extensions.""" venv = create_virtualenv("hgdev") reqs = "testing/requirements-hgdev.txt" process_pip_requirements(venv, reqs) install_editable(venv, "hghooks") install_editable(venv, "pylib/Bugsy") install_editable(venv, "pylib/mozhg") install_editable(venv, "pylib/mozhginfo") install_editable(venv, "pylib/mozautomation") install_editable(venv, "testing") install_mercurials(venv, hg=os.path.join(venv["bin_dir"], "hg")) return venv def install_cinnabar(dest=None): """Install git-cinnabar""" if not dest: dest = os.path.join(ROOT, "venv", "git-cinnabar") if not os.path.exists(dest): subprocess.check_call( [ "git", "clone", "--branch", "release", "https://github.com/glandium/git-cinnabar.git", dest, ] ) subprocess.check_call(["git", "pull"], cwd=dest) subprocess.check_call( [ "make", "-j4", "helper", "NO_OPENSSL=1", "NO_GETTEXT=1", ], cwd=dest, ) def create_global(): """Create the global test environment virtualenv This functions the same as ./create-test-environment """ from .hgmo import ( HgCluster, ) # No `name` parameter since this will be the top-level venv venv_py3 = create_virtualenv(name="py3", python="python3") # Install third-party dependencies process_pip_requirements(venv_py3, "test-requirements-3.txt") # Install editable packages editables = { "hgserver/hgmolib", "pylib/Bugsy", "pylib/mozansible", "pylib/mozhg", "pylib/mozhginfo", "pylib/mozautomation", "pylib/vcsreplicator", "hghooks", "testing", } for package in editables: install_editable(venv_py3, package) install_mercurials(venv_py3) cinnabar_dest = os.path.join(venv_py3["path"], "git-cinnabar") install_cinnabar(dest=cinnabar_dest) if os.getenv("NO_DOCKER"): print("Not building Docker images because NO_DOCKER is set") else: print("Building Docker images.") print("This could take a while and may consume a lot of internet bandwidth.") print( "If you don't want Docker images, it is safe to hit CTRL+c to abort this." ) try: HgCluster.build() except subprocess.CalledProcessError: print("You will not be able to run tests that require Docker.") print( "Please see https://docs.docker.com/installation/ for how to install Docker." ) print("When Docker is installed, re-run this script") print( "To avoid re-building the Docker images next time, set the `NO_DOCKER` " "environment variable to any value." ) sys.exit(1) return venv_py3 if __name__ == "__main__": import sys if sys.argv[1] != "install-mercurials": sys.exit(1) venv = { "path": os.path.join("/app", "venv"), "python": os.path.join("/app", "venv", "bin", "python"), } install_mercurials(venv, hg="hg") sys.exit(0)