blob: 20dafc0353fdc0a5a7107c5e2ec0e15d73b02a7c [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.
22 --clear Delete the environment directory if it already exists.
23 If not specified and the directory exists, an error is
24 raised.
25 --upgrade Upgrade the environment directory to use this version
26 of Python, assuming Python has been upgraded in-place.
Nick Coghlan8fbdb092013-11-23 00:30:34 +100027 --without-pip Skips installing or upgrading pip in the virtual
28 environment (pip is bootstrapped by default)
Vinay Sajip42211422012-05-26 20:36:12 +010029"""
Vinay Sajip7ded1f02012-05-26 03:45:29 +010030import logging
31import os
Vinay Sajip7ded1f02012-05-26 03:45:29 +010032import shutil
Vinay Sajip1e53f8d2014-04-15 11:18:10 +010033import struct
Nick Coghlan8fbdb092013-11-23 00:30:34 +100034import subprocess
Vinay Sajip7ded1f02012-05-26 03:45:29 +010035import sys
Vinay Sajipc07aa9e2013-07-12 21:10:19 +010036import types
Vinay Sajip7ded1f02012-05-26 03:45:29 +010037
38logger = logging.getLogger(__name__)
39
Vinay Sajip7ded1f02012-05-26 03:45:29 +010040
41class EnvBuilder:
42 """
43 This class exists to allow virtual environment creation to be
Vinay Sajip9c10d6b2013-11-15 20:58:13 +000044 customized. The constructor parameters determine the builder's
Vinay Sajip7ded1f02012-05-26 03:45:29 +010045 behaviour when called upon to create a virtual environment.
46
47 By default, the builder makes the system (global) site-packages dir
Vinay Sajip87ed5992012-11-14 11:18:35 +000048 *un*available to the created environment.
Vinay Sajip7ded1f02012-05-26 03:45:29 +010049
Vinay Sajip87ed5992012-11-14 11:18:35 +000050 If invoked using the Python -m option, the default is to use copying
51 on Windows platforms but symlinks elsewhere. If instantiated some
52 other way, the default is to *not* use symlinks.
Vinay Sajip7ded1f02012-05-26 03:45:29 +010053
54 :param system_site_packages: If True, the system (global) site-packages
55 dir is available to created environments.
56 :param clear: If True and the target directory exists, it is deleted.
57 Otherwise, if the target directory exists, an error is
58 raised.
59 :param symlinks: If True, attempt to symlink rather than copy files into
60 virtual environment.
61 :param upgrade: If True, upgrade an existing virtual environment.
Nick Coghlan8fbdb092013-11-23 00:30:34 +100062 :param with_pip: If True, ensure pip is installed in the virtual
63 environment
Vinay Sajip7ded1f02012-05-26 03:45:29 +010064 """
65
66 def __init__(self, system_site_packages=False, clear=False,
Nick Coghlan8fbdb092013-11-23 00:30:34 +100067 symlinks=False, upgrade=False, with_pip=False):
Vinay Sajip7ded1f02012-05-26 03:45:29 +010068 self.system_site_packages = system_site_packages
69 self.clear = clear
70 self.symlinks = symlinks
71 self.upgrade = upgrade
Nick Coghlan8fbdb092013-11-23 00:30:34 +100072 self.with_pip = with_pip
Vinay Sajip7ded1f02012-05-26 03:45:29 +010073
74 def create(self, env_dir):
75 """
76 Create a virtual environment in a directory.
77
78 :param env_dir: The target directory to create an environment in.
79
80 """
Vinay Sajip7ded1f02012-05-26 03:45:29 +010081 env_dir = os.path.abspath(env_dir)
82 context = self.ensure_directories(env_dir)
83 self.create_configuration(context)
84 self.setup_python(context)
Nick Coghlan8fbdb092013-11-23 00:30:34 +100085 if self.with_pip:
86 self._setup_pip(context)
Vinay Sajip7ded1f02012-05-26 03:45:29 +010087 if not self.upgrade:
88 self.setup_scripts(context)
89 self.post_setup(context)
90
Vinay Sajipbd40d3e2012-10-11 17:22:45 +010091 def clear_directory(self, path):
92 for fn in os.listdir(path):
93 fn = os.path.join(path, fn)
94 if os.path.islink(fn) or os.path.isfile(fn):
95 os.remove(fn)
96 elif os.path.isdir(fn):
97 shutil.rmtree(fn)
98
Vinay Sajip7ded1f02012-05-26 03:45:29 +010099 def ensure_directories(self, env_dir):
100 """
101 Create the directories for the environment.
102
103 Returns a context object which holds paths in the environment,
104 for use by subsequent logic.
105 """
106
107 def create_if_needed(d):
108 if not os.path.exists(d):
109 os.makedirs(d)
Vinay Sajipbd40d3e2012-10-11 17:22:45 +0100110 elif os.path.islink(d) or os.path.isfile(d):
111 raise ValueError('Unable to create directory %r' % d)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100112
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100113 if os.path.exists(env_dir) and self.clear:
Vinay Sajipbd40d3e2012-10-11 17:22:45 +0100114 self.clear_directory(env_dir)
Vinay Sajipc07aa9e2013-07-12 21:10:19 +0100115 context = types.SimpleNamespace()
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100116 context.env_dir = env_dir
117 context.env_name = os.path.split(env_dir)[1]
118 context.prompt = '(%s) ' % context.env_name
119 create_if_needed(env_dir)
120 env = os.environ
Vinay Sajip28952442012-06-25 00:47:46 +0100121 if sys.platform == 'darwin' and '__PYVENV_LAUNCHER__' in env:
122 executable = os.environ['__PYVENV_LAUNCHER__']
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100123 else:
124 executable = sys.executable
125 dirname, exename = os.path.split(os.path.abspath(executable))
126 context.executable = executable
127 context.python_dir = dirname
128 context.python_exe = exename
129 if sys.platform == 'win32':
130 binname = 'Scripts'
131 incpath = 'Include'
132 libpath = os.path.join(env_dir, 'Lib', 'site-packages')
133 else:
134 binname = 'bin'
135 incpath = 'include'
Vinay Sajip1e53f8d2014-04-15 11:18:10 +0100136 libpath = os.path.join(env_dir, 'lib',
137 'python%d.%d' % sys.version_info[:2],
138 'site-packages')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100139 context.inc_path = path = os.path.join(env_dir, incpath)
140 create_if_needed(path)
141 create_if_needed(libpath)
Vinay Sajip1e53f8d2014-04-15 11:18:10 +0100142 # Issue 21197: create lib64 as a symlink to lib on 64-bit non-OS X POSIX
143 if ((struct.calcsize('P') == 8) and (os.name == 'posix') and
144 (sys.platform != 'darwin')):
145 p = os.path.join(env_dir, 'lib')
146 link_path = os.path.join(env_dir, 'lib64')
147 os.symlink(p, link_path)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100148 context.bin_path = binpath = os.path.join(env_dir, binname)
149 context.bin_name = binname
150 context.env_exe = os.path.join(binpath, exename)
151 create_if_needed(binpath)
152 return context
153
154 def create_configuration(self, context):
155 """
156 Create a configuration file indicating where the environment's Python
157 was copied from, and whether the system site-packages should be made
158 available in the environment.
159
160 :param context: The information for the environment creation request
161 being processed.
162 """
163 context.cfg_path = path = os.path.join(context.env_dir, 'pyvenv.cfg')
164 with open(path, 'w', encoding='utf-8') as f:
165 f.write('home = %s\n' % context.python_dir)
166 if self.system_site_packages:
167 incl = 'true'
168 else:
169 incl = 'false'
170 f.write('include-system-site-packages = %s\n' % incl)
171 f.write('version = %d.%d.%d\n' % sys.version_info[:3])
172
173 if os.name == 'nt':
174 def include_binary(self, f):
175 if f.endswith(('.pyd', '.dll')):
176 result = True
177 else:
178 result = f.startswith('python') and f.endswith('.exe')
179 return result
180
181 def symlink_or_copy(self, src, dst):
182 """
183 Try symlinking a file, and if that fails, fall back to copying.
184 """
185 force_copy = not self.symlinks
186 if not force_copy:
187 try:
188 if not os.path.islink(dst): # can't link to itself!
189 os.symlink(src, dst)
190 except Exception: # may need to use a more specific exception
191 logger.warning('Unable to symlink %r to %r', src, dst)
192 force_copy = True
193 if force_copy:
194 shutil.copyfile(src, dst)
195
196 def setup_python(self, context):
197 """
198 Set up a Python executable in the environment.
199
200 :param context: The information for the environment creation request
201 being processed.
202 """
203 binpath = context.bin_path
204 exename = context.python_exe
205 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):
215 os.symlink(exename, path)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100216 else:
217 subdir = 'DLLs'
218 include = self.include_binary
219 files = [f for f in os.listdir(dirname) if include(f)]
220 for f in files:
221 src = os.path.join(dirname, f)
222 dst = os.path.join(binpath, f)
223 if dst != context.env_exe: # already done, above
224 copier(src, dst)
225 dirname = os.path.join(dirname, subdir)
226 if os.path.isdir(dirname):
227 files = [f for f in os.listdir(dirname) if include(f)]
228 for f in files:
229 src = os.path.join(dirname, f)
230 dst = os.path.join(binpath, f)
231 copier(src, dst)
232 # copy init.tcl over
233 for root, dirs, files in os.walk(context.python_dir):
234 if 'init.tcl' in files:
235 tcldir = os.path.basename(root)
236 tcldir = os.path.join(context.env_dir, 'Lib', tcldir)
237 os.makedirs(tcldir)
238 src = os.path.join(root, 'init.tcl')
239 dst = os.path.join(tcldir, 'init.tcl')
240 shutil.copyfile(src, dst)
241 break
242
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000243 def _setup_pip(self, context):
244 """Installs or upgrades pip in a virtual environment"""
Nick Coghland76cdc12013-11-23 11:37:28 +1000245 # We run ensurepip in isolated mode to avoid side effects from
246 # environment vars, the current directory and anything else
247 # intended for the global Python environment
Nick Coghlan1631b9b2013-11-24 11:53:03 +1000248 cmd = [context.env_exe, '-Im', 'ensurepip', '--upgrade',
Nick Coghland76cdc12013-11-23 11:37:28 +1000249 '--default-pip']
Nick Coghlan6fd12f22013-11-24 11:36:31 +1000250 subprocess.check_output(cmd, stderr=subprocess.STDOUT)
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000251
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100252 def setup_scripts(self, context):
253 """
254 Set up scripts into the created environment from a directory.
255
256 This method installs the default scripts into the environment
257 being created. You can prevent the default installation by overriding
258 this method if you really need to, or if you need to specify
259 a different location for the scripts to install. By default, the
260 'scripts' directory in the venv package is used as the source of
261 scripts to install.
262 """
263 path = os.path.abspath(os.path.dirname(__file__))
264 path = os.path.join(path, 'scripts')
265 self.install_scripts(context, path)
266
267 def post_setup(self, context):
268 """
269 Hook for post-setup modification of the venv. Subclasses may install
270 additional packages or scripts here, add activation shell scripts, etc.
271
272 :param context: The information for the environment creation request
273 being processed.
274 """
275 pass
276
277 def replace_variables(self, text, context):
278 """
279 Replace variable placeholders in script text with context-specific
280 variables.
281
282 Return the text passed in , but with variables replaced.
283
284 :param text: The text in which to replace placeholder variables.
285 :param context: The information for the environment creation request
286 being processed.
287 """
288 text = text.replace('__VENV_DIR__', context.env_dir)
Vinay Sajipdff9e252013-10-02 11:36:16 +0100289 text = text.replace('__VENV_NAME__', context.env_name)
290 text = text.replace('__VENV_PROMPT__', context.prompt)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100291 text = text.replace('__VENV_BIN_NAME__', context.bin_name)
292 text = text.replace('__VENV_PYTHON__', context.env_exe)
293 return text
294
295 def install_scripts(self, context, path):
296 """
297 Install scripts into the created environment from a directory.
298
299 :param context: The information for the environment creation request
300 being processed.
301 :param path: Absolute pathname of a directory containing script.
302 Scripts in the 'common' subdirectory of this directory,
303 and those in the directory named for the platform
304 being run on, are installed in the created environment.
305 Placeholder variables are replaced with environment-
306 specific values.
307 """
308 binpath = context.bin_path
309 plen = len(path)
310 for root, dirs, files in os.walk(path):
311 if root == path: # at top-level, remove irrelevant dirs
312 for d in dirs[:]:
313 if d not in ('common', os.name):
314 dirs.remove(d)
315 continue # ignore files in top level
316 for f in files:
317 srcfile = os.path.join(root, f)
318 suffix = root[plen:].split(os.sep)[2:]
319 if not suffix:
320 dstdir = binpath
321 else:
322 dstdir = os.path.join(binpath, *suffix)
323 if not os.path.exists(dstdir):
324 os.makedirs(dstdir)
325 dstfile = os.path.join(dstdir, f)
326 with open(srcfile, 'rb') as f:
327 data = f.read()
328 if srcfile.endswith('.exe'):
329 mode = 'wb'
330 else:
331 mode = 'w'
Vinay Sajipbdd13fd2012-10-28 12:39:39 +0000332 try:
333 data = data.decode('utf-8')
334 data = self.replace_variables(data, context)
335 except UnicodeDecodeError as e:
336 data = None
337 logger.warning('unable to copy script %r, '
338 'may be binary: %s', srcfile, e)
339 if data is not None:
340 with open(dstfile, mode) as f:
341 f.write(data)
342 shutil.copymode(srcfile, dstfile)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100343
344
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000345def create(env_dir, system_site_packages=False, clear=False,
346 symlinks=False, with_pip=False):
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100347 """
348 Create a virtual environment in a directory.
349
Vinay Sajip87ed5992012-11-14 11:18:35 +0000350 By default, makes the system (global) site-packages dir *un*available to
351 the created environment, and uses copying rather than symlinking for files
352 obtained from the source Python installation.
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100353
354 :param env_dir: The target directory to create an environment in.
355 :param system_site_packages: If True, the system (global) site-packages
356 dir is available to the environment.
357 :param clear: If True and the target directory exists, it is deleted.
358 Otherwise, if the target directory exists, an error is
359 raised.
360 :param symlinks: If True, attempt to symlink rather than copy files into
361 virtual environment.
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000362 :param with_pip: If True, ensure pip is installed in the virtual
363 environment
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100364 """
Vinay Sajip44697462012-05-28 16:33:01 +0100365 builder = EnvBuilder(system_site_packages=system_site_packages,
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000366 clear=clear, symlinks=symlinks, with_pip=with_pip)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100367 builder.create(env_dir)
368
369def main(args=None):
370 compatible = True
371 if sys.version_info < (3, 3):
372 compatible = False
373 elif not hasattr(sys, 'base_prefix'):
374 compatible = False
375 if not compatible:
Vinay Sajip0e6c66d2013-10-31 18:44:04 +0000376 raise ValueError('This script is only for use with Python >= 3.3')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100377 else:
378 import argparse
379
380 parser = argparse.ArgumentParser(prog=__name__,
381 description='Creates virtual Python '
382 'environments in one or '
383 'more target '
Vinay Sajip4d378d82012-07-08 17:50:42 +0100384 'directories.',
385 epilog='Once an environment has been '
386 'created, you may wish to '
387 'activate it, e.g. by '
388 'sourcing an activate script '
389 'in its bin directory.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100390 parser.add_argument('dirs', metavar='ENV_DIR', nargs='+',
391 help='A directory to create the environment in.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100392 parser.add_argument('--system-site-packages', default=False,
393 action='store_true', dest='system_site',
Vinay Sajip44697462012-05-28 16:33:01 +0100394 help='Give the virtual environment access to the '
395 'system site-packages dir.')
Vinay Sajip90db6612012-07-17 17:33:46 +0100396 if os.name == 'nt':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100397 use_symlinks = False
398 else:
399 use_symlinks = True
Vinay Sajip59390272013-08-25 00:04:06 +0100400 group = parser.add_mutually_exclusive_group()
401 group.add_argument('--symlinks', default=use_symlinks,
402 action='store_true', dest='symlinks',
403 help='Try to use symlinks rather than copies, '
404 'when symlinks are not the default for '
405 'the platform.')
406 group.add_argument('--copies', default=not use_symlinks,
407 action='store_false', dest='symlinks',
408 help='Try to use copies rather than symlinks, '
409 'even when symlinks are the default for '
410 'the platform.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100411 parser.add_argument('--clear', default=False, action='store_true',
Vinay Sajipbd40d3e2012-10-11 17:22:45 +0100412 dest='clear', help='Delete the contents of the '
413 'environment directory if it '
414 'already exists, before '
415 'environment creation.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100416 parser.add_argument('--upgrade', default=False, action='store_true',
417 dest='upgrade', help='Upgrade the environment '
418 'directory to use this version '
Vinay Sajip42211422012-05-26 20:36:12 +0100419 'of Python, assuming Python '
420 'has been upgraded in-place.')
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000421 parser.add_argument('--without-pip', dest='with_pip',
422 default=True, action='store_false',
423 help='Skips installing or upgrading pip in the '
424 'virtual environment (pip is bootstrapped '
425 'by default)')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100426 options = parser.parse_args(args)
427 if options.upgrade and options.clear:
428 raise ValueError('you cannot supply --upgrade and --clear together.')
Vinay Sajip44697462012-05-28 16:33:01 +0100429 builder = EnvBuilder(system_site_packages=options.system_site,
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000430 clear=options.clear,
431 symlinks=options.symlinks,
432 upgrade=options.upgrade,
433 with_pip=options.with_pip)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100434 for d in options.dirs:
435 builder.create(d)
436
437if __name__ == '__main__':
438 rc = 1
439 try:
440 main()
441 rc = 0
442 except Exception as e:
443 print('Error: %s' % e, file=sys.stderr)
444 sys.exit(rc)