blob: 14158e97d59f80ebaedfe8c3ab4edf9b479b4e31 [file] [log] [blame]
Vinay Sajip42211422012-05-26 20:36:12 +01001"""
2Virtual environment (venv) package for Python. Based on PEP 405.
3
Vinay Sajip28952442012-06-25 00:47:46 +01004Copyright (C) 2011-2012 Vinay Sajip.
5Licensed 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
Nick Coghlan8fbdb092013-11-23 00:30:34 +100033import subprocess
Vinay Sajip7ded1f02012-05-26 03:45:29 +010034import sys
Vinay Sajip44697462012-05-28 16:33:01 +010035import sysconfig
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'
136 libpath = os.path.join(env_dir, 'lib', 'python%d.%d' % sys.version_info[:2], 'site-packages')
137 context.inc_path = path = os.path.join(env_dir, incpath)
138 create_if_needed(path)
139 create_if_needed(libpath)
140 context.bin_path = binpath = os.path.join(env_dir, binname)
141 context.bin_name = binname
142 context.env_exe = os.path.join(binpath, exename)
143 create_if_needed(binpath)
144 return context
145
146 def create_configuration(self, context):
147 """
148 Create a configuration file indicating where the environment's Python
149 was copied from, and whether the system site-packages should be made
150 available in the environment.
151
152 :param context: The information for the environment creation request
153 being processed.
154 """
155 context.cfg_path = path = os.path.join(context.env_dir, 'pyvenv.cfg')
156 with open(path, 'w', encoding='utf-8') as f:
157 f.write('home = %s\n' % context.python_dir)
158 if self.system_site_packages:
159 incl = 'true'
160 else:
161 incl = 'false'
162 f.write('include-system-site-packages = %s\n' % incl)
163 f.write('version = %d.%d.%d\n' % sys.version_info[:3])
164
165 if os.name == 'nt':
166 def include_binary(self, f):
167 if f.endswith(('.pyd', '.dll')):
168 result = True
169 else:
170 result = f.startswith('python') and f.endswith('.exe')
171 return result
172
173 def symlink_or_copy(self, src, dst):
174 """
175 Try symlinking a file, and if that fails, fall back to copying.
176 """
177 force_copy = not self.symlinks
178 if not force_copy:
179 try:
180 if not os.path.islink(dst): # can't link to itself!
181 os.symlink(src, dst)
182 except Exception: # may need to use a more specific exception
183 logger.warning('Unable to symlink %r to %r', src, dst)
184 force_copy = True
185 if force_copy:
186 shutil.copyfile(src, dst)
187
188 def setup_python(self, context):
189 """
190 Set up a Python executable in the environment.
191
192 :param context: The information for the environment creation request
193 being processed.
194 """
195 binpath = context.bin_path
196 exename = context.python_exe
197 path = context.env_exe
198 copier = self.symlink_or_copy
199 copier(context.executable, path)
200 dirname = context.python_dir
201 if os.name != 'nt':
202 if not os.path.islink(path):
203 os.chmod(path, 0o755)
Vinay Sajip44697462012-05-28 16:33:01 +0100204 for suffix in ('python', 'python3'):
205 path = os.path.join(binpath, suffix)
206 if not os.path.exists(path):
207 os.symlink(exename, path)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100208 else:
209 subdir = 'DLLs'
210 include = self.include_binary
211 files = [f for f in os.listdir(dirname) if include(f)]
212 for f in files:
213 src = os.path.join(dirname, f)
214 dst = os.path.join(binpath, f)
215 if dst != context.env_exe: # already done, above
216 copier(src, dst)
217 dirname = os.path.join(dirname, subdir)
218 if os.path.isdir(dirname):
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 copier(src, dst)
224 # copy init.tcl over
225 for root, dirs, files in os.walk(context.python_dir):
226 if 'init.tcl' in files:
227 tcldir = os.path.basename(root)
228 tcldir = os.path.join(context.env_dir, 'Lib', tcldir)
229 os.makedirs(tcldir)
230 src = os.path.join(root, 'init.tcl')
231 dst = os.path.join(tcldir, 'init.tcl')
232 shutil.copyfile(src, dst)
233 break
234
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000235 def _setup_pip(self, context):
236 """Installs or upgrades pip in a virtual environment"""
Nick Coghland76cdc12013-11-23 11:37:28 +1000237 # We run ensurepip in isolated mode to avoid side effects from
238 # environment vars, the current directory and anything else
239 # intended for the global Python environment
240 cmd = [context.env_exe, '-Im', 'ensurepip', '--upgrade',
241 '--default-pip']
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000242 subprocess.check_output(cmd)
243
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100244 def setup_scripts(self, context):
245 """
246 Set up scripts into the created environment from a directory.
247
248 This method installs the default scripts into the environment
249 being created. You can prevent the default installation by overriding
250 this method if you really need to, or if you need to specify
251 a different location for the scripts to install. By default, the
252 'scripts' directory in the venv package is used as the source of
253 scripts to install.
254 """
255 path = os.path.abspath(os.path.dirname(__file__))
256 path = os.path.join(path, 'scripts')
257 self.install_scripts(context, path)
258
259 def post_setup(self, context):
260 """
261 Hook for post-setup modification of the venv. Subclasses may install
262 additional packages or scripts here, add activation shell scripts, etc.
263
264 :param context: The information for the environment creation request
265 being processed.
266 """
267 pass
268
269 def replace_variables(self, text, context):
270 """
271 Replace variable placeholders in script text with context-specific
272 variables.
273
274 Return the text passed in , but with variables replaced.
275
276 :param text: The text in which to replace placeholder variables.
277 :param context: The information for the environment creation request
278 being processed.
279 """
280 text = text.replace('__VENV_DIR__', context.env_dir)
Vinay Sajipdff9e252013-10-02 11:36:16 +0100281 text = text.replace('__VENV_NAME__', context.env_name)
282 text = text.replace('__VENV_PROMPT__', context.prompt)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100283 text = text.replace('__VENV_BIN_NAME__', context.bin_name)
284 text = text.replace('__VENV_PYTHON__', context.env_exe)
285 return text
286
287 def install_scripts(self, context, path):
288 """
289 Install scripts into the created environment from a directory.
290
291 :param context: The information for the environment creation request
292 being processed.
293 :param path: Absolute pathname of a directory containing script.
294 Scripts in the 'common' subdirectory of this directory,
295 and those in the directory named for the platform
296 being run on, are installed in the created environment.
297 Placeholder variables are replaced with environment-
298 specific values.
299 """
300 binpath = context.bin_path
301 plen = len(path)
302 for root, dirs, files in os.walk(path):
303 if root == path: # at top-level, remove irrelevant dirs
304 for d in dirs[:]:
305 if d not in ('common', os.name):
306 dirs.remove(d)
307 continue # ignore files in top level
308 for f in files:
309 srcfile = os.path.join(root, f)
310 suffix = root[plen:].split(os.sep)[2:]
311 if not suffix:
312 dstdir = binpath
313 else:
314 dstdir = os.path.join(binpath, *suffix)
315 if not os.path.exists(dstdir):
316 os.makedirs(dstdir)
317 dstfile = os.path.join(dstdir, f)
318 with open(srcfile, 'rb') as f:
319 data = f.read()
320 if srcfile.endswith('.exe'):
321 mode = 'wb'
322 else:
323 mode = 'w'
Vinay Sajipbdd13fd2012-10-28 12:39:39 +0000324 try:
325 data = data.decode('utf-8')
326 data = self.replace_variables(data, context)
327 except UnicodeDecodeError as e:
328 data = None
329 logger.warning('unable to copy script %r, '
330 'may be binary: %s', srcfile, e)
331 if data is not None:
332 with open(dstfile, mode) as f:
333 f.write(data)
334 shutil.copymode(srcfile, dstfile)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100335
336
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000337def create(env_dir, system_site_packages=False, clear=False,
338 symlinks=False, with_pip=False):
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100339 """
340 Create a virtual environment in a directory.
341
Vinay Sajip87ed5992012-11-14 11:18:35 +0000342 By default, makes the system (global) site-packages dir *un*available to
343 the created environment, and uses copying rather than symlinking for files
344 obtained from the source Python installation.
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100345
346 :param env_dir: The target directory to create an environment in.
347 :param system_site_packages: If True, the system (global) site-packages
348 dir is available to the environment.
349 :param clear: If True and the target directory exists, it is deleted.
350 Otherwise, if the target directory exists, an error is
351 raised.
352 :param symlinks: If True, attempt to symlink rather than copy files into
353 virtual environment.
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000354 :param with_pip: If True, ensure pip is installed in the virtual
355 environment
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100356 """
Vinay Sajip44697462012-05-28 16:33:01 +0100357 builder = EnvBuilder(system_site_packages=system_site_packages,
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000358 clear=clear, symlinks=symlinks, with_pip=with_pip)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100359 builder.create(env_dir)
360
361def main(args=None):
362 compatible = True
363 if sys.version_info < (3, 3):
364 compatible = False
365 elif not hasattr(sys, 'base_prefix'):
366 compatible = False
367 if not compatible:
Vinay Sajip0e6c66d2013-10-31 18:44:04 +0000368 raise ValueError('This script is only for use with Python >= 3.3')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100369 else:
370 import argparse
371
372 parser = argparse.ArgumentParser(prog=__name__,
373 description='Creates virtual Python '
374 'environments in one or '
375 'more target '
Vinay Sajip4d378d82012-07-08 17:50:42 +0100376 'directories.',
377 epilog='Once an environment has been '
378 'created, you may wish to '
379 'activate it, e.g. by '
380 'sourcing an activate script '
381 'in its bin directory.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100382 parser.add_argument('dirs', metavar='ENV_DIR', nargs='+',
383 help='A directory to create the environment in.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100384 parser.add_argument('--system-site-packages', default=False,
385 action='store_true', dest='system_site',
Vinay Sajip44697462012-05-28 16:33:01 +0100386 help='Give the virtual environment access to the '
387 'system site-packages dir.')
Vinay Sajip90db6612012-07-17 17:33:46 +0100388 if os.name == 'nt':
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100389 use_symlinks = False
390 else:
391 use_symlinks = True
Vinay Sajip59390272013-08-25 00:04:06 +0100392 group = parser.add_mutually_exclusive_group()
393 group.add_argument('--symlinks', default=use_symlinks,
394 action='store_true', dest='symlinks',
395 help='Try to use symlinks rather than copies, '
396 'when symlinks are not the default for '
397 'the platform.')
398 group.add_argument('--copies', default=not use_symlinks,
399 action='store_false', dest='symlinks',
400 help='Try to use copies rather than symlinks, '
401 'even when symlinks are the default for '
402 'the platform.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100403 parser.add_argument('--clear', default=False, action='store_true',
Vinay Sajipbd40d3e2012-10-11 17:22:45 +0100404 dest='clear', help='Delete the contents of the '
405 'environment directory if it '
406 'already exists, before '
407 'environment creation.')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100408 parser.add_argument('--upgrade', default=False, action='store_true',
409 dest='upgrade', help='Upgrade the environment '
410 'directory to use this version '
Vinay Sajip42211422012-05-26 20:36:12 +0100411 'of Python, assuming Python '
412 'has been upgraded in-place.')
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000413 parser.add_argument('--without-pip', dest='with_pip',
414 default=True, action='store_false',
415 help='Skips installing or upgrading pip in the '
416 'virtual environment (pip is bootstrapped '
417 'by default)')
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100418 options = parser.parse_args(args)
419 if options.upgrade and options.clear:
420 raise ValueError('you cannot supply --upgrade and --clear together.')
Vinay Sajip44697462012-05-28 16:33:01 +0100421 builder = EnvBuilder(system_site_packages=options.system_site,
Nick Coghlan8fbdb092013-11-23 00:30:34 +1000422 clear=options.clear,
423 symlinks=options.symlinks,
424 upgrade=options.upgrade,
425 with_pip=options.with_pip)
Vinay Sajip7ded1f02012-05-26 03:45:29 +0100426 for d in options.dirs:
427 builder.create(d)
428
429if __name__ == '__main__':
430 rc = 1
431 try:
432 main()
433 rc = 0
434 except Exception as e:
435 print('Error: %s' % e, file=sys.stderr)
436 sys.exit(rc)