1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5# This file contains code for populating the virtualenv environment for 6# Mozilla's build system. It is typically called as part of configure. 7 8from __future__ import absolute_import, print_function, unicode_literals 9 10import os 11import shutil 12import subprocess 13import sys 14 15 16IS_NATIVE_WIN = (sys.platform == 'win32' and os.sep == '\\') 17IS_CYGWIN = (sys.platform == 'cygwin') 18 19PY2 = sys.version_info[0] == 2 20PY3 = sys.version_info[0] == 3 21 22UPGRADE_WINDOWS = ''' 23Please upgrade to the latest MozillaBuild development environment. See 24https://developer.mozilla.org/en-US/docs/Developer_Guide/Build_Instructions/Windows_Prerequisites 25'''.lstrip() 26 27UPGRADE_OTHER = ''' 28Run |mach bootstrap| to ensure your system is up to date. 29 30If you still receive this error, your shell environment is likely detecting 31another Python version. Ensure a modern Python can be found in the paths 32defined by the $PATH environment variable and try again. 33'''.lstrip() 34 35here = os.path.abspath(os.path.dirname(__file__)) 36 37 38class VirtualenvManager(object): 39 """Contains logic for managing virtualenvs for building the tree.""" 40 41 def __init__(self, topsrcdir, topobjdir, virtualenv_path, log_handle, 42 manifest_path): 43 """Create a new manager. 44 45 Each manager is associated with a source directory, a path where you 46 want the virtualenv to be created, and a handle to write output to. 47 """ 48 # __PYVENV_LAUNCHER__ confuses pip, telling it to use the system 49 # python interpreter rather than the local virtual environment interpreter. 50 # See https://bugzilla.mozilla.org/show_bug.cgi?id=1607470 51 os.environ.pop('__PYVENV_LAUNCHER__', None) 52 53 assert os.path.isabs( 54 manifest_path), "manifest_path must be an absolute path: %s" % (manifest_path) 55 self.topsrcdir = topsrcdir 56 self.topobjdir = topobjdir 57 self.virtualenv_root = virtualenv_path 58 59 # Record the Python executable that was used to create the Virtualenv 60 # so we can check this against sys.executable when verifying the 61 # integrity of the virtualenv. 62 self.exe_info_path = os.path.join(self.virtualenv_root, 63 'python_exe.txt') 64 65 self.log_handle = log_handle 66 self.manifest_path = manifest_path 67 68 @property 69 def virtualenv_script_path(self): 70 """Path to virtualenv's own populator script.""" 71 return os.path.join(self.topsrcdir, 'third_party', 'python', 72 'virtualenv', 'virtualenv.py') 73 74 @property 75 def bin_path(self): 76 # virtualenv.py provides a similar API via path_locations(). However, 77 # we have a bit of a chicken-and-egg problem and can't reliably 78 # import virtualenv. The functionality is trivial, so just implement 79 # it here. 80 if IS_CYGWIN or IS_NATIVE_WIN: 81 return os.path.join(self.virtualenv_root, 'Scripts') 82 83 return os.path.join(self.virtualenv_root, 'bin') 84 85 @property 86 def python_path(self): 87 binary = 'python' 88 if sys.platform in ('win32', 'cygwin'): 89 binary += '.exe' 90 91 return os.path.join(self.bin_path, binary) 92 93 @property 94 def version_info(self): 95 return eval(subprocess.check_output([ 96 self.python_path, '-c', 'import sys; print(sys.version_info[:])'])) 97 98 @property 99 def activate_path(self): 100 return os.path.join(self.bin_path, 'activate_this.py') 101 102 def get_exe_info(self): 103 """Returns the version and file size of the python executable that was in 104 use when this virtualenv was created. 105 """ 106 with open(self.exe_info_path, 'r') as fh: 107 version, size = fh.read().splitlines() 108 return int(version), int(size) 109 110 def write_exe_info(self, python): 111 """Records the the version of the python executable that was in use when 112 this virtualenv was created. We record this explicitly because 113 on OS X our python path may end up being a different or modified 114 executable. 115 """ 116 ver = self.python_executable_hexversion(python) 117 with open(self.exe_info_path, 'w') as fh: 118 fh.write("%s\n" % ver) 119 fh.write("%s\n" % os.path.getsize(python)) 120 121 def python_executable_hexversion(self, python): 122 """Run a Python executable and return its sys.hexversion value.""" 123 program = 'import sys; print(sys.hexversion)' 124 out = subprocess.check_output([python, '-c', program]).rstrip() 125 return int(out) 126 127 def up_to_date(self, python): 128 """Returns whether the virtualenv is present and up to date. 129 130 Args: 131 python: Full path string to the Python executable that this virtualenv 132 should be running. If the Python executable passed in to this 133 argument is not the same version as the Python the virtualenv was 134 built with then this method will return False. 135 """ 136 137 deps = [self.manifest_path, __file__] 138 139 # check if virtualenv exists 140 if not os.path.exists(self.virtualenv_root) or \ 141 not os.path.exists(self.activate_path): 142 return False 143 144 # Modifications to our package dependency list or to this file mean the 145 # virtualenv should be rebuilt. 146 activate_mtime = os.path.getmtime(self.activate_path) 147 dep_mtime = max(os.path.getmtime(p) for p in deps) 148 if dep_mtime > activate_mtime: 149 return False 150 151 # Verify that the Python we're checking here is either the virutalenv 152 # python, or we have the Python version that was used to create the 153 # virtualenv. If this fails, it is likely system Python has been 154 # upgraded, and our virtualenv would not be usable. 155 orig_version, orig_size = self.get_exe_info() 156 python_size = os.path.getsize(python) 157 hexversion = self.python_executable_hexversion(python) 158 if ((python, python_size) != (self.python_path, os.path.getsize(self.python_path)) and 159 (hexversion, python_size) != (orig_version, orig_size)): 160 return False 161 162 # recursively check sub packages.txt files 163 submanifests = [i[1] for i in self.packages() 164 if i[0] == 'packages.txt'] 165 for submanifest in submanifests: 166 submanifest = os.path.join(self.topsrcdir, submanifest) 167 submanager = VirtualenvManager(self.topsrcdir, 168 self.topobjdir, 169 self.virtualenv_root, 170 self.log_handle, 171 submanifest) 172 if not submanager.up_to_date(python): 173 return False 174 175 return True 176 177 def ensure(self, python=sys.executable): 178 """Ensure the virtualenv is present and up to date. 179 180 If the virtualenv is up to date, this does nothing. Otherwise, it 181 creates and populates the virtualenv as necessary. 182 183 This should be the main API used from this class as it is the 184 highest-level. 185 """ 186 if self.up_to_date(python): 187 return self.virtualenv_root 188 return self.build(python) 189 190 def _log_process_output(self, *args, **kwargs): 191 env = kwargs.pop('env', None) or os.environ.copy() 192 # PYTHONEXECUTABLE can mess up the creation of virtualenvs when set. 193 env.pop('PYTHONEXECUTABLE', None) 194 kwargs['env'] = ensure_subprocess_env(env) 195 196 if hasattr(self.log_handle, 'fileno'): 197 return subprocess.call(*args, stdout=self.log_handle, 198 stderr=subprocess.STDOUT, **kwargs) 199 200 proc = subprocess.Popen(*args, stdout=subprocess.PIPE, 201 stderr=subprocess.STDOUT, **kwargs) 202 203 for line in proc.stdout: 204 if PY2: 205 self.log_handle.write(line) 206 else: 207 self.log_handle.write(line.decode('UTF-8')) 208 209 return proc.wait() 210 211 def create(self, python): 212 """Create a new, empty virtualenv. 213 214 Receives the path to virtualenv's virtualenv.py script (which will be 215 called out to), the path to create the virtualenv in, and a handle to 216 write output to. 217 """ 218 219 # Corner-case: in some cases, we call this function even though there's 220 # already a virtualenv in place in `self.virtualenv_root`. That is not a 221 # problem in itself, except when not using the same `python`. For example: 222 # - the old virtualenv was created with `/usr/bin/pythonx.y` 223 # - as such, in contains `pythonx.y` as a file, and `python` and `pythonx` 224 # as symbolic links to `pythonx.y` 225 # - the new virtualenv is being created with `/usr/bin/pythonx` 226 # - the virtualenv script uses shutil.copyfile to copy `/usr/bin/pythonx` 227 # to `pythonx` in the virtualenv. As that is an existing symbolic link, 228 # the copy ends up writing the file into `pythonx.y`. 229 # - the virtualenv script then creates `python` and `pythonx.y` symbolic 230 # links to `pythonx`. `pythonx` is still a symbolic link to `pythonx.y`, 231 # and now `pythonx.y` is a symbolic link to `pythonx`, so we end with a 232 # symbolic link loop, and no real python executable around. 233 # So if the file with the same name as the python executable used to create 234 # the new virtualenv is a symbolic link, remove it before invoking 235 # virtualenv. 236 venv_python = os.path.join(self.bin_path, os.path.basename(python)) 237 if os.path.islink(venv_python): 238 os.remove(venv_python) 239 240 args = [python, self.virtualenv_script_path, 241 # Without this, virtualenv.py may attempt to contact the outside 242 # world and search for or download a newer version of pip, 243 # setuptools, or wheel. This is bad for security, reproducibility, 244 # and speed. 245 '--no-download', 246 self.virtualenv_root] 247 248 result = self._log_process_output(args) 249 250 if result: 251 raise Exception( 252 'Failed to create virtualenv: %s (virtualenv.py retcode: %s)' % ( 253 self.virtualenv_root, result)) 254 255 self.write_exe_info(python) 256 257 return self.virtualenv_root 258 259 def packages(self): 260 mode = 'rU' if PY2 else 'r' 261 with open(self.manifest_path, mode) as fh: 262 packages = [line.rstrip().split(':') 263 for line in fh] 264 return packages 265 266 def populate(self): 267 """Populate the virtualenv. 268 269 The manifest file consists of colon-delimited fields. The first field 270 specifies the action. The remaining fields are arguments to that 271 action. The following actions are supported: 272 273 setup.py -- Invoke setup.py for a package. Expects the arguments: 274 1. relative path directory containing setup.py. 275 2. argument(s) to setup.py. e.g. "develop". Each program argument 276 is delimited by a colon. Arguments with colons are not yet 277 supported. 278 279 filename.pth -- Adds the path given as argument to filename.pth under 280 the virtualenv site packages directory. 281 282 optional -- This denotes the action as optional. The requested action 283 is attempted. If it fails, we issue a warning and go on. The 284 initial "optional" field is stripped then the remaining line is 285 processed like normal. e.g. 286 "optional:setup.py:python/foo:built_ext:-i" 287 288 copy -- Copies the given file in the virtualenv site packages 289 directory. 290 291 packages.txt -- Denotes that the specified path is a child manifest. It 292 will be read and processed as if its contents were concatenated 293 into the manifest being read. 294 295 objdir -- Denotes a relative path in the object directory to add to the 296 search path. e.g. "objdir:build" will add $topobjdir/build to the 297 search path. 298 299 windows -- This denotes that the action should only be taken when run 300 on Windows. 301 302 !windows -- This denotes that the action should only be taken when run 303 on non-Windows systems. 304 305 python3 -- This denotes that the action should only be taken when run 306 on Python 3. 307 308 python2 -- This denotes that the action should only be taken when run 309 on python 2. 310 311 Note that the Python interpreter running this function should be the 312 one from the virtualenv. If it is the system Python or if the 313 environment is not configured properly, packages could be installed 314 into the wrong place. This is how virtualenv's work. 315 """ 316 import distutils.sysconfig 317 318 packages = self.packages() 319 python_lib = distutils.sysconfig.get_python_lib() 320 321 def handle_package(package): 322 if package[0] == 'setup.py': 323 assert len(package) >= 2 324 325 self.call_setup(os.path.join(self.topsrcdir, package[1]), 326 package[2:]) 327 328 return True 329 330 if package[0] == 'copy': 331 assert len(package) == 2 332 333 src = os.path.join(self.topsrcdir, package[1]) 334 dst = os.path.join(python_lib, os.path.basename(package[1])) 335 336 shutil.copy(src, dst) 337 338 return True 339 340 if package[0] == 'packages.txt': 341 assert len(package) == 2 342 343 src = os.path.join(self.topsrcdir, package[1]) 344 assert os.path.isfile(src), "'%s' does not exist" % src 345 submanager = VirtualenvManager(self.topsrcdir, 346 self.topobjdir, 347 self.virtualenv_root, 348 self.log_handle, 349 src) 350 submanager.populate() 351 352 return True 353 354 if package[0].endswith('.pth'): 355 assert len(package) == 2 356 357 path = os.path.join(self.topsrcdir, package[1]) 358 359 with open(os.path.join(python_lib, package[0]), 'a') as f: 360 # This path is relative to the .pth file. Using a 361 # relative path allows the srcdir/objdir combination 362 # to be moved around (as long as the paths relative to 363 # each other remain the same). 364 try: 365 f.write("%s\n" % os.path.relpath(path, python_lib)) 366 except ValueError: 367 # When objdir is on a separate drive, relpath throws 368 f.write("%s\n" % os.path.join(python_lib, path)) 369 370 return True 371 372 if package[0] == 'optional': 373 try: 374 handle_package(package[1:]) 375 return True 376 except Exception: 377 print('Error processing command. Ignoring', 378 'because optional. (%s)' % ':'.join(package), 379 file=self.log_handle) 380 return False 381 382 if package[0] in ('windows', '!windows'): 383 for_win = not package[0].startswith('!') 384 is_win = sys.platform == 'win32' 385 if is_win == for_win: 386 handle_package(package[1:]) 387 return True 388 389 if package[0] in ('python2', 'python3'): 390 for_python3 = package[0].endswith('3') 391 if PY3 == for_python3: 392 handle_package(package[1:]) 393 return True 394 395 if package[0] == 'objdir': 396 assert len(package) == 2 397 path = os.path.join(self.topobjdir, package[1]) 398 399 with open(os.path.join(python_lib, 'objdir.pth'), 'a') as f: 400 f.write('%s\n' % path) 401 402 return True 403 404 raise Exception('Unknown action: %s' % package[0]) 405 406 # We always target the OS X deployment target that Python itself was 407 # built with, regardless of what's in the current environment. If we 408 # don't do # this, we may run into a Python bug. See 409 # http://bugs.python.org/issue9516 and bug 659881. 410 # 411 # Note that this assumes that nothing compiled in the virtualenv is 412 # shipped as part of a distribution. If we do ship anything, the 413 # deployment target here may be different from what's targeted by the 414 # shipping binaries and # virtualenv-produced binaries may fail to 415 # work. 416 # 417 # We also ignore environment variables that may have been altered by 418 # configure or a mozconfig activated in the current shell. We trust 419 # Python is smart enough to find a proper compiler and to use the 420 # proper compiler flags. If it isn't your Python is likely broken. 421 IGNORE_ENV_VARIABLES = ('CC', 'CXX', 'CFLAGS', 'CXXFLAGS', 'LDFLAGS') 422 423 try: 424 old_target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', None) 425 sysconfig_target = \ 426 distutils.sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') 427 428 if sysconfig_target is not None: 429 # MACOSX_DEPLOYMENT_TARGET is usually a string (e.g.: "10.14.6"), but 430 # in some cases it is an int (e.g.: 11). Since environment variables 431 # must all be str, explicitly convert it. 432 os.environ["MACOSX_DEPLOYMENT_TARGET"] = str(sysconfig_target) 433 434 old_env_variables = {} 435 for k in IGNORE_ENV_VARIABLES: 436 if k not in os.environ: 437 continue 438 439 old_env_variables[k] = os.environ[k] 440 del os.environ[k] 441 442 for package in packages: 443 handle_package(package) 444 445 sitecustomize = os.path.join( 446 os.path.dirname(python_lib), 'sitecustomize.py') 447 with open(sitecustomize, 'w') as f: 448 f.write( 449 '# Importing mach_bootstrap has the side effect of\n' 450 '# installing an import hook\n' 451 'import mach_bootstrap\n' 452 ) 453 454 finally: 455 os.environ.pop('MACOSX_DEPLOYMENT_TARGET', None) 456 457 if old_target is not None: 458 os.environ['MACOSX_DEPLOYMENT_TARGET'] = old_target 459 460 os.environ.update(old_env_variables) 461 462 def call_setup(self, directory, arguments): 463 """Calls setup.py in a directory.""" 464 setup = os.path.join(directory, 'setup.py') 465 466 program = [self.python_path, setup] 467 program.extend(arguments) 468 469 # We probably could call the contents of this file inside the context 470 # of this interpreter using execfile() or similar. However, if global 471 # variables like sys.path are adjusted, this could cause all kinds of 472 # havoc. While this may work, invoking a new process is safer. 473 474 try: 475 output = subprocess.check_output(program, cwd=directory, stderr=subprocess.STDOUT) 476 print(output) 477 except subprocess.CalledProcessError as e: 478 if 'Python.h: No such file or directory' in e.output: 479 print('WARNING: Python.h not found. Install Python development headers.') 480 else: 481 print(e.output) 482 483 raise Exception('Error installing package: %s' % directory) 484 485 def build(self, python): 486 """Build a virtualenv per tree conventions. 487 488 This returns the path of the created virtualenv. 489 """ 490 491 self.create(python) 492 493 # We need to populate the virtualenv using the Python executable in 494 # the virtualenv for paths to be proper. 495 496 # If this module was run from Python 2 then the __file__ attribute may 497 # point to a Python 2 .pyc file. If we are generating a Python 3 498 # virtualenv from Python 2 make sure we call Python 3 with the path to 499 # the module and not the Python 2 .pyc file. 500 if os.path.splitext(__file__)[1] in ('.pyc', '.pyo'): 501 thismodule = __file__[:-1] 502 else: 503 thismodule = __file__ 504 505 args = [self.python_path, thismodule, 'populate', self.topsrcdir, 506 self.topobjdir, self.virtualenv_root, self.manifest_path] 507 508 result = self._log_process_output(args, cwd=self.topsrcdir) 509 510 if result != 0: 511 raise Exception('Error populating virtualenv.') 512 513 os.utime(self.activate_path, None) 514 515 return self.virtualenv_root 516 517 def activate(self): 518 """Activate the virtualenv in this Python context. 519 520 If you run a random Python script and wish to "activate" the 521 virtualenv, you can simply instantiate an instance of this class 522 and call .ensure() and .activate() to make the virtualenv active. 523 """ 524 525 exec(open(self.activate_path).read(), dict(__file__=self.activate_path)) 526 if PY2 and isinstance(os.environ['PATH'], unicode): 527 os.environ['PATH'] = os.environ['PATH'].encode('utf-8') 528 529 def install_pip_package(self, package, vendored=False): 530 """Install a package via pip. 531 532 The supplied package is specified using a pip requirement specifier. 533 e.g. 'foo' or 'foo==1.0'. 534 535 If the package is already installed, this is a no-op. 536 537 If vendored is True, no package index will be used and no dependencies 538 will be installed. 539 """ 540 from pip._internal.req.constructors import install_req_from_line 541 542 req = install_req_from_line(package) 543 req.check_if_exists(use_user_site=False) 544 if req.satisfied_by is not None: 545 return 546 547 args = [ 548 'install', 549 package, 550 ] 551 552 if vendored: 553 args.extend([ 554 '--no-deps', 555 '--no-index', 556 # The setup will by default be performed in an isolated build 557 # environment, and since we're running with --no-index, this 558 # means that pip will be unable to install in the isolated build 559 # environment any dependencies that might be specified in a 560 # setup_requires directive for the package. Since we're manually 561 # controlling our build environment, build isolation isn't a 562 # concern and we can disable that feature. Note that this is 563 # safe and doesn't risk trampling any other packages that may be 564 # installed due to passing `--no-deps --no-index` as well. 565 '--no-build-isolation', 566 ]) 567 568 return self._run_pip(args) 569 570 def install_pip_requirements(self, path, require_hashes=True, quiet=False, vendored=False): 571 """Install a pip requirements.txt file. 572 573 The supplied path is a text file containing pip requirement 574 specifiers. 575 576 If require_hashes is True, each specifier must contain the 577 expected hash of the downloaded package. See: 578 https://pip.pypa.io/en/stable/reference/pip_install/#hash-checking-mode 579 """ 580 581 if not os.path.isabs(path): 582 path = os.path.join(self.topsrcdir, path) 583 584 args = [ 585 'install', 586 '--requirement', 587 path, 588 ] 589 590 if require_hashes: 591 args.append('--require-hashes') 592 593 if quiet: 594 args.append('--quiet') 595 596 if vendored: 597 args.extend([ 598 '--no-deps', 599 '--no-index', 600 ]) 601 602 return self._run_pip(args) 603 604 def _run_pip(self, args): 605 # It's tempting to call pip natively via pip.main(). However, 606 # the current Python interpreter may not be the virtualenv python. 607 # This will confuse pip and cause the package to attempt to install 608 # against the executing interpreter. By creating a new process, we 609 # force the virtualenv's interpreter to be used and all is well. 610 # It /might/ be possible to cheat and set sys.executable to 611 # self.python_path. However, this seems more risk than it's worth. 612 pip = os.path.join(self.bin_path, 'pip') 613 subprocess.check_call([pip] + args, stderr=subprocess.STDOUT, cwd=self.topsrcdir, 614 universal_newlines=PY3) 615 616 def activate_pipenv(self, pipfile=None, populate=False, python=None): 617 """Activate a virtual environment managed by pipenv 618 619 If ``pipfile`` is not ``None`` then the Pipfile located at the path 620 provided will be used to create the virtual environment. If 621 ``populate`` is ``True`` then the virtual environment will be 622 populated from the manifest file. The optional ``python`` argument 623 indicates the version of Python for pipenv to use. 624 """ 625 pipenv = os.path.join(self.bin_path, 'pipenv') 626 env = ensure_subprocess_env(os.environ.copy()) 627 env.update(ensure_subprocess_env({ 628 'PIPENV_IGNORE_VIRTUALENVS': '1', 629 'PIP_NO_INDEX': '1', 630 'WORKON_HOME': str(os.path.normpath(os.path.join(self.topobjdir, '_virtualenvs'))) 631 })) 632 # On mac, running pipenv with LC_CTYPE set to "UTF-8" (which happens 633 # when wrapping with run-task on automation) fails. 634 # Unsetting it doesn't really matter for what pipenv does. 635 env.pop('LC_CTYPE', None) 636 637 # Avoid click RuntimeError under python 3 on linux: http://click.pocoo.org/python3/ 638 if PY3 and sys.platform == 'linux': 639 env.update(ensure_subprocess_env({ 640 'LC_ALL': 'C.UTF-8', 641 'LANG': 'C.UTF-8' 642 })) 643 644 if python is not None: 645 env.update(ensure_subprocess_env({ 646 'PIPENV_DEFAULT_PYTHON_VERSION': str(python), 647 'PIPENV_PYTHON': str(python) 648 })) 649 650 def ensure_venv(): 651 """Create virtual environment if needed and return path""" 652 venv = get_venv() 653 if venv is not None: 654 return venv 655 if python is not None: 656 subprocess.check_call( 657 [pipenv, '--python', python], 658 stderr=subprocess.STDOUT, 659 env=env) 660 return get_venv() 661 662 def get_venv(): 663 """Return path to virtual environment or None""" 664 try: 665 return subprocess.check_output( 666 [pipenv, '--venv'], 667 stderr=subprocess.STDOUT, 668 env=env, universal_newlines=True).rstrip() 669 670 except subprocess.CalledProcessError: 671 # virtual environment does not exist 672 return None 673 674 if pipfile is not None: 675 # Install from Pipfile 676 env_ = env.copy() 677 del env_['PIP_NO_INDEX'] 678 env_.update(ensure_subprocess_env({ 679 'PIPENV_PIPFILE': str(pipfile) 680 })) 681 subprocess.check_call([pipenv, 'install'], stderr=subprocess.STDOUT, env=env_) 682 683 self.virtualenv_root = ensure_venv() 684 685 if populate: 686 # Populate from the manifest 687 subprocess.check_call([ 688 pipenv, 'run', 'python', os.path.join(here, 'virtualenv.py'), 'populate', 689 self.topsrcdir, self.topobjdir, self.virtualenv_root, self.manifest_path], 690 stderr=subprocess.STDOUT, env=env) 691 692 self.activate() 693 694 695def verify_python_version(log_handle): 696 """Ensure the current version of Python is sufficient.""" 697 from distutils.version import LooseVersion 698 699 major, minor, micro = sys.version_info[:3] 700 minimum_python_versions = { 701 2: LooseVersion('2.7.3'), 702 3: LooseVersion('3.5.0'), 703 } 704 our = LooseVersion('%d.%d.%d' % (major, minor, micro)) 705 706 if (major not in minimum_python_versions or 707 our < minimum_python_versions[major]): 708 log_handle.write('One of the following Python versions are required:\n') 709 for minver in minimum_python_versions.values(): 710 log_handle.write('* Python %s or greater\n' % minver) 711 log_handle.write('You are running Python %s.\n' % our) 712 713 if os.name in ('nt', 'ce'): 714 log_handle.write(UPGRADE_WINDOWS) 715 else: 716 log_handle.write(UPGRADE_OTHER) 717 718 sys.exit(1) 719 720 721def ensure_subprocess_env(env, encoding='utf-8'): 722 """Ensure the environment is in the correct format for the `subprocess` 723 module. 724 725 This method uses the method with same name from mozbuild.utils as 726 virtualenv.py must be a standalone module. 727 728 This will convert all keys and values to bytes on Python 2, and text on 729 Python 3. 730 731 Args: 732 env (dict): Environment to ensure. 733 encoding (str): Encoding to use when converting to/from bytes/text 734 (default: utf-8). 735 """ 736 # We can't import six.ensure_binary() or six.ensure_text() because this module 737 # has to run stand-alone. Instead we'll implement an abbreviated version of the 738 # checks it does. 739 740 if PY3: 741 text_type = str 742 binary_type = bytes 743 else: 744 text_type = unicode 745 binary_type = str 746 747 def ensure_binary(s): 748 if isinstance(s, text_type): 749 return s.encode(encoding, errors='strict') 750 elif isinstance(s, binary_type): 751 return s 752 else: 753 raise TypeError("not expecting type '%s'" % type(s)) 754 755 def ensure_text(s): 756 if isinstance(s, binary_type): 757 return s.decode(encoding, errors='strict') 758 elif isinstance(s, text_type): 759 return s 760 else: 761 raise TypeError("not expecting type '%s'" % type(s)) 762 763 ensure = ensure_binary if PY2 else ensure_text 764 765 try: 766 return {ensure(k): ensure(v) for k, v in env.iteritems()} 767 except AttributeError: 768 return {ensure(k): ensure(v) for k, v in env.items()} 769 770 771if __name__ == '__main__': 772 if len(sys.argv) < 5: 773 print( 774 'Usage: populate_virtualenv.py /path/to/topsrcdir ' 775 '/path/to/topobjdir /path/to/virtualenv /path/to/virtualenv_manifest') 776 sys.exit(1) 777 778 verify_python_version(sys.stdout) 779 780 topsrcdir, topobjdir, virtualenv_path, manifest_path = sys.argv[1:5] 781 populate = False 782 783 # This should only be called internally. 784 if sys.argv[1] == 'populate': 785 populate = True 786 topsrcdir, topobjdir, virtualenv_path, manifest_path = sys.argv[2:] 787 788 manager = VirtualenvManager(topsrcdir, topobjdir, virtualenv_path, 789 sys.stdout, manifest_path) 790 791 if populate: 792 manager.populate() 793 else: 794 manager.ensure() 795