• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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