blob: 486e2da373a2c6d909bb19cde7219380df0d83b3 [file] [log] [blame]
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001"""packaging.util
2Miscellaneous utility functions.
3"""
4import errno
5import csv
6import hashlib
7import os
8import sys
9import re
10import shutil
11import string
12import tarfile
13import zipfile
14import posixpath
15import sysconfig
16import subprocess
17from copy import copy
18from glob import iglob as std_iglob
19from fnmatch import fnmatchcase
20from inspect import getsource
21from configparser import RawConfigParser
22
23from packaging import logger
24from packaging.errors import (PackagingPlatformError, PackagingFileError,
25 PackagingByteCompileError, PackagingExecError,
26 InstallationException, PackagingInternalError)
27
28_PLATFORM = None
29_DEFAULT_INSTALLER = 'packaging'
30
31
32def newer(source, target):
33 """Tell if the target is newer than the source.
34
35 Returns true if 'source' exists and is more recently modified than
36 'target', or if 'source' exists and 'target' doesn't.
37
38 Returns false if both exist and 'target' is the same age or younger
39 than 'source'. Raise PackagingFileError if 'source' does not exist.
40
41 Note that this test is not very accurate: files created in the same second
42 will have the same "age".
43 """
44 if not os.path.exists(source):
45 raise PackagingFileError("file '%s' does not exist" %
46 os.path.abspath(source))
47 if not os.path.exists(target):
48 return True
49
50 return os.stat(source).st_mtime > os.stat(target).st_mtime
51
52
53def get_platform():
54 """Return a string that identifies the current platform.
55
56 By default, will return the value returned by sysconfig.get_platform(),
57 but it can be changed by calling set_platform().
58 """
59 global _PLATFORM
60 if _PLATFORM is None:
61 _PLATFORM = sysconfig.get_platform()
62 return _PLATFORM
63
64
65def set_platform(identifier):
66 """Set the platform string identifier returned by get_platform().
67
68 Note that this change doesn't impact the value returned by
69 sysconfig.get_platform(); it is local to packaging.
70 """
71 global _PLATFORM
72 _PLATFORM = identifier
73
74
75def convert_path(pathname):
76 """Return 'pathname' as a name that will work on the native filesystem.
77
78 The path is split on '/' and put back together again using the current
79 directory separator. Needed because filenames in the setup script are
80 always supplied in Unix style, and have to be converted to the local
81 convention before we can actually use them in the filesystem. Raises
82 ValueError on non-Unix-ish systems if 'pathname' either starts or
83 ends with a slash.
84 """
85 if os.sep == '/':
86 return pathname
87 if not pathname:
88 return pathname
89 if pathname[0] == '/':
90 raise ValueError("path '%s' cannot be absolute" % pathname)
91 if pathname[-1] == '/':
92 raise ValueError("path '%s' cannot end with '/'" % pathname)
93
94 paths = pathname.split('/')
95 while os.curdir in paths:
96 paths.remove(os.curdir)
97 if not paths:
98 return os.curdir
99 return os.path.join(*paths)
100
101
102def change_root(new_root, pathname):
103 """Return 'pathname' with 'new_root' prepended.
104
105 If 'pathname' is relative, this is equivalent to
106 os.path.join(new_root,pathname). Otherwise, it requires making 'pathname'
107 relative and then joining the two, which is tricky on DOS/Windows.
108 """
109 if os.name == 'posix':
110 if not os.path.isabs(pathname):
111 return os.path.join(new_root, pathname)
112 else:
113 return os.path.join(new_root, pathname[1:])
114
115 elif os.name == 'nt':
116 drive, path = os.path.splitdrive(pathname)
117 if path[0] == '\\':
118 path = path[1:]
119 return os.path.join(new_root, path)
120
121 elif os.name == 'os2':
122 drive, path = os.path.splitdrive(pathname)
123 if path[0] == os.sep:
124 path = path[1:]
125 return os.path.join(new_root, path)
126
127 else:
128 raise PackagingPlatformError("nothing known about "
129 "platform '%s'" % os.name)
130
131_environ_checked = False
132
133
134def check_environ():
135 """Ensure that 'os.environ' has all the environment variables needed.
136
137 We guarantee that users can use in config files, command-line options,
138 etc. Currently this includes:
139 HOME - user's home directory (Unix only)
140 PLAT - description of the current platform, including hardware
141 and OS (see 'get_platform()')
142 """
143 global _environ_checked
144 if _environ_checked:
145 return
146
147 if os.name == 'posix' and 'HOME' not in os.environ:
148 import pwd
149 os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]
150
151 if 'PLAT' not in os.environ:
152 os.environ['PLAT'] = sysconfig.get_platform()
153
154 _environ_checked = True
155
156
157def subst_vars(s, local_vars):
158 """Perform shell/Perl-style variable substitution on 'string'.
159
160 Every occurrence of '$' followed by a name is considered a variable, and
161 variable is substituted by the value found in the 'local_vars'
162 dictionary, or in 'os.environ' if it's not in 'local_vars'.
163 'os.environ' is first checked/augmented to guarantee that it contains
164 certain values: see 'check_environ()'. Raise ValueError for any
165 variables not found in either 'local_vars' or 'os.environ'.
166 """
167 check_environ()
168
169 def _subst(match, local_vars=local_vars):
170 var_name = match.group(1)
171 if var_name in local_vars:
172 return str(local_vars[var_name])
173 else:
174 return os.environ[var_name]
175
176 try:
177 return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s)
178 except KeyError as var:
179 raise ValueError("invalid variable '$%s'" % var)
180
181
182# Needed by 'split_quoted()'
183_wordchars_re = _squote_re = _dquote_re = None
184
185
186def _init_regex():
187 global _wordchars_re, _squote_re, _dquote_re
188 _wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)
189 _squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")
190 _dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
191
192
193def split_quoted(s):
194 """Split a string up according to Unix shell-like rules for quotes and
195 backslashes.
196
197 In short: words are delimited by spaces, as long as those
198 spaces are not escaped by a backslash, or inside a quoted string.
199 Single and double quotes are equivalent, and the quote characters can
200 be backslash-escaped. The backslash is stripped from any two-character
201 escape sequence, leaving only the escaped character. The quote
202 characters are stripped from any quoted string. Returns a list of
203 words.
204 """
205 # This is a nice algorithm for splitting up a single string, since it
206 # doesn't require character-by-character examination. It was a little
207 # bit of a brain-bender to get it working right, though...
208 if _wordchars_re is None:
209 _init_regex()
210
211 s = s.strip()
212 words = []
213 pos = 0
214
215 while s:
216 m = _wordchars_re.match(s, pos)
217 end = m.end()
218 if end == len(s):
219 words.append(s[:end])
220 break
221
222 if s[end] in string.whitespace: # unescaped, unquoted whitespace: now
223 words.append(s[:end]) # we definitely have a word delimiter
224 s = s[end:].lstrip()
225 pos = 0
226
227 elif s[end] == '\\': # preserve whatever is being escaped;
228 # will become part of the current word
229 s = s[:end] + s[end + 1:]
230 pos = end + 1
231
232 else:
233 if s[end] == "'": # slurp singly-quoted string
234 m = _squote_re.match(s, end)
235 elif s[end] == '"': # slurp doubly-quoted string
236 m = _dquote_re.match(s, end)
237 else:
238 raise RuntimeError("this can't happen "
239 "(bad char '%c')" % s[end])
240
241 if m is None:
242 raise ValueError("bad string (mismatched %s quotes?)" % s[end])
243
244 beg, end = m.span()
245 s = s[:beg] + s[beg + 1:end - 1] + s[end:]
246 pos = m.end() - 2
247
248 if pos >= len(s):
249 words.append(s)
250 break
251
252 return words
253
254
255def execute(func, args, msg=None, verbose=0, dry_run=False):
256 """Perform some action that affects the outside world.
257
258 Some actions (e.g. writing to the filesystem) are special because
259 they are disabled by the 'dry_run' flag. This method takes care of all
260 that bureaucracy for you; all you have to do is supply the
261 function to call and an argument tuple for it (to embody the
262 "external action" being performed), and an optional message to
263 print.
264 """
265 if msg is None:
266 msg = "%s%r" % (func.__name__, args)
267 if msg[-2:] == ',)': # correct for singleton tuple
268 msg = msg[0:-2] + ')'
269
270 logger.info(msg)
271 if not dry_run:
272 func(*args)
273
274
275def strtobool(val):
276 """Convert a string representation of truth to true (1) or false (0).
277
278 True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
279 are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
280 'val' is anything else.
281 """
282 val = val.lower()
283 if val in ('y', 'yes', 't', 'true', 'on', '1'):
284 return True
285 elif val in ('n', 'no', 'f', 'false', 'off', '0'):
286 return False
287 else:
288 raise ValueError("invalid truth value %r" % (val,))
289
290
291def byte_compile(py_files, optimize=0, force=False, prefix=None,
292 base_dir=None, verbose=0, dry_run=False, direct=None):
293 """Byte-compile a collection of Python source files to either .pyc
294 or .pyo files in the same directory.
295
296 'py_files' is a list of files to compile; any files that don't end in
297 ".py" are silently skipped. 'optimize' must be one of the following:
298 0 - don't optimize (generate .pyc)
299 1 - normal optimization (like "python -O")
300 2 - extra optimization (like "python -OO")
301 If 'force' is true, all files are recompiled regardless of
302 timestamps.
303
304 The source filename encoded in each bytecode file defaults to the
305 filenames listed in 'py_files'; you can modify these with 'prefix' and
306 'basedir'. 'prefix' is a string that will be stripped off of each
307 source filename, and 'base_dir' is a directory name that will be
308 prepended (after 'prefix' is stripped). You can supply either or both
309 (or neither) of 'prefix' and 'base_dir', as you wish.
310
311 If 'dry_run' is true, doesn't actually do anything that would
312 affect the filesystem.
313
314 Byte-compilation is either done directly in this interpreter process
315 with the standard py_compile module, or indirectly by writing a
316 temporary script and executing it. Normally, you should let
317 'byte_compile()' figure out to use direct compilation or not (see
318 the source for details). The 'direct' flag is used by the script
319 generated in indirect mode; unless you know what you're doing, leave
320 it set to None.
321 """
322 # nothing is done if sys.dont_write_bytecode is True
323 # FIXME this should not raise an error
324 if hasattr(sys, 'dont_write_bytecode') and sys.dont_write_bytecode:
325 raise PackagingByteCompileError('byte-compiling is disabled.')
326
327 # First, if the caller didn't force us into direct or indirect mode,
328 # figure out which mode we should be in. We take a conservative
329 # approach: choose direct mode *only* if the current interpreter is
330 # in debug mode and optimize is 0. If we're not in debug mode (-O
331 # or -OO), we don't know which level of optimization this
332 # interpreter is running with, so we can't do direct
333 # byte-compilation and be certain that it's the right thing. Thus,
334 # always compile indirectly if the current interpreter is in either
335 # optimize mode, or if either optimization level was requested by
336 # the caller.
337 if direct is None:
338 direct = (__debug__ and optimize == 0)
339
340 # "Indirect" byte-compilation: write a temporary script and then
341 # run it with the appropriate flags.
342 if not direct:
343 from tempfile import mkstemp
344 # XXX script_fd may leak, use something better than mkstemp
345 script_fd, script_name = mkstemp(".py")
346 logger.info("writing byte-compilation script '%s'", script_name)
347 if not dry_run:
348 if script_fd is not None:
349 script = os.fdopen(script_fd, "w")
350 else:
351 script = open(script_name, "w")
352
353 try:
354 script.write("""\
355from packaging.util import byte_compile
356files = [
357""")
358
359 # XXX would be nice to write absolute filenames, just for
360 # safety's sake (script should be more robust in the face of
361 # chdir'ing before running it). But this requires abspath'ing
362 # 'prefix' as well, and that breaks the hack in build_lib's
363 # 'byte_compile()' method that carefully tacks on a trailing
364 # slash (os.sep really) to make sure the prefix here is "just
365 # right". This whole prefix business is rather delicate -- the
366 # problem is that it's really a directory, but I'm treating it
367 # as a dumb string, so trailing slashes and so forth matter.
368
369 #py_files = map(os.path.abspath, py_files)
370 #if prefix:
371 # prefix = os.path.abspath(prefix)
372
373 script.write(",\n".join(map(repr, py_files)) + "]\n")
374 script.write("""
375byte_compile(files, optimize=%r, force=%r,
376 prefix=%r, base_dir=%r,
377 verbose=%r, dry_run=False,
378 direct=True)
379""" % (optimize, force, prefix, base_dir, verbose))
380
381 finally:
382 script.close()
383
384 cmd = [sys.executable, script_name]
385 if optimize == 1:
386 cmd.insert(1, "-O")
387 elif optimize == 2:
388 cmd.insert(1, "-OO")
389
390 env = copy(os.environ)
391 env['PYTHONPATH'] = os.path.pathsep.join(sys.path)
392 try:
393 spawn(cmd, env=env)
394 finally:
395 execute(os.remove, (script_name,), "removing %s" % script_name,
396 dry_run=dry_run)
397
398 # "Direct" byte-compilation: use the py_compile module to compile
399 # right here, right now. Note that the script generated in indirect
400 # mode simply calls 'byte_compile()' in direct mode, a weird sort of
401 # cross-process recursion. Hey, it works!
402 else:
403 from py_compile import compile
404
405 for file in py_files:
406 if file[-3:] != ".py":
407 # This lets us be lazy and not filter filenames in
408 # the "install_lib" command.
409 continue
410
411 # Terminology from the py_compile module:
412 # cfile - byte-compiled file
413 # dfile - purported source filename (same as 'file' by default)
414 cfile = file + (__debug__ and "c" or "o")
415 dfile = file
416 if prefix:
417 if file[:len(prefix)] != prefix:
418 raise ValueError("invalid prefix: filename %r doesn't "
419 "start with %r" % (file, prefix))
420 dfile = dfile[len(prefix):]
421 if base_dir:
422 dfile = os.path.join(base_dir, dfile)
423
424 cfile_base = os.path.basename(cfile)
425 if direct:
426 if force or newer(file, cfile):
427 logger.info("byte-compiling %s to %s", file, cfile_base)
428 if not dry_run:
429 compile(file, cfile, dfile)
430 else:
431 logger.debug("skipping byte-compilation of %s to %s",
432 file, cfile_base)
433
434
435def rfc822_escape(header):
436 """Return a form of *header* suitable for inclusion in an RFC 822-header.
437
438 This function ensures there are 8 spaces after each newline.
439 """
440 lines = header.split('\n')
441 sep = '\n' + 8 * ' '
442 return sep.join(lines)
443
444_RE_VERSION = re.compile('(\d+\.\d+(\.\d+)*)')
445_MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld '
446 'PROJECT:ld64-((\d+)(\.\d+)*)')
447
448
449def _find_ld_version():
450 """Find the ld version. The version scheme differs under Mac OS X."""
451 if sys.platform == 'darwin':
452 return _find_exe_version('ld -v', _MAC_OS_X_LD_VERSION)
453 else:
454 return _find_exe_version('ld -v')
455
456
457def _find_exe_version(cmd, pattern=_RE_VERSION):
458 """Find the version of an executable by running `cmd` in the shell.
459
460 `pattern` is a compiled regular expression. If not provided, defaults
461 to _RE_VERSION. If the command is not found, or the output does not
462 match the mattern, returns None.
463 """
464 from subprocess import Popen, PIPE
465 executable = cmd.split()[0]
466 if find_executable(executable) is None:
467 return None
468 pipe = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
469 try:
470 stdout, stderr = pipe.stdout.read(), pipe.stderr.read()
471 finally:
472 pipe.stdout.close()
473 pipe.stderr.close()
474 # some commands like ld under MacOS X, will give the
475 # output in the stderr, rather than stdout.
476 if stdout != '':
477 out_string = stdout
478 else:
479 out_string = stderr
480
481 result = pattern.search(out_string)
482 if result is None:
483 return None
484 return result.group(1)
485
486
487def get_compiler_versions():
488 """Return a tuple providing the versions of gcc, ld and dllwrap
489
490 For each command, if a command is not found, None is returned.
491 Otherwise a string with the version is returned.
492 """
493 gcc = _find_exe_version('gcc -dumpversion')
494 ld = _find_ld_version()
495 dllwrap = _find_exe_version('dllwrap --version')
496 return gcc, ld, dllwrap
497
498
499def newer_group(sources, target, missing='error'):
500 """Return true if 'target' is out-of-date with respect to any file
501 listed in 'sources'.
502
503 In other words, if 'target' exists and is newer
504 than every file in 'sources', return false; otherwise return true.
505 'missing' controls what we do when a source file is missing; the
506 default ("error") is to blow up with an OSError from inside 'stat()';
507 if it is "ignore", we silently drop any missing source files; if it is
508 "newer", any missing source files make us assume that 'target' is
509 out-of-date (this is handy in "dry-run" mode: it'll make you pretend to
510 carry out commands that wouldn't work because inputs are missing, but
511 that doesn't matter because you're not actually going to run the
512 commands).
513 """
514 # If the target doesn't even exist, then it's definitely out-of-date.
515 if not os.path.exists(target):
516 return True
517
518 # Otherwise we have to find out the hard way: if *any* source file
519 # is more recent than 'target', then 'target' is out-of-date and
520 # we can immediately return true. If we fall through to the end
521 # of the loop, then 'target' is up-to-date and we return false.
522 target_mtime = os.stat(target).st_mtime
523
524 for source in sources:
525 if not os.path.exists(source):
526 if missing == 'error': # blow up when we stat() the file
527 pass
528 elif missing == 'ignore': # missing source dropped from
529 continue # target's dependency list
530 elif missing == 'newer': # missing source means target is
531 return True # out-of-date
532
533 if os.stat(source).st_mtime > target_mtime:
534 return True
535
536 return False
537
538
539def write_file(filename, contents):
540 """Create *filename* and write *contents* to it.
541
542 *contents* is a sequence of strings without line terminators.
543 """
544 with open(filename, "w") as f:
545 for line in contents:
546 f.write(line + "\n")
547
548
549def _is_package(path):
550 if not os.path.isdir(path):
551 return False
552 return os.path.isfile(os.path.join(path, '__init__.py'))
553
554
555# Code taken from the pip project
556def _is_archive_file(name):
557 archives = ('.zip', '.tar.gz', '.tar.bz2', '.tgz', '.tar')
558 ext = splitext(name)[1].lower()
559 if ext in archives:
560 return True
561 return False
562
563
564def _under(path, root):
565 path = path.split(os.sep)
566 root = root.split(os.sep)
567 if len(root) > len(path):
568 return False
569 for pos, part in enumerate(root):
570 if path[pos] != part:
571 return False
572 return True
573
574
575def _package_name(root_path, path):
576 # Return a dotted package name, given a subpath
577 if not _under(path, root_path):
578 raise ValueError('"%s" is not a subpath of "%s"' % (path, root_path))
579 return path[len(root_path) + 1:].replace(os.sep, '.')
580
581
582def find_packages(paths=(os.curdir,), exclude=()):
583 """Return a list all Python packages found recursively within
584 directories 'paths'
585
586 'paths' should be supplied as a sequence of "cross-platform"
587 (i.e. URL-style) path; it will be converted to the appropriate local
588 path syntax.
589
590 'exclude' is a sequence of package names to exclude; '*' can be used as
591 a wildcard in the names, such that 'foo.*' will exclude all subpackages
592 of 'foo' (but not 'foo' itself).
593 """
594 packages = []
595 discarded = []
596
597 def _discarded(path):
598 for discard in discarded:
599 if _under(path, discard):
600 return True
601 return False
602
603 for path in paths:
604 path = convert_path(path)
605 for root, dirs, files in os.walk(path):
606 for dir_ in dirs:
607 fullpath = os.path.join(root, dir_)
608 if _discarded(fullpath):
609 continue
610 # we work only with Python packages
611 if not _is_package(fullpath):
612 discarded.append(fullpath)
613 continue
614 # see if it's excluded
615 excluded = False
616 package_name = _package_name(path, fullpath)
617 for pattern in exclude:
618 if fnmatchcase(package_name, pattern):
619 excluded = True
620 break
621 if excluded:
622 continue
623
624 # adding it to the list
625 packages.append(package_name)
626 return packages
627
628
629def resolve_name(name):
630 """Resolve a name like ``module.object`` to an object and return it.
631
632 Raise ImportError if the module or name is not found.
633 """
634 parts = name.split('.')
635 cursor = len(parts)
636 module_name = parts[:cursor]
637
638 while cursor > 0:
639 try:
640 ret = __import__('.'.join(module_name))
641 break
642 except ImportError:
643 if cursor == 0:
644 raise
645 cursor -= 1
646 module_name = parts[:cursor]
647 ret = ''
648
649 for part in parts[1:]:
650 try:
651 ret = getattr(ret, part)
652 except AttributeError as exc:
653 raise ImportError(exc)
654
655 return ret
656
657
658def splitext(path):
659 """Like os.path.splitext, but take off .tar too"""
660 base, ext = posixpath.splitext(path)
661 if base.lower().endswith('.tar'):
662 ext = base[-4:] + ext
663 base = base[:-4]
664 return base, ext
665
666
667def unzip_file(filename, location, flatten=True):
668 """Unzip the file *filename* into the *location* directory."""
669 if not os.path.exists(location):
670 os.makedirs(location)
671 with open(filename, 'rb') as zipfp:
672 zip = zipfile.ZipFile(zipfp)
673 leading = has_leading_dir(zip.namelist()) and flatten
674 for name in zip.namelist():
675 data = zip.read(name)
676 fn = name
677 if leading:
678 fn = split_leading_dir(name)[1]
679 fn = os.path.join(location, fn)
680 dir = os.path.dirname(fn)
681 if not os.path.exists(dir):
682 os.makedirs(dir)
683 if fn.endswith('/') or fn.endswith('\\'):
684 # A directory
685 if not os.path.exists(fn):
686 os.makedirs(fn)
687 else:
688 with open(fn, 'wb') as fp:
689 fp.write(data)
690
691
692def untar_file(filename, location):
693 """Untar the file *filename* into the *location* directory."""
694 if not os.path.exists(location):
695 os.makedirs(location)
696 if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):
697 mode = 'r:gz'
698 elif (filename.lower().endswith('.bz2')
699 or filename.lower().endswith('.tbz')):
700 mode = 'r:bz2'
701 elif filename.lower().endswith('.tar'):
702 mode = 'r'
703 else:
704 mode = 'r:*'
705 with tarfile.open(filename, mode) as tar:
706 leading = has_leading_dir(member.name for member in tar.getmembers())
707 for member in tar.getmembers():
708 fn = member.name
709 if leading:
710 fn = split_leading_dir(fn)[1]
711 path = os.path.join(location, fn)
712 if member.isdir():
713 if not os.path.exists(path):
714 os.makedirs(path)
715 else:
716 try:
717 fp = tar.extractfile(member)
718 except (KeyError, AttributeError):
719 # Some corrupt tar files seem to produce this
720 # (specifically bad symlinks)
721 continue
722 try:
723 if not os.path.exists(os.path.dirname(path)):
724 os.makedirs(os.path.dirname(path))
725 with open(path, 'wb') as destfp:
726 shutil.copyfileobj(fp, destfp)
727 finally:
728 fp.close()
729
730
731def has_leading_dir(paths):
732 """Return true if all the paths have the same leading path name.
733
734 In other words, check that everything is in one subdirectory in an
735 archive.
736 """
737 common_prefix = None
738 for path in paths:
739 prefix, rest = split_leading_dir(path)
740 if not prefix:
741 return False
742 elif common_prefix is None:
743 common_prefix = prefix
744 elif prefix != common_prefix:
745 return False
746 return True
747
748
749def split_leading_dir(path):
750 path = str(path)
751 path = path.lstrip('/').lstrip('\\')
752 if '/' in path and (('\\' in path and path.find('/') < path.find('\\'))
753 or '\\' not in path):
754 return path.split('/', 1)
755 elif '\\' in path:
756 return path.split('\\', 1)
757 else:
758 return path, ''
759
760
761def spawn(cmd, search_path=True, verbose=0, dry_run=False, env=None):
762 """Run another program specified as a command list 'cmd' in a new process.
763
764 'cmd' is just the argument list for the new process, ie.
765 cmd[0] is the program to run and cmd[1:] are the rest of its arguments.
766 There is no way to run a program with a name different from that of its
767 executable.
768
769 If 'search_path' is true (the default), the system's executable
770 search path will be used to find the program; otherwise, cmd[0]
771 must be the exact path to the executable. If 'dry_run' is true,
772 the command will not actually be run.
773
774 If 'env' is given, it's a environment dictionary used for the execution
775 environment.
776
777 Raise PackagingExecError if running the program fails in any way; just
778 return on success.
779 """
780 logger.info(' '.join(cmd))
781 if dry_run:
782 return
783 exit_status = subprocess.call(cmd, env=env)
784 if exit_status != 0:
785 msg = "command '%s' failed with exit status %d"
786 raise PackagingExecError(msg % (cmd, exit_status))
787
788
789def find_executable(executable, path=None):
790 """Try to find 'executable' in the directories listed in 'path'.
791
792 *path* is a string listing directories separated by 'os.pathsep' and
793 defaults to os.environ['PATH']. Returns the complete filename or None
794 if not found.
795 """
796 if path is None:
797 path = os.environ['PATH']
798 paths = path.split(os.pathsep)
799 base, ext = os.path.splitext(executable)
800
801 if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
802 executable = executable + '.exe'
803
804 if not os.path.isfile(executable):
805 for p in paths:
806 f = os.path.join(p, executable)
807 if os.path.isfile(f):
808 # the file exists, we have a shot at spawn working
809 return f
810 return None
811 else:
812 return executable
813
814
815DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
816DEFAULT_REALM = 'pypi'
817DEFAULT_PYPIRC = """\
818[distutils]
819index-servers =
820 pypi
821
822[pypi]
823username:%s
824password:%s
825"""
826
827
828def get_pypirc_path():
829 """Return path to pypirc config file."""
830 return os.path.join(os.path.expanduser('~'), '.pypirc')
831
832
833def generate_pypirc(username, password):
834 """Create a default .pypirc file."""
835 rc = get_pypirc_path()
836 with open(rc, 'w') as f:
837 f.write(DEFAULT_PYPIRC % (username, password))
838 try:
839 os.chmod(rc, 0o600)
840 except OSError:
841 # should do something better here
842 pass
843
844
845def read_pypirc(repository=DEFAULT_REPOSITORY, realm=DEFAULT_REALM):
846 """Read the .pypirc file."""
847 rc = get_pypirc_path()
848 if os.path.exists(rc):
849 config = RawConfigParser()
850 config.read(rc)
851 sections = config.sections()
852 if 'distutils' in sections:
853 # let's get the list of servers
854 index_servers = config.get('distutils', 'index-servers')
855 _servers = [server.strip() for server in
856 index_servers.split('\n')
857 if server.strip() != '']
858 if _servers == []:
859 # nothing set, let's try to get the default pypi
860 if 'pypi' in sections:
861 _servers = ['pypi']
862 else:
863 # the file is not properly defined, returning
864 # an empty dict
865 return {}
866 for server in _servers:
867 current = {'server': server}
868 current['username'] = config.get(server, 'username')
869
870 # optional params
871 for key, default in (('repository', DEFAULT_REPOSITORY),
872 ('realm', DEFAULT_REALM),
873 ('password', None)):
874 if config.has_option(server, key):
875 current[key] = config.get(server, key)
876 else:
877 current[key] = default
878 if (current['server'] == repository or
879 current['repository'] == repository):
880 return current
881 elif 'server-login' in sections:
882 # old format
883 server = 'server-login'
884 if config.has_option(server, 'repository'):
885 repository = config.get(server, 'repository')
886 else:
887 repository = DEFAULT_REPOSITORY
888
889 return {'username': config.get(server, 'username'),
890 'password': config.get(server, 'password'),
891 'repository': repository,
892 'server': server,
893 'realm': DEFAULT_REALM}
894
895 return {}
896
897
898# utility functions for 2to3 support
899
900def run_2to3(files, doctests_only=False, fixer_names=None,
901 options=None, explicit=None):
902 """ Wrapper function around the refactor() class which
903 performs the conversions on a list of python files.
904 Invoke 2to3 on a list of Python files. The files should all come
905 from the build area, as the modification is done in-place."""
906
907 #if not files:
908 # return
909
910 # Make this class local, to delay import of 2to3
911 from lib2to3.refactor import get_fixers_from_package, RefactoringTool
912 fixers = []
913 fixers = get_fixers_from_package('lib2to3.fixes')
914
915 if fixer_names:
916 for fixername in fixer_names:
917 fixers.extend(fixer for fixer in
918 get_fixers_from_package(fixername))
919 r = RefactoringTool(fixers, options=options)
920 r.refactor(files, write=True, doctests_only=doctests_only)
921
922
923class Mixin2to3:
924 """ Wrapper class for commands that run 2to3.
925 To configure 2to3, setup scripts may either change
926 the class variables, or inherit from this class
927 to override how 2to3 is invoked.
928 """
929 # provide list of fixers to run.
930 # defaults to all from lib2to3.fixers
931 fixer_names = None
932
933 # options dictionary
934 options = None
935
936 # list of fixers to invoke even though they are marked as explicit
937 explicit = None
938
939 def run_2to3(self, files, doctests_only=False):
940 """ Issues a call to util.run_2to3. """
941 return run_2to3(files, doctests_only, self.fixer_names,
942 self.options, self.explicit)
943
944RICH_GLOB = re.compile(r'\{([^}]*)\}')
945_CHECK_RECURSIVE_GLOB = re.compile(r'[^/,{]\*\*|\*\*[^/,}]')
946_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
947
948
949def iglob(path_glob):
950 """Extended globbing function that supports ** and {opt1,opt2,opt3}."""
951 if _CHECK_RECURSIVE_GLOB.search(path_glob):
952 msg = """invalid glob %r: recursive glob "**" must be used alone"""
953 raise ValueError(msg % path_glob)
954 if _CHECK_MISMATCH_SET.search(path_glob):
955 msg = """invalid glob %r: mismatching set marker '{' or '}'"""
956 raise ValueError(msg % path_glob)
957 return _iglob(path_glob)
958
959
960def _iglob(path_glob):
961 rich_path_glob = RICH_GLOB.split(path_glob, 1)
962 if len(rich_path_glob) > 1:
963 assert len(rich_path_glob) == 3, rich_path_glob
964 prefix, set, suffix = rich_path_glob
965 for item in set.split(','):
966 for path in _iglob(''.join((prefix, item, suffix))):
967 yield path
968 else:
969 if '**' not in path_glob:
970 for item in std_iglob(path_glob):
971 yield item
972 else:
973 prefix, radical = path_glob.split('**', 1)
974 if prefix == '':
975 prefix = '.'
976 if radical == '':
977 radical = '*'
978 else:
979 radical = radical.lstrip('/')
980 for path, dir, files in os.walk(prefix):
981 path = os.path.normpath(path)
982 for file in _iglob(os.path.join(path, radical)):
983 yield file
984
985
986def cfg_to_args(path='setup.cfg'):
987 """Compatibility helper to use setup.cfg in setup.py.
988
989 This functions uses an existing setup.cfg to generate a dictionnary of
990 keywords that can be used by distutils.core.setup(**kwargs). It is used
991 by generate_setup_py.
992
993 *file* is the path to the setup.cfg file. If it doesn't exist,
994 PackagingFileError is raised.
995 """
996 # We need to declare the following constants here so that it's easier to
997 # generate the setup.py afterwards, using inspect.getsource.
998
999 # XXX ** == needs testing
1000 D1_D2_SETUP_ARGS = {"name": ("metadata",),
1001 "version": ("metadata",),
1002 "author": ("metadata",),
1003 "author_email": ("metadata",),
1004 "maintainer": ("metadata",),
1005 "maintainer_email": ("metadata",),
1006 "url": ("metadata", "home_page"),
1007 "description": ("metadata", "summary"),
1008 "long_description": ("metadata", "description"),
1009 "download-url": ("metadata",),
1010 "classifiers": ("metadata", "classifier"),
1011 "platforms": ("metadata", "platform"), # **
1012 "license": ("metadata",),
1013 "requires": ("metadata", "requires_dist"),
1014 "provides": ("metadata", "provides_dist"), # **
1015 "obsoletes": ("metadata", "obsoletes_dist"), # **
1016 "packages": ("files",),
1017 "scripts": ("files",),
1018 "py_modules": ("files", "modules"), # **
1019 }
1020
1021 MULTI_FIELDS = ("classifiers",
1022 "requires",
1023 "platforms",
1024 "packages",
1025 "scripts")
1026
1027 def has_get_option(config, section, option):
1028 if config.has_option(section, option):
1029 return config.get(section, option)
1030 elif config.has_option(section, option.replace('_', '-')):
1031 return config.get(section, option.replace('_', '-'))
1032 else:
1033 return False
1034
1035 # The real code starts here
1036 config = RawConfigParser()
1037 if not os.path.exists(file):
1038 raise PackagingFileError("file '%s' does not exist" %
1039 os.path.abspath(file))
1040 config.read(path)
1041
1042 kwargs = {}
1043 for arg in D1_D2_SETUP_ARGS:
1044 if len(D1_D2_SETUP_ARGS[arg]) == 2:
1045 # The distutils field name is different than packaging's
1046 section, option = D1_D2_SETUP_ARGS[arg]
1047
1048 else:
1049 # The distutils field name is the same thant packaging's
1050 section = D1_D2_SETUP_ARGS[arg][0]
1051 option = arg
1052
1053 in_cfg_value = has_get_option(config, section, option)
1054 if not in_cfg_value:
1055 # There is no such option in the setup.cfg
1056 if arg == "long_description":
1057 filename = has_get_option(config, section, "description_file")
1058 if filename:
1059 with open(filename) as fp:
1060 in_cfg_value = fp.read()
1061 else:
1062 continue
1063
1064 if arg in MULTI_FIELDS:
1065 # support multiline options
1066 in_cfg_value = in_cfg_value.strip().split('\n')
1067
1068 kwargs[arg] = in_cfg_value
1069
1070 return kwargs
1071
1072
1073_SETUP_TMPL = """\
1074# This script was automatically generated by packaging
1075import os
1076from distutils.core import setup
1077from ConfigParser import RawConfigParser
1078
1079%(func)s
1080
1081setup(**cfg_to_args())
1082"""
1083
1084
1085def generate_setup_py():
1086 """Generate a distutils compatible setup.py using an existing setup.cfg.
1087
1088 Raises a PackagingFileError when a setup.py already exists.
1089 """
1090 if os.path.exists("setup.py"):
1091 raise PackagingFileError("a setup.py file alreadyexists")
1092
1093 with open("setup.py", "w") as fp:
1094 fp.write(_SETUP_TMPL % {'func': getsource(cfg_to_args)})
1095
1096
1097# Taken from the pip project
1098# https://github.com/pypa/pip/blob/master/pip/util.py
1099def ask(message, options):
1100 """Prompt the user with *message*; *options* contains allowed responses."""
1101 while True:
1102 response = input(message)
1103 response = response.strip().lower()
1104 if response not in options:
1105 print('invalid response: %r' % response)
1106 print('choose one of', ', '.join(repr(o) for o in options))
1107 else:
1108 return response
1109
1110
1111def _parse_record_file(record_file):
1112 distinfo, extra_metadata, installed = ({}, [], [])
1113 with open(record_file, 'r') as rfile:
1114 for path in rfile:
1115 path = path.strip()
1116 if path.endswith('egg-info') and os.path.isfile(path):
1117 distinfo_dir = path.replace('egg-info', 'dist-info')
1118 metadata = path
1119 egginfo = path
1120 elif path.endswith('egg-info') and os.path.isdir(path):
1121 distinfo_dir = path.replace('egg-info', 'dist-info')
1122 egginfo = path
1123 for metadata_file in os.listdir(path):
1124 metadata_fpath = os.path.join(path, metadata_file)
1125 if metadata_file == 'PKG-INFO':
1126 metadata = metadata_fpath
1127 else:
1128 extra_metadata.append(metadata_fpath)
1129 elif 'egg-info' in path and os.path.isfile(path):
1130 # skip extra metadata files
1131 continue
1132 else:
1133 installed.append(path)
1134
1135 distinfo['egginfo'] = egginfo
1136 distinfo['metadata'] = metadata
1137 distinfo['distinfo_dir'] = distinfo_dir
1138 distinfo['installer_path'] = os.path.join(distinfo_dir, 'INSTALLER')
1139 distinfo['metadata_path'] = os.path.join(distinfo_dir, 'METADATA')
1140 distinfo['record_path'] = os.path.join(distinfo_dir, 'RECORD')
1141 distinfo['requested_path'] = os.path.join(distinfo_dir, 'REQUESTED')
1142 installed.extend([distinfo['installer_path'], distinfo['metadata_path']])
1143 distinfo['installed'] = installed
1144 distinfo['extra_metadata'] = extra_metadata
1145 return distinfo
1146
1147
1148def _write_record_file(record_path, installed_files):
1149 with open(record_path, 'w', encoding='utf-8') as f:
1150 writer = csv.writer(f, delimiter=',', lineterminator=os.linesep,
1151 quotechar='"')
1152
1153 for fpath in installed_files:
1154 if fpath.endswith('.pyc') or fpath.endswith('.pyo'):
1155 # do not put size and md5 hash, as in PEP-376
1156 writer.writerow((fpath, '', ''))
1157 else:
1158 hash = hashlib.md5()
1159 with open(fpath, 'rb') as fp:
1160 hash.update(fp.read())
1161 md5sum = hash.hexdigest()
1162 size = os.path.getsize(fpath)
1163 writer.writerow((fpath, md5sum, size))
1164
1165 # add the RECORD file itself
1166 writer.writerow((record_path, '', ''))
1167 return record_path
1168
1169
1170def egginfo_to_distinfo(record_file, installer=_DEFAULT_INSTALLER,
1171 requested=False, remove_egginfo=False):
1172 """Create files and directories required for PEP 376
1173
1174 :param record_file: path to RECORD file as produced by setup.py --record
1175 :param installer: installer name
1176 :param requested: True if not installed as a dependency
1177 :param remove_egginfo: delete egginfo dir?
1178 """
1179 distinfo = _parse_record_file(record_file)
1180 distinfo_dir = distinfo['distinfo_dir']
1181 if os.path.isdir(distinfo_dir) and not os.path.islink(distinfo_dir):
1182 shutil.rmtree(distinfo_dir)
1183 elif os.path.exists(distinfo_dir):
1184 os.unlink(distinfo_dir)
1185
1186 os.makedirs(distinfo_dir)
1187
1188 # copy setuptools extra metadata files
1189 if distinfo['extra_metadata']:
1190 for path in distinfo['extra_metadata']:
1191 shutil.copy2(path, distinfo_dir)
1192 new_path = path.replace('egg-info', 'dist-info')
1193 distinfo['installed'].append(new_path)
1194
1195 metadata_path = distinfo['metadata_path']
1196 logger.info('creating %s', metadata_path)
1197 shutil.copy2(distinfo['metadata'], metadata_path)
1198
1199 installer_path = distinfo['installer_path']
1200 logger.info('creating %s', installer_path)
1201 with open(installer_path, 'w') as f:
1202 f.write(installer)
1203
1204 if requested:
1205 requested_path = distinfo['requested_path']
1206 logger.info('creating %s', requested_path)
1207 open(requested_path, 'w').close()
1208 distinfo['installed'].append(requested_path)
1209
1210 record_path = distinfo['record_path']
1211 logger.info('creating %s', record_path)
1212 _write_record_file(record_path, distinfo['installed'])
1213
1214 if remove_egginfo:
1215 egginfo = distinfo['egginfo']
1216 logger.info('removing %s', egginfo)
1217 if os.path.isfile(egginfo):
1218 os.remove(egginfo)
1219 else:
1220 shutil.rmtree(egginfo)
1221
1222
1223def _has_egg_info(srcdir):
1224 if os.path.isdir(srcdir):
1225 for item in os.listdir(srcdir):
1226 full_path = os.path.join(srcdir, item)
1227 if item.endswith('.egg-info') and os.path.isdir(full_path):
1228 logger.info("found egg-info directory")
1229 return True
1230 logger.warning("no egg-info directory found")
1231 return False
1232
1233
1234def _has_setuptools_text(setup_py):
1235 return _has_text(setup_py, 'setuptools')
1236
1237
1238def _has_distutils_text(setup_py):
1239 return _has_text(setup_py, 'distutils')
1240
1241
1242def _has_text(setup_py, installer):
1243 installer_pattern = re.compile('import {0}|from {0}'.format(installer))
1244 with open(setup_py, 'r', encoding='utf-8') as setup:
1245 for line in setup:
1246 if re.search(installer_pattern, line):
1247 logger.info("found %s text in setup.py", installer)
1248 return True
1249 logger.warning("no %s text found in setup.py", installer)
1250 return False
1251
1252
1253def _has_required_metadata(setup_cfg):
1254 config = RawConfigParser()
1255 config.read([setup_cfg], encoding='utf8')
1256 return (config.has_section('metadata') and
1257 'name' in config.options('metadata') and
1258 'version' in config.options('metadata'))
1259
1260
1261def _has_pkg_info(srcdir):
1262 pkg_info = os.path.join(srcdir, 'PKG-INFO')
1263 has_pkg_info = os.path.isfile(pkg_info)
1264 if has_pkg_info:
1265 logger.info("PKG-INFO file found")
1266 logger.warning("no PKG-INFO file found")
1267 return has_pkg_info
1268
1269
1270def _has_setup_py(srcdir):
1271 setup_py = os.path.join(srcdir, 'setup.py')
1272 if os.path.isfile(setup_py):
1273 logger.info('setup.py file found')
1274 return True
1275 return False
1276
1277
1278def _has_setup_cfg(srcdir):
1279 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1280 if os.path.isfile(setup_cfg):
1281 logger.info('setup.cfg file found')
1282 return True
1283 logger.warning("no setup.cfg file found")
1284 return False
1285
1286
1287def is_setuptools(path):
1288 """Check if the project is based on setuptools.
1289
1290 :param path: path to source directory containing a setup.py script.
1291
1292 Return True if the project requires setuptools to install, else False.
1293 """
1294 srcdir = os.path.abspath(path)
1295 setup_py = os.path.join(srcdir, 'setup.py')
1296
1297 return _has_setup_py(srcdir) and (_has_egg_info(srcdir) or
1298 _has_setuptools_text(setup_py))
1299
1300
1301def is_distutils(path):
1302 """Check if the project is based on distutils.
1303
1304 :param path: path to source directory containing a setup.py script.
1305
1306 Return True if the project requires distutils to install, else False.
1307 """
1308 srcdir = os.path.abspath(path)
1309 setup_py = os.path.join(srcdir, 'setup.py')
1310
1311 return _has_setup_py(srcdir) and (_has_pkg_info(srcdir) or
1312 _has_distutils_text(setup_py))
1313
1314
1315def is_packaging(path):
1316 """Check if the project is based on packaging
1317
1318 :param path: path to source directory containing a setup.cfg file.
1319
1320 Return True if the project has a valid setup.cfg, else False.
1321 """
1322 srcdir = os.path.abspath(path)
1323 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1324
1325 return _has_setup_cfg(srcdir) and _has_required_metadata(setup_cfg)
1326
1327
1328def get_install_method(path):
1329 """Check if the project is based on packaging, setuptools, or distutils
1330
1331 :param path: path to source directory containing a setup.cfg file,
1332 or setup.py.
1333
1334 Returns a string representing the best install method to use.
1335 """
1336 if is_packaging(path):
1337 return "packaging"
1338 elif is_setuptools(path):
1339 return "setuptools"
1340 elif is_distutils(path):
1341 return "distutils"
1342 else:
1343 raise InstallationException('Cannot detect install method')
1344
1345
1346# XXX to be replaced by shutil.copytree
1347def copy_tree(src, dst, preserve_mode=True, preserve_times=True,
1348 preserve_symlinks=False, update=False, verbose=True,
1349 dry_run=False):
1350 from distutils.file_util import copy_file
1351
1352 if not dry_run and not os.path.isdir(src):
1353 raise PackagingFileError(
1354 "cannot copy tree '%s': not a directory" % src)
1355 try:
1356 names = os.listdir(src)
1357 except os.error as e:
1358 errstr = e[1]
1359 if dry_run:
1360 names = []
1361 else:
1362 raise PackagingFileError(
1363 "error listing files in '%s': %s" % (src, errstr))
1364
1365 if not dry_run:
1366 _mkpath(dst, verbose=verbose)
1367
1368 outputs = []
1369
1370 for n in names:
1371 src_name = os.path.join(src, n)
1372 dst_name = os.path.join(dst, n)
1373
1374 if preserve_symlinks and os.path.islink(src_name):
1375 link_dest = os.readlink(src_name)
1376 if verbose >= 1:
1377 logger.info("linking %s -> %s", dst_name, link_dest)
1378 if not dry_run:
1379 os.symlink(link_dest, dst_name)
1380 outputs.append(dst_name)
1381
1382 elif os.path.isdir(src_name):
1383 outputs.extend(
1384 copy_tree(src_name, dst_name, preserve_mode,
1385 preserve_times, preserve_symlinks, update,
1386 verbose=verbose, dry_run=dry_run))
1387 else:
1388 copy_file(src_name, dst_name, preserve_mode,
1389 preserve_times, update, verbose=verbose,
1390 dry_run=dry_run)
1391 outputs.append(dst_name)
1392
1393 return outputs
1394
1395# cache for by mkpath() -- in addition to cheapening redundant calls,
1396# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
1397_path_created = set()
1398
1399
1400# I don't use os.makedirs because a) it's new to Python 1.5.2, and
1401# b) it blows up if the directory already exists (I want to silently
1402# succeed in that case).
1403def _mkpath(name, mode=0o777, verbose=True, dry_run=False):
1404 # Detect a common bug -- name is None
1405 if not isinstance(name, str):
1406 raise PackagingInternalError(
1407 "mkpath: 'name' must be a string (got %r)" % (name,))
1408
1409 # XXX what's the better way to handle verbosity? print as we create
1410 # each directory in the path (the current behaviour), or only announce
1411 # the creation of the whole path? (quite easy to do the latter since
1412 # we're not using a recursive algorithm)
1413
1414 name = os.path.normpath(name)
1415 created_dirs = []
1416 if os.path.isdir(name) or name == '':
1417 return created_dirs
1418 if os.path.abspath(name) in _path_created:
1419 return created_dirs
1420
1421 head, tail = os.path.split(name)
1422 tails = [tail] # stack of lone dirs to create
1423
1424 while head and tail and not os.path.isdir(head):
1425 head, tail = os.path.split(head)
1426 tails.insert(0, tail) # push next higher dir onto stack
1427
1428 # now 'head' contains the deepest directory that already exists
1429 # (that is, the child of 'head' in 'name' is the highest directory
1430 # that does *not* exist)
1431 for d in tails:
1432 head = os.path.join(head, d)
1433 abs_head = os.path.abspath(head)
1434
1435 if abs_head in _path_created:
1436 continue
1437
1438 if verbose >= 1:
1439 logger.info("creating %s", head)
1440
1441 if not dry_run:
1442 try:
1443 os.mkdir(head, mode)
1444 except OSError as exc:
1445 if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
1446 raise PackagingFileError(
1447 "could not create '%s': %s" % (head, exc.args[-1]))
1448 created_dirs.append(head)
1449
1450 _path_created.add(abs_head)
1451 return created_dirs