blob: 74245abbb2a83348efe9d503cdefd45de8a6945e [file] [log] [blame]
Vinay Sajip42211422012-05-26 20:36:12 +01001"""
2Virtual environment (venv) package for Python. Based on PEP 405.
3
Vinay Sajip1e53f8d2014-04-15 11:18:10 +01004Copyright (C) 2011-2014 Vinay Sajip.
Vinay Sajip28952442012-06-25 00:47:46 +01005Licensed to the PSF under a contributor agreement.
Vinay Sajip42211422012-05-26 20:36:12 +01006
Vinay Sajip44697462012-05-28 16:33:01 +01007usage: python -m venv [-h] [--system-site-packages] [--symlinks] [--clear]
8 [--upgrade]
Vinay Sajip42211422012-05-26 20:36:12 +01009 ENV_DIR [ENV_DIR ...]
10
11Creates virtual Python environments in one or more target directories.
12
13positional arguments:
14 ENV_DIR A directory to create the environment in.
15
16optional arguments:
17 -h, --help show this help message and exit
Vinay Sajip42211422012-05-26 20:36:12 +010018 --system-site-packages
19 Give the virtual environment access to the system
20 site-packages dir.
21 --symlinks Attempt to symlink rather than copy.
Berker Peksag547f66f2016-01-28 09:01:26 +020022 --clear Delete the contents of the environment directory if it
23 already exists, before environment creation.
Vinay Sajip42211422012-05-26 20:36:12 +010024 --upgrade Upgrade the environment directory to use this version
25 of Python, assuming Python has been upgraded in-place.
Nick Coghlan8fbdb092013-11-23 00:30:34 +100026 --without-pip Skips installing or upgrading pip in the virtual
27 environment (pip is bootstrapped by default)
Vinay Sajip42211422012-05-26 20:36:12 +010028"""
Vinay Sajip7ded1f02012-05-26 03:45:29 +010029import logging
30import os
Vinay Sajip7ded1f02012-05-26 03:45:29 +010031import shutil
Nick Coghlan8fbdb092013-11-23 00:30:34 +100032import subprocess
Vinay Sajip7ded1f02012-05-26 03:45:29 +010033import sys
Vinay Sajipc07aa9e2013-07-12 21:10:19 +010034import types
Vinay Sajip7ded1f02012-05-26 03:45:29 +010035
36logger = logging.getLogger(__name__)
37
Vinay Sajip7ded1f02012-05-26 03:45:29 +010038
39class EnvBuilder:
40 """
41 This class exists to allow virtual environment creation to be
Vinay Sajip9c10d6b2013-11-15 20:58:13 +000042 customized. The constructor parameters determine the builder's
Vinay Sajip7ded1f02012-05-26 03:45:29 +010043 behaviour when called upon to create a virtual environment.
44
45 By default, the builder makes the system (global) site-packages dir
Vinay Sajip87ed5992012-11-14 11:18:35 +000046 *un*available to the created environment.
Vinay Sajip7ded1f02012-05-26 03:45:29 +010047
Vinay Sajip87ed5992012-11-14 11:18:35 +000048 If invoked using the Python -m option, the default is to use copying
49 on Windows platforms but symlinks elsewhere. If instantiated some
50 other way, the default is to *not* use symlinks.
Vinay Sajip7ded1f02012-05-26 03:45:29 +010051
52 :param system_site_packages: If True, the system (global) site-packages
53 dir is available to created environments.
Berker Peksag22532ac2016-01-28 09:13:14 +020054 :param clear: If True, delete the contents of the environment directory if
55 it already exists, before environment creation.
Vinay Sajip7ded1f02012-05-26 03:45:29 +010056 :param symlinks: If True, attempt to symlink rather than copy files into
57 virtual environment.
58 :param upgrade: If True, upgrade an existing virtual environment.
Nick Coghlan8fbdb092013-11-23 00:30:34 +100059 :param with_pip: If True, ensure pip is installed in the virtual
60 environment
Vinay Sajip7ded1f02012-05-26 03:45:29 +010061 """
62
63 def __init__(self, system_site_packages=False, clear=False,
Nick Coghlan8fbdb092013-11-23 00:30:34 +100064 symlinks=False, upgrade=False, with_pip=False):
Vinay Sajip7ded1f02012-05-26 03:45:29 +010065 self.system_site_packages = system_site_packages
66 self.clear = clear
67 self.symlinks = symlinks
68 self.upgrade = upgrade
Nick Coghlan8fbdb092013-11-23 00:30:34 +100069 self.with_pip = with_pip
Vinay Sajip7ded1f02012-05-26 03:45:29 +010070
71 def create(self, env_dir):
72 """
73 Create a virtual environment in a directory.
74
75 :param env_dir: The target directory to create an environment in.
76
77 """
Vinay Sajip7ded1f02012-05-26 03:45:29 +010078 env_dir = os.path.abspath(env_dir)
79 context = self.ensure_directories(env_dir)
80 self.create_configuration(context)
81 self.setup_python(context)
Nick Coghlan8fbdb092013-11-23 00:30:34 +100082 if self.with_pip:
83 self._setup_pip(context)
Vinay Sajip7ded1f02012-05-26 03:45:29 +010084 if not self.upgrade:
85 self.setup_scripts(context)
86 self.post_setup(context)
87
Vinay Sajipbd40d3e2012-10-11 17:22:45 +010088 def clear_directory(self, path):
89 for fn in os.listdir(path):
90 fn = os.path.join(path, fn)
91 if os.path.islink(fn) or os.path.isfile(fn):
92 os.remove(fn)
93 elif os.path.isdir(fn):
94 shutil.rmtree(fn)
95
Vinay Sajip7ded1f02012-05-26 03:45:29 +010096 def ensure_directories(self, env_dir):
97 """
98 Create the directories for the environment.
99
100 Returns a context object which holds paths in the environment,
101 for use by subsequent logic.
102 """
103
104 def create_if_needed(d):
105 if not os.path.exists(d):
106 os.makedirs(d)
Vinay Sajipbd40d3e2012-10-11 17:22:45 +0100107 elif os.path.islink(d) or os.path.isfile(d):
108 raise ValueError('Unable to create directory %r' % d)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100109
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100110 if os.path.exists(env_dir) and self.clear:
Vinay Sajipbd40d3e2012-10-11 17:22:45 +0100111 self.clear_directory(env_dir)
Vinay Sajipc07aa9e2013-07-12 21:10:19 +0100112 context = types.SimpleNamespace()
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100113 context.env_dir = env_dir
114 context.env_name = os.path.split(env_dir)[1]
115 context.prompt = '(%s) ' % context.env_name
116 create_if_needed(env_dir)
117 env = os.environ
Vinay Sajip28952442012-06-25 00:47:46 +0100118 if sys.platform == 'darwin' and '__PYVENV_LAUNCHER__' in env:
119 executable = os.environ['__PYVENV_LAUNCHER__']
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100120 else:
121 executable = sys.executable
122 dirname, exename = os.path.split(os.path.abspath(executable))
123 context.executable = executable
124 context.python_dir = dirname
125 context.python_exe = exename
126 if sys.platform == 'win32':
127 binname = 'Scripts'
128 incpath = 'Include'
129 libpath = os.path.join(env_dir, 'Lib', 'site-packages')
130 else:
131 binname = 'bin'
132 incpath = 'include'
Vinay Sajip1e53f8d2014-04-15 11:18:10 +0100133 libpath = os.path.join(env_dir, 'lib',
134 'python%d.%d' % sys.version_info[:2],
135 'site-packages')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100136 context.inc_path = path = os.path.join(env_dir, incpath)
137 create_if_needed(path)
138 create_if_needed(libpath)
Vinay Sajip1e53f8d2014-04-15 11:18:10 +0100139 # Issue 21197: create lib64 as a symlink to lib on 64-bit non-OS X POSIX
Vinay Sajipb9b965f2014-06-03 16:47:51 +0100140 if ((sys.maxsize > 2**32) and (os.name == 'posix') and
Vinay Sajip1e53f8d2014-04-15 11:18:10 +0100141 (sys.platform != 'darwin')):
Vinay Sajip1e53f8d2014-04-15 11:18:10 +0100142 link_path = os.path.join(env_dir, 'lib64')
Vinay Sajipb9b965f2014-06-03 16:47:51 +0100143 if not os.path.exists(link_path): # Issue #21643
Barry Warsaw581c29f2015-02-06 11:23:58 -0500144 os.symlink('lib', link_path)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100145 context.bin_path = binpath = os.path.join(env_dir, binname)
146 context.bin_name = binname
147 context.env_exe = os.path.join(binpath, exename)
148 create_if_needed(binpath)
149 return context
150
151 def create_configuration(self, context):
152 """
153 Create a configuration file indicating where the environment's Python
154 was copied from, and whether the system site-packages should be made
155 available in the environment.
156
157 :param context: The information for the environment creation request
158 being processed.
159 """
160 context.cfg_path = path = os.path.join(context.env_dir, 'pyvenv.cfg')
161 with open(path, 'w', encoding='utf-8') as f:
162 f.write('home = %s\n' % context.python_dir)
163 if self.system_site_packages:
164 incl = 'true'
165 else:
166 incl = 'false'
167 f.write('include-system-site-packages = %s\n' % incl)
168 f.write('version = %d.%d.%d\n' % sys.version_info[:3])
169
170 if os.name == 'nt':
171 def include_binary(self, f):
172 if f.endswith(('.pyd', '.dll')):
173 result = True
174 else:
175 result = f.startswith('python') and f.endswith('.exe')
176 return result
177
Barry Warsaw581c29f2015-02-06 11:23:58 -0500178 def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100179 """
180 Try symlinking a file, and if that fails, fall back to copying.
181 """
182 force_copy = not self.symlinks
183 if not force_copy:
184 try:
185 if not os.path.islink(dst): # can't link to itself!
Barry Warsaw581c29f2015-02-06 11:23:58 -0500186 if relative_symlinks_ok:
187 assert os.path.dirname(src) == os.path.dirname(dst)
188 os.symlink(os.path.basename(src), dst)
189 else:
190 os.symlink(src, dst)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100191 except Exception: # may need to use a more specific exception
192 logger.warning('Unable to symlink %r to %r', src, dst)
193 force_copy = True
194 if force_copy:
195 shutil.copyfile(src, dst)
196
197 def setup_python(self, context):
198 """
199 Set up a Python executable in the environment.
200
201 :param context: The information for the environment creation request
202 being processed.
203 """
204 binpath = context.bin_path
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100205 path = context.env_exe
206 copier = self.symlink_or_copy
207 copier(context.executable, path)
208 dirname = context.python_dir
209 if os.name != 'nt':
210 if not os.path.islink(path):
211 os.chmod(path, 0o755)
Vinay Sajip44697462012-05-28 16:33:01 +0100212 for suffix in ('python', 'python3'):
213 path = os.path.join(binpath, suffix)
214 if not os.path.exists(path):
Vinay Sajip432810f2014-05-28 08:06:24 +0100215 # Issue 18807: make copies if
216 # symlinks are not wanted
Barry Warsaw581c29f2015-02-06 11:23:58 -0500217 copier(context.env_exe, path, relative_symlinks_ok=True)
Barry Warsawadaddde2014-06-04 14:11:46 -0400218 if not os.path.islink(path):
219 os.chmod(path, 0o755)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100220 else:
221 subdir = 'DLLs'
222 include = self.include_binary
223 files = [f for f in os.listdir(dirname) if include(f)]
224 for f in files:
225 src = os.path.join(dirname, f)
226 dst = os.path.join(binpath, f)
227 if dst != context.env_exe: # already done, above
228 copier(src, dst)
229 dirname = os.path.join(dirname, subdir)
230 if os.path.isdir(dirname):
231 files = [f for f in os.listdir(dirname) if include(f)]
232 for f in files:
233 src = os.path.join(dirname, f)
234 dst = os.path.join(binpath, f)
235 copier(src, dst)
236 # copy init.tcl over
237 for root, dirs, files in os.walk(context.python_dir):
238 if 'init.tcl' in files:
239 tcldir = os.path.basename(root)
240 tcldir = os.path.join(context.env_dir, 'Lib', tcldir)
Vinay Sajip1a170a72014-06-05 09:31:20 +0100241 if not os.path.exists(tcldir):
242 os.makedirs(tcldir)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100243 src = os.path.join(root, 'init.tcl')
244 dst = os.path.join(tcldir, 'init.tcl')
245 shutil.copyfile(src, dst)
246 break
247
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000248 def _setup_pip(self, context):
249 """Installs or upgrades pip in a virtual environment"""
Nick Coghland76cdc12013-11-23 11:37:28 +1000250 # We run ensurepip in isolated mode to avoid side effects from
251 # environment vars, the current directory and anything else
252 # intended for the global Python environment
Nick Coghlan1631b9b2013-11-24 11:53:03 +1000253 cmd = [context.env_exe, '-Im', 'ensurepip', '--upgrade',
Nick Coghland76cdc12013-11-23 11:37:28 +1000254 '--default-pip']
Nick Coghlan6fd12f22013-11-24 11:36:31 +1000255 subprocess.check_output(cmd, stderr=subprocess.STDOUT)
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000256
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100257 def setup_scripts(self, context):
258 """
259 Set up scripts into the created environment from a directory.
260
261 This method installs the default scripts into the environment
262 being created. You can prevent the default installation by overriding
263 this method if you really need to, or if you need to specify
264 a different location for the scripts to install. By default, the
265 'scripts' directory in the venv package is used as the source of
266 scripts to install.
267 """
268 path = os.path.abspath(os.path.dirname(__file__))
269 path = os.path.join(path, 'scripts')
270 self.install_scripts(context, path)
271
272 def post_setup(self, context):
273 """
274 Hook for post-setup modification of the venv. Subclasses may install
275 additional packages or scripts here, add activation shell scripts, etc.
276
277 :param context: The information for the environment creation request
278 being processed.
279 """
280 pass
281
282 def replace_variables(self, text, context):
283 """
284 Replace variable placeholders in script text with context-specific
285 variables.
286
287 Return the text passed in , but with variables replaced.
288
289 :param text: The text in which to replace placeholder variables.
290 :param context: The information for the environment creation request
291 being processed.
292 """
293 text = text.replace('__VENV_DIR__', context.env_dir)
Vinay Sajipdff9e252013-10-02 11:36:16 +0100294 text = text.replace('__VENV_NAME__', context.env_name)
295 text = text.replace('__VENV_PROMPT__', context.prompt)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100296 text = text.replace('__VENV_BIN_NAME__', context.bin_name)
297 text = text.replace('__VENV_PYTHON__', context.env_exe)
298 return text
299
300 def install_scripts(self, context, path):
301 """
302 Install scripts into the created environment from a directory.
303
304 :param context: The information for the environment creation request
305 being processed.
306 :param path: Absolute pathname of a directory containing script.
307 Scripts in the 'common' subdirectory of this directory,
308 and those in the directory named for the platform
309 being run on, are installed in the created environment.
310 Placeholder variables are replaced with environment-
311 specific values.
312 """
313 binpath = context.bin_path
314 plen = len(path)
315 for root, dirs, files in os.walk(path):
316 if root == path: # at top-level, remove irrelevant dirs
317 for d in dirs[:]:
318 if d not in ('common', os.name):
319 dirs.remove(d)
320 continue # ignore files in top level
321 for f in files:
322 srcfile = os.path.join(root, f)
323 suffix = root[plen:].split(os.sep)[2:]
324 if not suffix:
325 dstdir = binpath
326 else:
327 dstdir = os.path.join(binpath, *suffix)
328 if not os.path.exists(dstdir):
329 os.makedirs(dstdir)
330 dstfile = os.path.join(dstdir, f)
331 with open(srcfile, 'rb') as f:
332 data = f.read()
333 if srcfile.endswith('.exe'):
334 mode = 'wb'
335 else:
336 mode = 'w'
Vinay Sajipbdd13fd2012-10-28 12:39:39 +0000337 try:
338 data = data.decode('utf-8')
339 data = self.replace_variables(data, context)
340 except UnicodeDecodeError as e:
341 data = None
342 logger.warning('unable to copy script %r, '
343 'may be binary: %s', srcfile, e)
344 if data is not None:
345 with open(dstfile, mode) as f:
346 f.write(data)
347 shutil.copymode(srcfile, dstfile)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100348
349
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000350def create(env_dir, system_site_packages=False, clear=False,
351 symlinks=False, with_pip=False):
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100352 """
353 Create a virtual environment in a directory.
354
Vinay Sajip87ed5992012-11-14 11:18:35 +0000355 By default, makes the system (global) site-packages dir *un*available to
356 the created environment, and uses copying rather than symlinking for files
357 obtained from the source Python installation.
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100358
359 :param env_dir: The target directory to create an environment in.
360 :param system_site_packages: If True, the system (global) site-packages
361 dir is available to the environment.
Berker Peksag22532ac2016-01-28 09:13:14 +0200362 :param clear: If True, delete the contents of the environment directory if
363 it already exists, before environment creation.
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100364 :param symlinks: If True, attempt to symlink rather than copy files into
365 virtual environment.
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000366 :param with_pip: If True, ensure pip is installed in the virtual
367 environment
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100368 """
Vinay Sajip44697462012-05-28 16:33:01 +0100369 builder = EnvBuilder(system_site_packages=system_site_packages,
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000370 clear=clear, symlinks=symlinks, with_pip=with_pip)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100371 builder.create(env_dir)
372
373def main(args=None):
374 compatible = True
375 if sys.version_info < (3, 3):
376 compatible = False
377 elif not hasattr(sys, 'base_prefix'):
378 compatible = False
379 if not compatible:
Vinay Sajip0e6c66d2013-10-31 18:44:04 +0000380 raise ValueError('This script is only for use with Python >= 3.3')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100381 else:
382 import argparse
383
384 parser = argparse.ArgumentParser(prog=__name__,
385 description='Creates virtual Python '
386 'environments in one or '
387 'more target '
Vinay Sajip4d378d82012-07-08 17:50:42 +0100388 'directories.',
389 epilog='Once an environment has been '
390 'created, you may wish to '
391 'activate it, e.g. by '
392 'sourcing an activate script '
393 'in its bin directory.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100394 parser.add_argument('dirs', metavar='ENV_DIR', nargs='+',
395 help='A directory to create the environment in.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100396 parser.add_argument('--system-site-packages', default=False,
397 action='store_true', dest='system_site',
Vinay Sajip44697462012-05-28 16:33:01 +0100398 help='Give the virtual environment access to the '
399 'system site-packages dir.')
Vinay Sajip90db6612012-07-17 17:33:46 +0100400 if os.name == 'nt':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100401 use_symlinks = False
402 else:
403 use_symlinks = True
Vinay Sajip59390272013-08-25 00:04:06 +0100404 group = parser.add_mutually_exclusive_group()
405 group.add_argument('--symlinks', default=use_symlinks,
406 action='store_true', dest='symlinks',
407 help='Try to use symlinks rather than copies, '
408 'when symlinks are not the default for '
409 'the platform.')
410 group.add_argument('--copies', default=not use_symlinks,
411 action='store_false', dest='symlinks',
412 help='Try to use copies rather than symlinks, '
413 'even when symlinks are the default for '
414 'the platform.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100415 parser.add_argument('--clear', default=False, action='store_true',
Vinay Sajipbd40d3e2012-10-11 17:22:45 +0100416 dest='clear', help='Delete the contents of the '
417 'environment directory if it '
418 'already exists, before '
419 'environment creation.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100420 parser.add_argument('--upgrade', default=False, action='store_true',
421 dest='upgrade', help='Upgrade the environment '
422 'directory to use this version '
Vinay Sajip42211422012-05-26 20:36:12 +0100423 'of Python, assuming Python '
424 'has been upgraded in-place.')
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000425 parser.add_argument('--without-pip', dest='with_pip',
426 default=True, action='store_false',
427 help='Skips installing or upgrading pip in the '
428 'virtual environment (pip is bootstrapped '
429 'by default)')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100430 options = parser.parse_args(args)
431 if options.upgrade and options.clear:
432 raise ValueError('you cannot supply --upgrade and --clear together.')
Vinay Sajip44697462012-05-28 16:33:01 +0100433 builder = EnvBuilder(system_site_packages=options.system_site,
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000434 clear=options.clear,
435 symlinks=options.symlinks,
436 upgrade=options.upgrade,
437 with_pip=options.with_pip)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100438 for d in options.dirs:
439 builder.create(d)
440
441if __name__ == '__main__':
442 rc = 1
443 try:
444 main()
445 rc = 0
446 except Exception as e:
447 print('Error: %s' % e, file=sys.stderr)
448 sys.exit(rc)