1""" 2Virtual environment (venv) package for Python. Based on PEP 405. 3 4Copyright (C) 2011-2014 Vinay Sajip. 5Licensed to the PSF under a contributor agreement. 6""" 7import logging 8import os 9import shutil 10import subprocess 11import sys 12import sysconfig 13import types 14import shlex 15 16 17CORE_VENV_DEPS = ('pip',) 18logger = logging.getLogger(__name__) 19 20 21class EnvBuilder: 22 """ 23 This class exists to allow virtual environment creation to be 24 customized. The constructor parameters determine the builder's 25 behaviour when called upon to create a virtual environment. 26 27 By default, the builder makes the system (global) site-packages dir 28 *un*available to the created environment. 29 30 If invoked using the Python -m option, the default is to use copying 31 on Windows platforms but symlinks elsewhere. If instantiated some 32 other way, the default is to *not* use symlinks. 33 34 :param system_site_packages: If True, the system (global) site-packages 35 dir is available to created environments. 36 :param clear: If True, delete the contents of the environment directory if 37 it already exists, before environment creation. 38 :param symlinks: If True, attempt to symlink rather than copy files into 39 virtual environment. 40 :param upgrade: If True, upgrade an existing virtual environment. 41 :param with_pip: If True, ensure pip is installed in the virtual 42 environment 43 :param prompt: Alternative terminal prefix for the environment. 44 :param upgrade_deps: Update the base venv modules to the latest on PyPI 45 :param scm_ignore_files: Create ignore files for the SCMs specified by the 46 iterable. 47 """ 48 49 def __init__(self, system_site_packages=False, clear=False, 50 symlinks=False, upgrade=False, with_pip=False, prompt=None, 51 upgrade_deps=False, *, scm_ignore_files=frozenset()): 52 self.system_site_packages = system_site_packages 53 self.clear = clear 54 self.symlinks = symlinks 55 self.upgrade = upgrade 56 self.with_pip = with_pip 57 self.orig_prompt = prompt 58 if prompt == '.': # see bpo-38901 59 prompt = os.path.basename(os.getcwd()) 60 self.prompt = prompt 61 self.upgrade_deps = upgrade_deps 62 self.scm_ignore_files = frozenset(map(str.lower, scm_ignore_files)) 63 64 def create(self, env_dir): 65 """ 66 Create a virtual environment in a directory. 67 68 :param env_dir: The target directory to create an environment in. 69 70 """ 71 env_dir = os.path.abspath(env_dir) 72 context = self.ensure_directories(env_dir) 73 for scm in self.scm_ignore_files: 74 getattr(self, f"create_{scm}_ignore_file")(context) 75 # See issue 24875. We need system_site_packages to be False 76 # until after pip is installed. 77 true_system_site_packages = self.system_site_packages 78 self.system_site_packages = False 79 self.create_configuration(context) 80 self.setup_python(context) 81 if self.with_pip: 82 self._setup_pip(context) 83 if not self.upgrade: 84 self.setup_scripts(context) 85 self.post_setup(context) 86 if true_system_site_packages: 87 # We had set it to False before, now 88 # restore it and rewrite the configuration 89 self.system_site_packages = True 90 self.create_configuration(context) 91 if self.upgrade_deps: 92 self.upgrade_dependencies(context) 93 94 def clear_directory(self, path): 95 for fn in os.listdir(path): 96 fn = os.path.join(path, fn) 97 if os.path.islink(fn) or os.path.isfile(fn): 98 os.remove(fn) 99 elif os.path.isdir(fn): 100 shutil.rmtree(fn) 101 102 def _venv_path(self, env_dir, name): 103 vars = { 104 'base': env_dir, 105 'platbase': env_dir, 106 'installed_base': env_dir, 107 'installed_platbase': env_dir, 108 } 109 return sysconfig.get_path(name, scheme='venv', vars=vars) 110 111 @classmethod 112 def _same_path(cls, path1, path2): 113 """Check whether two paths appear the same. 114 115 Whether they refer to the same file is irrelevant; we're testing for 116 whether a human reader would look at the path string and easily tell 117 that they're the same file. 118 """ 119 if sys.platform == 'win32': 120 if os.path.normcase(path1) == os.path.normcase(path2): 121 return True 122 # gh-90329: Don't display a warning for short/long names 123 import _winapi 124 try: 125 path1 = _winapi.GetLongPathName(os.fsdecode(path1)) 126 except OSError: 127 pass 128 try: 129 path2 = _winapi.GetLongPathName(os.fsdecode(path2)) 130 except OSError: 131 pass 132 if os.path.normcase(path1) == os.path.normcase(path2): 133 return True 134 return False 135 else: 136 return path1 == path2 137 138 def ensure_directories(self, env_dir): 139 """ 140 Create the directories for the environment. 141 142 Returns a context object which holds paths in the environment, 143 for use by subsequent logic. 144 """ 145 146 def create_if_needed(d): 147 if not os.path.exists(d): 148 os.makedirs(d) 149 elif os.path.islink(d) or os.path.isfile(d): 150 raise ValueError('Unable to create directory %r' % d) 151 152 if os.pathsep in os.fspath(env_dir): 153 raise ValueError(f'Refusing to create a venv in {env_dir} because ' 154 f'it contains the PATH separator {os.pathsep}.') 155 if os.path.exists(env_dir) and self.clear: 156 self.clear_directory(env_dir) 157 context = types.SimpleNamespace() 158 context.env_dir = env_dir 159 context.env_name = os.path.split(env_dir)[1] 160 context.prompt = self.prompt if self.prompt is not None else context.env_name 161 create_if_needed(env_dir) 162 executable = sys._base_executable 163 if not executable: # see gh-96861 164 raise ValueError('Unable to determine path to the running ' 165 'Python interpreter. Provide an explicit path or ' 166 'check that your PATH environment variable is ' 167 'correctly set.') 168 dirname, exename = os.path.split(os.path.abspath(executable)) 169 if sys.platform == 'win32': 170 # Always create the simplest name in the venv. It will either be a 171 # link back to executable, or a copy of the appropriate launcher 172 _d = '_d' if os.path.splitext(exename)[0].endswith('_d') else '' 173 exename = f'python{_d}.exe' 174 context.executable = executable 175 context.python_dir = dirname 176 context.python_exe = exename 177 binpath = self._venv_path(env_dir, 'scripts') 178 incpath = self._venv_path(env_dir, 'include') 179 libpath = self._venv_path(env_dir, 'purelib') 180 181 context.inc_path = incpath 182 create_if_needed(incpath) 183 context.lib_path = libpath 184 create_if_needed(libpath) 185 # Issue 21197: create lib64 as a symlink to lib on 64-bit non-OS X POSIX 186 if ((sys.maxsize > 2**32) and (os.name == 'posix') and 187 (sys.platform != 'darwin')): 188 link_path = os.path.join(env_dir, 'lib64') 189 if not os.path.exists(link_path): # Issue #21643 190 os.symlink('lib', link_path) 191 context.bin_path = binpath 192 context.bin_name = os.path.relpath(binpath, env_dir) 193 context.env_exe = os.path.join(binpath, exename) 194 create_if_needed(binpath) 195 # Assign and update the command to use when launching the newly created 196 # environment, in case it isn't simply the executable script (e.g. bpo-45337) 197 context.env_exec_cmd = context.env_exe 198 if sys.platform == 'win32': 199 # bpo-45337: Fix up env_exec_cmd to account for file system redirections. 200 # Some redirects only apply to CreateFile and not CreateProcess 201 real_env_exe = os.path.realpath(context.env_exe) 202 if not self._same_path(real_env_exe, context.env_exe): 203 logger.warning('Actual environment location may have moved due to ' 204 'redirects, links or junctions.\n' 205 ' Requested location: "%s"\n' 206 ' Actual location: "%s"', 207 context.env_exe, real_env_exe) 208 context.env_exec_cmd = real_env_exe 209 return context 210 211 def create_configuration(self, context): 212 """ 213 Create a configuration file indicating where the environment's Python 214 was copied from, and whether the system site-packages should be made 215 available in the environment. 216 217 :param context: The information for the environment creation request 218 being processed. 219 """ 220 context.cfg_path = path = os.path.join(context.env_dir, 'pyvenv.cfg') 221 with open(path, 'w', encoding='utf-8') as f: 222 f.write('home = %s\n' % context.python_dir) 223 if self.system_site_packages: 224 incl = 'true' 225 else: 226 incl = 'false' 227 f.write('include-system-site-packages = %s\n' % incl) 228 f.write('version = %d.%d.%d\n' % sys.version_info[:3]) 229 if self.prompt is not None: 230 f.write(f'prompt = {self.prompt!r}\n') 231 f.write('executable = %s\n' % os.path.realpath(sys.executable)) 232 args = [] 233 nt = os.name == 'nt' 234 if nt and self.symlinks: 235 args.append('--symlinks') 236 if not nt and not self.symlinks: 237 args.append('--copies') 238 if not self.with_pip: 239 args.append('--without-pip') 240 if self.system_site_packages: 241 args.append('--system-site-packages') 242 if self.clear: 243 args.append('--clear') 244 if self.upgrade: 245 args.append('--upgrade') 246 if self.upgrade_deps: 247 args.append('--upgrade-deps') 248 if self.orig_prompt is not None: 249 args.append(f'--prompt="{self.orig_prompt}"') 250 if not self.scm_ignore_files: 251 args.append('--without-scm-ignore-files') 252 253 args.append(context.env_dir) 254 args = ' '.join(args) 255 f.write(f'command = {sys.executable} -m venv {args}\n') 256 257 def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): 258 """ 259 Try symlinking a file, and if that fails, fall back to copying. 260 (Unused on Windows, because we can't just copy a failed symlink file: we 261 switch to a different set of files instead.) 262 """ 263 assert os.name != 'nt' 264 force_copy = not self.symlinks 265 if not force_copy: 266 try: 267 if not os.path.islink(dst): # can't link to itself! 268 if relative_symlinks_ok: 269 assert os.path.dirname(src) == os.path.dirname(dst) 270 os.symlink(os.path.basename(src), dst) 271 else: 272 os.symlink(src, dst) 273 except Exception: # may need to use a more specific exception 274 logger.warning('Unable to symlink %r to %r', src, dst) 275 force_copy = True 276 if force_copy: 277 shutil.copyfile(src, dst) 278 279 def create_git_ignore_file(self, context): 280 """ 281 Create a .gitignore file in the environment directory. 282 283 The contents of the file cause the entire environment directory to be 284 ignored by git. 285 """ 286 gitignore_path = os.path.join(context.env_dir, '.gitignore') 287 with open(gitignore_path, 'w', encoding='utf-8') as file: 288 file.write('# Created by venv; ' 289 'see https://docs.python.org/3/library/venv.html\n') 290 file.write('*\n') 291 292 if os.name != 'nt': 293 def setup_python(self, context): 294 """ 295 Set up a Python executable in the environment. 296 297 :param context: The information for the environment creation request 298 being processed. 299 """ 300 binpath = context.bin_path 301 path = context.env_exe 302 copier = self.symlink_or_copy 303 dirname = context.python_dir 304 copier(context.executable, path) 305 if not os.path.islink(path): 306 os.chmod(path, 0o755) 307 for suffix in ('python', 'python3', 308 f'python3.{sys.version_info[1]}'): 309 path = os.path.join(binpath, suffix) 310 if not os.path.exists(path): 311 # Issue 18807: make copies if 312 # symlinks are not wanted 313 copier(context.env_exe, path, relative_symlinks_ok=True) 314 if not os.path.islink(path): 315 os.chmod(path, 0o755) 316 317 else: 318 def setup_python(self, context): 319 """ 320 Set up a Python executable in the environment. 321 322 :param context: The information for the environment creation request 323 being processed. 324 """ 325 binpath = context.bin_path 326 dirname = context.python_dir 327 exename = os.path.basename(context.env_exe) 328 exe_stem = os.path.splitext(exename)[0] 329 exe_d = '_d' if os.path.normcase(exe_stem).endswith('_d') else '' 330 if sysconfig.is_python_build(): 331 scripts = dirname 332 else: 333 scripts = os.path.join(os.path.dirname(__file__), 334 'scripts', 'nt') 335 if not sysconfig.get_config_var("Py_GIL_DISABLED"): 336 python_exe = os.path.join(dirname, f'python{exe_d}.exe') 337 pythonw_exe = os.path.join(dirname, f'pythonw{exe_d}.exe') 338 link_sources = { 339 'python.exe': python_exe, 340 f'python{exe_d}.exe': python_exe, 341 'pythonw.exe': pythonw_exe, 342 f'pythonw{exe_d}.exe': pythonw_exe, 343 } 344 python_exe = os.path.join(scripts, f'venvlauncher{exe_d}.exe') 345 pythonw_exe = os.path.join(scripts, f'venvwlauncher{exe_d}.exe') 346 copy_sources = { 347 'python.exe': python_exe, 348 f'python{exe_d}.exe': python_exe, 349 'pythonw.exe': pythonw_exe, 350 f'pythonw{exe_d}.exe': pythonw_exe, 351 } 352 else: 353 exe_t = f'3.{sys.version_info[1]}t' 354 python_exe = os.path.join(dirname, f'python{exe_t}{exe_d}.exe') 355 pythonw_exe = os.path.join(dirname, f'pythonw{exe_t}{exe_d}.exe') 356 link_sources = { 357 'python.exe': python_exe, 358 f'python{exe_d}.exe': python_exe, 359 f'python{exe_t}.exe': python_exe, 360 f'python{exe_t}{exe_d}.exe': python_exe, 361 'pythonw.exe': pythonw_exe, 362 f'pythonw{exe_d}.exe': pythonw_exe, 363 f'pythonw{exe_t}.exe': pythonw_exe, 364 f'pythonw{exe_t}{exe_d}.exe': pythonw_exe, 365 } 366 python_exe = os.path.join(scripts, f'venvlaunchert{exe_d}.exe') 367 pythonw_exe = os.path.join(scripts, f'venvwlaunchert{exe_d}.exe') 368 copy_sources = { 369 'python.exe': python_exe, 370 f'python{exe_d}.exe': python_exe, 371 f'python{exe_t}.exe': python_exe, 372 f'python{exe_t}{exe_d}.exe': python_exe, 373 'pythonw.exe': pythonw_exe, 374 f'pythonw{exe_d}.exe': pythonw_exe, 375 f'pythonw{exe_t}.exe': pythonw_exe, 376 f'pythonw{exe_t}{exe_d}.exe': pythonw_exe, 377 } 378 379 do_copies = True 380 if self.symlinks: 381 do_copies = False 382 # For symlinking, we need all the DLLs to be available alongside 383 # the executables. 384 link_sources.update({ 385 f: os.path.join(dirname, f) for f in os.listdir(dirname) 386 if os.path.normcase(f).startswith(('python', 'vcruntime')) 387 and os.path.normcase(os.path.splitext(f)[1]) == '.dll' 388 }) 389 390 to_unlink = [] 391 for dest, src in link_sources.items(): 392 dest = os.path.join(binpath, dest) 393 try: 394 os.symlink(src, dest) 395 to_unlink.append(dest) 396 except OSError: 397 logger.warning('Unable to symlink %r to %r', src, dest) 398 do_copies = True 399 for f in to_unlink: 400 try: 401 os.unlink(f) 402 except OSError: 403 logger.warning('Failed to clean up symlink %r', 404 f) 405 logger.warning('Retrying with copies') 406 break 407 408 if do_copies: 409 for dest, src in copy_sources.items(): 410 dest = os.path.join(binpath, dest) 411 try: 412 shutil.copy2(src, dest) 413 except OSError: 414 logger.warning('Unable to copy %r to %r', src, dest) 415 416 if sysconfig.is_python_build(): 417 # copy init.tcl 418 for root, dirs, files in os.walk(context.python_dir): 419 if 'init.tcl' in files: 420 tcldir = os.path.basename(root) 421 tcldir = os.path.join(context.env_dir, 'Lib', tcldir) 422 if not os.path.exists(tcldir): 423 os.makedirs(tcldir) 424 src = os.path.join(root, 'init.tcl') 425 dst = os.path.join(tcldir, 'init.tcl') 426 shutil.copyfile(src, dst) 427 break 428 429 def _call_new_python(self, context, *py_args, **kwargs): 430 """Executes the newly created Python using safe-ish options""" 431 # gh-98251: We do not want to just use '-I' because that masks 432 # legitimate user preferences (such as not writing bytecode). All we 433 # really need is to ensure that the path variables do not overrule 434 # normal venv handling. 435 args = [context.env_exec_cmd, *py_args] 436 kwargs['env'] = env = os.environ.copy() 437 env['VIRTUAL_ENV'] = context.env_dir 438 env.pop('PYTHONHOME', None) 439 env.pop('PYTHONPATH', None) 440 kwargs['cwd'] = context.env_dir 441 kwargs['executable'] = context.env_exec_cmd 442 subprocess.check_output(args, **kwargs) 443 444 def _setup_pip(self, context): 445 """Installs or upgrades pip in a virtual environment""" 446 self._call_new_python(context, '-m', 'ensurepip', '--upgrade', 447 '--default-pip', stderr=subprocess.STDOUT) 448 449 def setup_scripts(self, context): 450 """ 451 Set up scripts into the created environment from a directory. 452 453 This method installs the default scripts into the environment 454 being created. You can prevent the default installation by overriding 455 this method if you really need to, or if you need to specify 456 a different location for the scripts to install. By default, the 457 'scripts' directory in the venv package is used as the source of 458 scripts to install. 459 """ 460 path = os.path.abspath(os.path.dirname(__file__)) 461 path = os.path.join(path, 'scripts') 462 self.install_scripts(context, path) 463 464 def post_setup(self, context): 465 """ 466 Hook for post-setup modification of the venv. Subclasses may install 467 additional packages or scripts here, add activation shell scripts, etc. 468 469 :param context: The information for the environment creation request 470 being processed. 471 """ 472 pass 473 474 def replace_variables(self, text, context): 475 """ 476 Replace variable placeholders in script text with context-specific 477 variables. 478 479 Return the text passed in , but with variables replaced. 480 481 :param text: The text in which to replace placeholder variables. 482 :param context: The information for the environment creation request 483 being processed. 484 """ 485 replacements = { 486 '__VENV_DIR__': context.env_dir, 487 '__VENV_NAME__': context.env_name, 488 '__VENV_PROMPT__': context.prompt, 489 '__VENV_BIN_NAME__': context.bin_name, 490 '__VENV_PYTHON__': context.env_exe, 491 } 492 493 def quote_ps1(s): 494 """ 495 This should satisfy PowerShell quoting rules [1], unless the quoted 496 string is passed directly to Windows native commands [2]. 497 [1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules 498 [2]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters 499 """ 500 s = s.replace("'", "''") 501 return f"'{s}'" 502 503 def quote_bat(s): 504 return s 505 506 # gh-124651: need to quote the template strings properly 507 quote = shlex.quote 508 script_path = context.script_path 509 if script_path.endswith('.ps1'): 510 quote = quote_ps1 511 elif script_path.endswith('.bat'): 512 quote = quote_bat 513 else: 514 # fallbacks to POSIX shell compliant quote 515 quote = shlex.quote 516 517 replacements = {key: quote(s) for key, s in replacements.items()} 518 for key, quoted in replacements.items(): 519 text = text.replace(key, quoted) 520 return text 521 522 def install_scripts(self, context, path): 523 """ 524 Install scripts into the created environment from a directory. 525 526 :param context: The information for the environment creation request 527 being processed. 528 :param path: Absolute pathname of a directory containing script. 529 Scripts in the 'common' subdirectory of this directory, 530 and those in the directory named for the platform 531 being run on, are installed in the created environment. 532 Placeholder variables are replaced with environment- 533 specific values. 534 """ 535 binpath = context.bin_path 536 plen = len(path) 537 if os.name == 'nt': 538 def skip_file(f): 539 f = os.path.normcase(f) 540 return (f.startswith(('python', 'venv')) 541 and f.endswith(('.exe', '.pdb'))) 542 else: 543 def skip_file(f): 544 return False 545 for root, dirs, files in os.walk(path): 546 if root == path: # at top-level, remove irrelevant dirs 547 for d in dirs[:]: 548 if d not in ('common', os.name): 549 dirs.remove(d) 550 continue # ignore files in top level 551 for f in files: 552 if skip_file(f): 553 continue 554 srcfile = os.path.join(root, f) 555 suffix = root[plen:].split(os.sep)[2:] 556 if not suffix: 557 dstdir = binpath 558 else: 559 dstdir = os.path.join(binpath, *suffix) 560 if not os.path.exists(dstdir): 561 os.makedirs(dstdir) 562 dstfile = os.path.join(dstdir, f) 563 if os.name == 'nt' and srcfile.endswith(('.exe', '.pdb')): 564 shutil.copy2(srcfile, dstfile) 565 continue 566 with open(srcfile, 'rb') as f: 567 data = f.read() 568 try: 569 context.script_path = srcfile 570 new_data = ( 571 self.replace_variables(data.decode('utf-8'), context) 572 .encode('utf-8') 573 ) 574 except UnicodeError as e: 575 logger.warning('unable to copy script %r, ' 576 'may be binary: %s', srcfile, e) 577 continue 578 if new_data == data: 579 shutil.copy2(srcfile, dstfile) 580 else: 581 with open(dstfile, 'wb') as f: 582 f.write(new_data) 583 shutil.copymode(srcfile, dstfile) 584 585 def upgrade_dependencies(self, context): 586 logger.debug( 587 f'Upgrading {CORE_VENV_DEPS} packages in {context.bin_path}' 588 ) 589 self._call_new_python(context, '-m', 'pip', 'install', '--upgrade', 590 *CORE_VENV_DEPS) 591 592 593def create(env_dir, system_site_packages=False, clear=False, 594 symlinks=False, with_pip=False, prompt=None, upgrade_deps=False, 595 *, scm_ignore_files=frozenset()): 596 """Create a virtual environment in a directory.""" 597 builder = EnvBuilder(system_site_packages=system_site_packages, 598 clear=clear, symlinks=symlinks, with_pip=with_pip, 599 prompt=prompt, upgrade_deps=upgrade_deps, 600 scm_ignore_files=scm_ignore_files) 601 builder.create(env_dir) 602 603 604def main(args=None): 605 import argparse 606 607 parser = argparse.ArgumentParser(prog=__name__, 608 description='Creates virtual Python ' 609 'environments in one or ' 610 'more target ' 611 'directories.', 612 epilog='Once an environment has been ' 613 'created, you may wish to ' 614 'activate it, e.g. by ' 615 'sourcing an activate script ' 616 'in its bin directory.') 617 parser.add_argument('dirs', metavar='ENV_DIR', nargs='+', 618 help='A directory to create the environment in.') 619 parser.add_argument('--system-site-packages', default=False, 620 action='store_true', dest='system_site', 621 help='Give the virtual environment access to the ' 622 'system site-packages dir.') 623 if os.name == 'nt': 624 use_symlinks = False 625 else: 626 use_symlinks = True 627 group = parser.add_mutually_exclusive_group() 628 group.add_argument('--symlinks', default=use_symlinks, 629 action='store_true', dest='symlinks', 630 help='Try to use symlinks rather than copies, ' 631 'when symlinks are not the default for ' 632 'the platform.') 633 group.add_argument('--copies', default=not use_symlinks, 634 action='store_false', dest='symlinks', 635 help='Try to use copies rather than symlinks, ' 636 'even when symlinks are the default for ' 637 'the platform.') 638 parser.add_argument('--clear', default=False, action='store_true', 639 dest='clear', help='Delete the contents of the ' 640 'environment directory if it ' 641 'already exists, before ' 642 'environment creation.') 643 parser.add_argument('--upgrade', default=False, action='store_true', 644 dest='upgrade', help='Upgrade the environment ' 645 'directory to use this version ' 646 'of Python, assuming Python ' 647 'has been upgraded in-place.') 648 parser.add_argument('--without-pip', dest='with_pip', 649 default=True, action='store_false', 650 help='Skips installing or upgrading pip in the ' 651 'virtual environment (pip is bootstrapped ' 652 'by default)') 653 parser.add_argument('--prompt', 654 help='Provides an alternative prompt prefix for ' 655 'this environment.') 656 parser.add_argument('--upgrade-deps', default=False, action='store_true', 657 dest='upgrade_deps', 658 help=f'Upgrade core dependencies ({", ".join(CORE_VENV_DEPS)}) ' 659 'to the latest version in PyPI') 660 parser.add_argument('--without-scm-ignore-files', dest='scm_ignore_files', 661 action='store_const', const=frozenset(), 662 default=frozenset(['git']), 663 help='Skips adding SCM ignore files to the environment ' 664 'directory (Git is supported by default).') 665 options = parser.parse_args(args) 666 if options.upgrade and options.clear: 667 raise ValueError('you cannot supply --upgrade and --clear together.') 668 builder = EnvBuilder(system_site_packages=options.system_site, 669 clear=options.clear, 670 symlinks=options.symlinks, 671 upgrade=options.upgrade, 672 with_pip=options.with_pip, 673 prompt=options.prompt, 674 upgrade_deps=options.upgrade_deps, 675 scm_ignore_files=options.scm_ignore_files) 676 for d in options.dirs: 677 builder.create(d) 678 679 680if __name__ == '__main__': 681 rc = 1 682 try: 683 main() 684 rc = 0 685 except Exception as e: 686 print('Error: %s' % e, file=sys.stderr) 687 sys.exit(rc) 688