blob: 058f13e334e02ff399fbd37b44a14d47cdc41077 [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
Victor Stinner21a9c742011-05-19 15:51:27 +0200353 with script:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200354 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
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200381 cmd = [sys.executable, script_name]
382 if optimize == 1:
383 cmd.insert(1, "-O")
384 elif optimize == 2:
385 cmd.insert(1, "-OO")
386
387 env = copy(os.environ)
388 env['PYTHONPATH'] = os.path.pathsep.join(sys.path)
389 try:
390 spawn(cmd, env=env)
391 finally:
392 execute(os.remove, (script_name,), "removing %s" % script_name,
393 dry_run=dry_run)
394
395 # "Direct" byte-compilation: use the py_compile module to compile
396 # right here, right now. Note that the script generated in indirect
397 # mode simply calls 'byte_compile()' in direct mode, a weird sort of
398 # cross-process recursion. Hey, it works!
399 else:
400 from py_compile import compile
401
402 for file in py_files:
403 if file[-3:] != ".py":
404 # This lets us be lazy and not filter filenames in
405 # the "install_lib" command.
406 continue
407
408 # Terminology from the py_compile module:
409 # cfile - byte-compiled file
410 # dfile - purported source filename (same as 'file' by default)
411 cfile = file + (__debug__ and "c" or "o")
412 dfile = file
413 if prefix:
414 if file[:len(prefix)] != prefix:
415 raise ValueError("invalid prefix: filename %r doesn't "
416 "start with %r" % (file, prefix))
417 dfile = dfile[len(prefix):]
418 if base_dir:
419 dfile = os.path.join(base_dir, dfile)
420
421 cfile_base = os.path.basename(cfile)
422 if direct:
423 if force or newer(file, cfile):
424 logger.info("byte-compiling %s to %s", file, cfile_base)
425 if not dry_run:
426 compile(file, cfile, dfile)
427 else:
428 logger.debug("skipping byte-compilation of %s to %s",
429 file, cfile_base)
430
431
432def rfc822_escape(header):
433 """Return a form of *header* suitable for inclusion in an RFC 822-header.
434
435 This function ensures there are 8 spaces after each newline.
436 """
437 lines = header.split('\n')
438 sep = '\n' + 8 * ' '
439 return sep.join(lines)
440
441_RE_VERSION = re.compile('(\d+\.\d+(\.\d+)*)')
442_MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld '
443 'PROJECT:ld64-((\d+)(\.\d+)*)')
444
445
446def _find_ld_version():
447 """Find the ld version. The version scheme differs under Mac OS X."""
448 if sys.platform == 'darwin':
449 return _find_exe_version('ld -v', _MAC_OS_X_LD_VERSION)
450 else:
451 return _find_exe_version('ld -v')
452
453
454def _find_exe_version(cmd, pattern=_RE_VERSION):
455 """Find the version of an executable by running `cmd` in the shell.
456
457 `pattern` is a compiled regular expression. If not provided, defaults
458 to _RE_VERSION. If the command is not found, or the output does not
459 match the mattern, returns None.
460 """
461 from subprocess import Popen, PIPE
462 executable = cmd.split()[0]
463 if find_executable(executable) is None:
464 return None
465 pipe = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
466 try:
467 stdout, stderr = pipe.stdout.read(), pipe.stderr.read()
468 finally:
469 pipe.stdout.close()
470 pipe.stderr.close()
471 # some commands like ld under MacOS X, will give the
472 # output in the stderr, rather than stdout.
473 if stdout != '':
474 out_string = stdout
475 else:
476 out_string = stderr
477
478 result = pattern.search(out_string)
479 if result is None:
480 return None
481 return result.group(1)
482
483
484def get_compiler_versions():
485 """Return a tuple providing the versions of gcc, ld and dllwrap
486
487 For each command, if a command is not found, None is returned.
488 Otherwise a string with the version is returned.
489 """
490 gcc = _find_exe_version('gcc -dumpversion')
491 ld = _find_ld_version()
492 dllwrap = _find_exe_version('dllwrap --version')
493 return gcc, ld, dllwrap
494
495
496def newer_group(sources, target, missing='error'):
497 """Return true if 'target' is out-of-date with respect to any file
498 listed in 'sources'.
499
500 In other words, if 'target' exists and is newer
501 than every file in 'sources', return false; otherwise return true.
502 'missing' controls what we do when a source file is missing; the
503 default ("error") is to blow up with an OSError from inside 'stat()';
504 if it is "ignore", we silently drop any missing source files; if it is
505 "newer", any missing source files make us assume that 'target' is
506 out-of-date (this is handy in "dry-run" mode: it'll make you pretend to
507 carry out commands that wouldn't work because inputs are missing, but
508 that doesn't matter because you're not actually going to run the
509 commands).
510 """
511 # If the target doesn't even exist, then it's definitely out-of-date.
512 if not os.path.exists(target):
513 return True
514
515 # Otherwise we have to find out the hard way: if *any* source file
516 # is more recent than 'target', then 'target' is out-of-date and
517 # we can immediately return true. If we fall through to the end
518 # of the loop, then 'target' is up-to-date and we return false.
519 target_mtime = os.stat(target).st_mtime
520
521 for source in sources:
522 if not os.path.exists(source):
523 if missing == 'error': # blow up when we stat() the file
524 pass
525 elif missing == 'ignore': # missing source dropped from
526 continue # target's dependency list
527 elif missing == 'newer': # missing source means target is
528 return True # out-of-date
529
530 if os.stat(source).st_mtime > target_mtime:
531 return True
532
533 return False
534
535
536def write_file(filename, contents):
537 """Create *filename* and write *contents* to it.
538
539 *contents* is a sequence of strings without line terminators.
540 """
541 with open(filename, "w") as f:
542 for line in contents:
543 f.write(line + "\n")
544
545
546def _is_package(path):
547 if not os.path.isdir(path):
548 return False
549 return os.path.isfile(os.path.join(path, '__init__.py'))
550
551
552# Code taken from the pip project
553def _is_archive_file(name):
554 archives = ('.zip', '.tar.gz', '.tar.bz2', '.tgz', '.tar')
555 ext = splitext(name)[1].lower()
556 if ext in archives:
557 return True
558 return False
559
560
561def _under(path, root):
562 path = path.split(os.sep)
563 root = root.split(os.sep)
564 if len(root) > len(path):
565 return False
566 for pos, part in enumerate(root):
567 if path[pos] != part:
568 return False
569 return True
570
571
572def _package_name(root_path, path):
573 # Return a dotted package name, given a subpath
574 if not _under(path, root_path):
575 raise ValueError('"%s" is not a subpath of "%s"' % (path, root_path))
576 return path[len(root_path) + 1:].replace(os.sep, '.')
577
578
579def find_packages(paths=(os.curdir,), exclude=()):
580 """Return a list all Python packages found recursively within
581 directories 'paths'
582
583 'paths' should be supplied as a sequence of "cross-platform"
584 (i.e. URL-style) path; it will be converted to the appropriate local
585 path syntax.
586
587 'exclude' is a sequence of package names to exclude; '*' can be used as
588 a wildcard in the names, such that 'foo.*' will exclude all subpackages
589 of 'foo' (but not 'foo' itself).
590 """
591 packages = []
592 discarded = []
593
594 def _discarded(path):
595 for discard in discarded:
596 if _under(path, discard):
597 return True
598 return False
599
600 for path in paths:
601 path = convert_path(path)
602 for root, dirs, files in os.walk(path):
603 for dir_ in dirs:
604 fullpath = os.path.join(root, dir_)
605 if _discarded(fullpath):
606 continue
607 # we work only with Python packages
608 if not _is_package(fullpath):
609 discarded.append(fullpath)
610 continue
611 # see if it's excluded
612 excluded = False
613 package_name = _package_name(path, fullpath)
614 for pattern in exclude:
615 if fnmatchcase(package_name, pattern):
616 excluded = True
617 break
618 if excluded:
619 continue
620
621 # adding it to the list
622 packages.append(package_name)
623 return packages
624
625
626def resolve_name(name):
627 """Resolve a name like ``module.object`` to an object and return it.
628
629 Raise ImportError if the module or name is not found.
630 """
631 parts = name.split('.')
632 cursor = len(parts)
633 module_name = parts[:cursor]
634
635 while cursor > 0:
636 try:
637 ret = __import__('.'.join(module_name))
638 break
639 except ImportError:
640 if cursor == 0:
641 raise
642 cursor -= 1
643 module_name = parts[:cursor]
644 ret = ''
645
646 for part in parts[1:]:
647 try:
648 ret = getattr(ret, part)
649 except AttributeError as exc:
650 raise ImportError(exc)
651
652 return ret
653
654
655def splitext(path):
656 """Like os.path.splitext, but take off .tar too"""
657 base, ext = posixpath.splitext(path)
658 if base.lower().endswith('.tar'):
659 ext = base[-4:] + ext
660 base = base[:-4]
661 return base, ext
662
663
664def unzip_file(filename, location, flatten=True):
665 """Unzip the file *filename* into the *location* directory."""
666 if not os.path.exists(location):
667 os.makedirs(location)
668 with open(filename, 'rb') as zipfp:
669 zip = zipfile.ZipFile(zipfp)
670 leading = has_leading_dir(zip.namelist()) and flatten
671 for name in zip.namelist():
672 data = zip.read(name)
673 fn = name
674 if leading:
675 fn = split_leading_dir(name)[1]
676 fn = os.path.join(location, fn)
677 dir = os.path.dirname(fn)
678 if not os.path.exists(dir):
679 os.makedirs(dir)
680 if fn.endswith('/') or fn.endswith('\\'):
681 # A directory
682 if not os.path.exists(fn):
683 os.makedirs(fn)
684 else:
685 with open(fn, 'wb') as fp:
686 fp.write(data)
687
688
689def untar_file(filename, location):
690 """Untar the file *filename* into the *location* directory."""
691 if not os.path.exists(location):
692 os.makedirs(location)
693 if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):
694 mode = 'r:gz'
695 elif (filename.lower().endswith('.bz2')
696 or filename.lower().endswith('.tbz')):
697 mode = 'r:bz2'
698 elif filename.lower().endswith('.tar'):
699 mode = 'r'
700 else:
701 mode = 'r:*'
702 with tarfile.open(filename, mode) as tar:
703 leading = has_leading_dir(member.name for member in tar.getmembers())
704 for member in tar.getmembers():
705 fn = member.name
706 if leading:
707 fn = split_leading_dir(fn)[1]
708 path = os.path.join(location, fn)
709 if member.isdir():
710 if not os.path.exists(path):
711 os.makedirs(path)
712 else:
713 try:
714 fp = tar.extractfile(member)
715 except (KeyError, AttributeError):
716 # Some corrupt tar files seem to produce this
717 # (specifically bad symlinks)
718 continue
719 try:
720 if not os.path.exists(os.path.dirname(path)):
721 os.makedirs(os.path.dirname(path))
722 with open(path, 'wb') as destfp:
723 shutil.copyfileobj(fp, destfp)
724 finally:
725 fp.close()
726
727
728def has_leading_dir(paths):
729 """Return true if all the paths have the same leading path name.
730
731 In other words, check that everything is in one subdirectory in an
732 archive.
733 """
734 common_prefix = None
735 for path in paths:
736 prefix, rest = split_leading_dir(path)
737 if not prefix:
738 return False
739 elif common_prefix is None:
740 common_prefix = prefix
741 elif prefix != common_prefix:
742 return False
743 return True
744
745
746def split_leading_dir(path):
747 path = str(path)
748 path = path.lstrip('/').lstrip('\\')
749 if '/' in path and (('\\' in path and path.find('/') < path.find('\\'))
750 or '\\' not in path):
751 return path.split('/', 1)
752 elif '\\' in path:
753 return path.split('\\', 1)
754 else:
755 return path, ''
756
757
758def spawn(cmd, search_path=True, verbose=0, dry_run=False, env=None):
759 """Run another program specified as a command list 'cmd' in a new process.
760
761 'cmd' is just the argument list for the new process, ie.
762 cmd[0] is the program to run and cmd[1:] are the rest of its arguments.
763 There is no way to run a program with a name different from that of its
764 executable.
765
766 If 'search_path' is true (the default), the system's executable
767 search path will be used to find the program; otherwise, cmd[0]
768 must be the exact path to the executable. If 'dry_run' is true,
769 the command will not actually be run.
770
771 If 'env' is given, it's a environment dictionary used for the execution
772 environment.
773
774 Raise PackagingExecError if running the program fails in any way; just
775 return on success.
776 """
777 logger.info(' '.join(cmd))
778 if dry_run:
779 return
780 exit_status = subprocess.call(cmd, env=env)
781 if exit_status != 0:
782 msg = "command '%s' failed with exit status %d"
783 raise PackagingExecError(msg % (cmd, exit_status))
784
785
786def find_executable(executable, path=None):
787 """Try to find 'executable' in the directories listed in 'path'.
788
789 *path* is a string listing directories separated by 'os.pathsep' and
790 defaults to os.environ['PATH']. Returns the complete filename or None
791 if not found.
792 """
793 if path is None:
794 path = os.environ['PATH']
795 paths = path.split(os.pathsep)
796 base, ext = os.path.splitext(executable)
797
798 if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
799 executable = executable + '.exe'
800
801 if not os.path.isfile(executable):
802 for p in paths:
803 f = os.path.join(p, executable)
804 if os.path.isfile(f):
805 # the file exists, we have a shot at spawn working
806 return f
807 return None
808 else:
809 return executable
810
811
812DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
813DEFAULT_REALM = 'pypi'
814DEFAULT_PYPIRC = """\
815[distutils]
816index-servers =
817 pypi
818
819[pypi]
820username:%s
821password:%s
822"""
823
824
825def get_pypirc_path():
826 """Return path to pypirc config file."""
827 return os.path.join(os.path.expanduser('~'), '.pypirc')
828
829
830def generate_pypirc(username, password):
831 """Create a default .pypirc file."""
832 rc = get_pypirc_path()
833 with open(rc, 'w') as f:
834 f.write(DEFAULT_PYPIRC % (username, password))
835 try:
836 os.chmod(rc, 0o600)
837 except OSError:
838 # should do something better here
839 pass
840
841
842def read_pypirc(repository=DEFAULT_REPOSITORY, realm=DEFAULT_REALM):
843 """Read the .pypirc file."""
844 rc = get_pypirc_path()
845 if os.path.exists(rc):
846 config = RawConfigParser()
847 config.read(rc)
848 sections = config.sections()
849 if 'distutils' in sections:
850 # let's get the list of servers
851 index_servers = config.get('distutils', 'index-servers')
852 _servers = [server.strip() for server in
853 index_servers.split('\n')
854 if server.strip() != '']
855 if _servers == []:
856 # nothing set, let's try to get the default pypi
857 if 'pypi' in sections:
858 _servers = ['pypi']
859 else:
860 # the file is not properly defined, returning
861 # an empty dict
862 return {}
863 for server in _servers:
864 current = {'server': server}
865 current['username'] = config.get(server, 'username')
866
867 # optional params
868 for key, default in (('repository', DEFAULT_REPOSITORY),
869 ('realm', DEFAULT_REALM),
870 ('password', None)):
871 if config.has_option(server, key):
872 current[key] = config.get(server, key)
873 else:
874 current[key] = default
875 if (current['server'] == repository or
876 current['repository'] == repository):
877 return current
878 elif 'server-login' in sections:
879 # old format
880 server = 'server-login'
881 if config.has_option(server, 'repository'):
882 repository = config.get(server, 'repository')
883 else:
884 repository = DEFAULT_REPOSITORY
885
886 return {'username': config.get(server, 'username'),
887 'password': config.get(server, 'password'),
888 'repository': repository,
889 'server': server,
890 'realm': DEFAULT_REALM}
891
892 return {}
893
894
895# utility functions for 2to3 support
896
897def run_2to3(files, doctests_only=False, fixer_names=None,
898 options=None, explicit=None):
899 """ Wrapper function around the refactor() class which
900 performs the conversions on a list of python files.
901 Invoke 2to3 on a list of Python files. The files should all come
902 from the build area, as the modification is done in-place."""
903
904 #if not files:
905 # return
906
907 # Make this class local, to delay import of 2to3
908 from lib2to3.refactor import get_fixers_from_package, RefactoringTool
909 fixers = []
910 fixers = get_fixers_from_package('lib2to3.fixes')
911
912 if fixer_names:
913 for fixername in fixer_names:
914 fixers.extend(fixer for fixer in
915 get_fixers_from_package(fixername))
916 r = RefactoringTool(fixers, options=options)
917 r.refactor(files, write=True, doctests_only=doctests_only)
918
919
920class Mixin2to3:
921 """ Wrapper class for commands that run 2to3.
922 To configure 2to3, setup scripts may either change
923 the class variables, or inherit from this class
924 to override how 2to3 is invoked.
925 """
926 # provide list of fixers to run.
927 # defaults to all from lib2to3.fixers
928 fixer_names = None
929
930 # options dictionary
931 options = None
932
933 # list of fixers to invoke even though they are marked as explicit
934 explicit = None
935
936 def run_2to3(self, files, doctests_only=False):
937 """ Issues a call to util.run_2to3. """
938 return run_2to3(files, doctests_only, self.fixer_names,
939 self.options, self.explicit)
940
941RICH_GLOB = re.compile(r'\{([^}]*)\}')
942_CHECK_RECURSIVE_GLOB = re.compile(r'[^/,{]\*\*|\*\*[^/,}]')
943_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
944
945
946def iglob(path_glob):
947 """Extended globbing function that supports ** and {opt1,opt2,opt3}."""
948 if _CHECK_RECURSIVE_GLOB.search(path_glob):
949 msg = """invalid glob %r: recursive glob "**" must be used alone"""
950 raise ValueError(msg % path_glob)
951 if _CHECK_MISMATCH_SET.search(path_glob):
952 msg = """invalid glob %r: mismatching set marker '{' or '}'"""
953 raise ValueError(msg % path_glob)
954 return _iglob(path_glob)
955
956
957def _iglob(path_glob):
958 rich_path_glob = RICH_GLOB.split(path_glob, 1)
959 if len(rich_path_glob) > 1:
960 assert len(rich_path_glob) == 3, rich_path_glob
961 prefix, set, suffix = rich_path_glob
962 for item in set.split(','):
963 for path in _iglob(''.join((prefix, item, suffix))):
964 yield path
965 else:
966 if '**' not in path_glob:
967 for item in std_iglob(path_glob):
968 yield item
969 else:
970 prefix, radical = path_glob.split('**', 1)
971 if prefix == '':
972 prefix = '.'
973 if radical == '':
974 radical = '*'
975 else:
976 radical = radical.lstrip('/')
977 for path, dir, files in os.walk(prefix):
978 path = os.path.normpath(path)
979 for file in _iglob(os.path.join(path, radical)):
980 yield file
981
982
983def cfg_to_args(path='setup.cfg'):
984 """Compatibility helper to use setup.cfg in setup.py.
985
986 This functions uses an existing setup.cfg to generate a dictionnary of
987 keywords that can be used by distutils.core.setup(**kwargs). It is used
988 by generate_setup_py.
989
990 *file* is the path to the setup.cfg file. If it doesn't exist,
991 PackagingFileError is raised.
992 """
993 # We need to declare the following constants here so that it's easier to
994 # generate the setup.py afterwards, using inspect.getsource.
995
996 # XXX ** == needs testing
997 D1_D2_SETUP_ARGS = {"name": ("metadata",),
998 "version": ("metadata",),
999 "author": ("metadata",),
1000 "author_email": ("metadata",),
1001 "maintainer": ("metadata",),
1002 "maintainer_email": ("metadata",),
1003 "url": ("metadata", "home_page"),
1004 "description": ("metadata", "summary"),
1005 "long_description": ("metadata", "description"),
1006 "download-url": ("metadata",),
1007 "classifiers": ("metadata", "classifier"),
1008 "platforms": ("metadata", "platform"), # **
1009 "license": ("metadata",),
1010 "requires": ("metadata", "requires_dist"),
1011 "provides": ("metadata", "provides_dist"), # **
1012 "obsoletes": ("metadata", "obsoletes_dist"), # **
1013 "packages": ("files",),
1014 "scripts": ("files",),
1015 "py_modules": ("files", "modules"), # **
1016 }
1017
1018 MULTI_FIELDS = ("classifiers",
1019 "requires",
1020 "platforms",
1021 "packages",
1022 "scripts")
1023
1024 def has_get_option(config, section, option):
1025 if config.has_option(section, option):
1026 return config.get(section, option)
1027 elif config.has_option(section, option.replace('_', '-')):
1028 return config.get(section, option.replace('_', '-'))
1029 else:
1030 return False
1031
1032 # The real code starts here
1033 config = RawConfigParser()
1034 if not os.path.exists(file):
1035 raise PackagingFileError("file '%s' does not exist" %
1036 os.path.abspath(file))
1037 config.read(path)
1038
1039 kwargs = {}
1040 for arg in D1_D2_SETUP_ARGS:
1041 if len(D1_D2_SETUP_ARGS[arg]) == 2:
1042 # The distutils field name is different than packaging's
1043 section, option = D1_D2_SETUP_ARGS[arg]
1044
1045 else:
1046 # The distutils field name is the same thant packaging's
1047 section = D1_D2_SETUP_ARGS[arg][0]
1048 option = arg
1049
1050 in_cfg_value = has_get_option(config, section, option)
1051 if not in_cfg_value:
1052 # There is no such option in the setup.cfg
1053 if arg == "long_description":
1054 filename = has_get_option(config, section, "description_file")
1055 if filename:
1056 with open(filename) as fp:
1057 in_cfg_value = fp.read()
1058 else:
1059 continue
1060
1061 if arg in MULTI_FIELDS:
1062 # support multiline options
1063 in_cfg_value = in_cfg_value.strip().split('\n')
1064
1065 kwargs[arg] = in_cfg_value
1066
1067 return kwargs
1068
1069
1070_SETUP_TMPL = """\
1071# This script was automatically generated by packaging
1072import os
1073from distutils.core import setup
1074from ConfigParser import RawConfigParser
1075
1076%(func)s
1077
1078setup(**cfg_to_args())
1079"""
1080
1081
1082def generate_setup_py():
1083 """Generate a distutils compatible setup.py using an existing setup.cfg.
1084
1085 Raises a PackagingFileError when a setup.py already exists.
1086 """
1087 if os.path.exists("setup.py"):
1088 raise PackagingFileError("a setup.py file alreadyexists")
1089
1090 with open("setup.py", "w") as fp:
1091 fp.write(_SETUP_TMPL % {'func': getsource(cfg_to_args)})
1092
1093
1094# Taken from the pip project
1095# https://github.com/pypa/pip/blob/master/pip/util.py
1096def ask(message, options):
1097 """Prompt the user with *message*; *options* contains allowed responses."""
1098 while True:
1099 response = input(message)
1100 response = response.strip().lower()
1101 if response not in options:
1102 print('invalid response: %r' % response)
1103 print('choose one of', ', '.join(repr(o) for o in options))
1104 else:
1105 return response
1106
1107
1108def _parse_record_file(record_file):
1109 distinfo, extra_metadata, installed = ({}, [], [])
1110 with open(record_file, 'r') as rfile:
1111 for path in rfile:
1112 path = path.strip()
1113 if path.endswith('egg-info') and os.path.isfile(path):
1114 distinfo_dir = path.replace('egg-info', 'dist-info')
1115 metadata = path
1116 egginfo = path
1117 elif path.endswith('egg-info') and os.path.isdir(path):
1118 distinfo_dir = path.replace('egg-info', 'dist-info')
1119 egginfo = path
1120 for metadata_file in os.listdir(path):
1121 metadata_fpath = os.path.join(path, metadata_file)
1122 if metadata_file == 'PKG-INFO':
1123 metadata = metadata_fpath
1124 else:
1125 extra_metadata.append(metadata_fpath)
1126 elif 'egg-info' in path and os.path.isfile(path):
1127 # skip extra metadata files
1128 continue
1129 else:
1130 installed.append(path)
1131
1132 distinfo['egginfo'] = egginfo
1133 distinfo['metadata'] = metadata
1134 distinfo['distinfo_dir'] = distinfo_dir
1135 distinfo['installer_path'] = os.path.join(distinfo_dir, 'INSTALLER')
1136 distinfo['metadata_path'] = os.path.join(distinfo_dir, 'METADATA')
1137 distinfo['record_path'] = os.path.join(distinfo_dir, 'RECORD')
1138 distinfo['requested_path'] = os.path.join(distinfo_dir, 'REQUESTED')
1139 installed.extend([distinfo['installer_path'], distinfo['metadata_path']])
1140 distinfo['installed'] = installed
1141 distinfo['extra_metadata'] = extra_metadata
1142 return distinfo
1143
1144
1145def _write_record_file(record_path, installed_files):
1146 with open(record_path, 'w', encoding='utf-8') as f:
1147 writer = csv.writer(f, delimiter=',', lineterminator=os.linesep,
1148 quotechar='"')
1149
1150 for fpath in installed_files:
1151 if fpath.endswith('.pyc') or fpath.endswith('.pyo'):
1152 # do not put size and md5 hash, as in PEP-376
1153 writer.writerow((fpath, '', ''))
1154 else:
1155 hash = hashlib.md5()
1156 with open(fpath, 'rb') as fp:
1157 hash.update(fp.read())
1158 md5sum = hash.hexdigest()
1159 size = os.path.getsize(fpath)
1160 writer.writerow((fpath, md5sum, size))
1161
1162 # add the RECORD file itself
1163 writer.writerow((record_path, '', ''))
1164 return record_path
1165
1166
1167def egginfo_to_distinfo(record_file, installer=_DEFAULT_INSTALLER,
1168 requested=False, remove_egginfo=False):
1169 """Create files and directories required for PEP 376
1170
1171 :param record_file: path to RECORD file as produced by setup.py --record
1172 :param installer: installer name
1173 :param requested: True if not installed as a dependency
1174 :param remove_egginfo: delete egginfo dir?
1175 """
1176 distinfo = _parse_record_file(record_file)
1177 distinfo_dir = distinfo['distinfo_dir']
1178 if os.path.isdir(distinfo_dir) and not os.path.islink(distinfo_dir):
1179 shutil.rmtree(distinfo_dir)
1180 elif os.path.exists(distinfo_dir):
1181 os.unlink(distinfo_dir)
1182
1183 os.makedirs(distinfo_dir)
1184
1185 # copy setuptools extra metadata files
1186 if distinfo['extra_metadata']:
1187 for path in distinfo['extra_metadata']:
1188 shutil.copy2(path, distinfo_dir)
1189 new_path = path.replace('egg-info', 'dist-info')
1190 distinfo['installed'].append(new_path)
1191
1192 metadata_path = distinfo['metadata_path']
1193 logger.info('creating %s', metadata_path)
1194 shutil.copy2(distinfo['metadata'], metadata_path)
1195
1196 installer_path = distinfo['installer_path']
1197 logger.info('creating %s', installer_path)
1198 with open(installer_path, 'w') as f:
1199 f.write(installer)
1200
1201 if requested:
1202 requested_path = distinfo['requested_path']
1203 logger.info('creating %s', requested_path)
Victor Stinner4c9706b2011-05-19 15:52:59 +02001204 open(requested_path, 'wb').close()
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001205 distinfo['installed'].append(requested_path)
1206
1207 record_path = distinfo['record_path']
1208 logger.info('creating %s', record_path)
1209 _write_record_file(record_path, distinfo['installed'])
1210
1211 if remove_egginfo:
1212 egginfo = distinfo['egginfo']
1213 logger.info('removing %s', egginfo)
1214 if os.path.isfile(egginfo):
1215 os.remove(egginfo)
1216 else:
1217 shutil.rmtree(egginfo)
1218
1219
1220def _has_egg_info(srcdir):
1221 if os.path.isdir(srcdir):
1222 for item in os.listdir(srcdir):
1223 full_path = os.path.join(srcdir, item)
1224 if item.endswith('.egg-info') and os.path.isdir(full_path):
1225 logger.info("found egg-info directory")
1226 return True
1227 logger.warning("no egg-info directory found")
1228 return False
1229
1230
1231def _has_setuptools_text(setup_py):
1232 return _has_text(setup_py, 'setuptools')
1233
1234
1235def _has_distutils_text(setup_py):
1236 return _has_text(setup_py, 'distutils')
1237
1238
1239def _has_text(setup_py, installer):
1240 installer_pattern = re.compile('import {0}|from {0}'.format(installer))
1241 with open(setup_py, 'r', encoding='utf-8') as setup:
1242 for line in setup:
1243 if re.search(installer_pattern, line):
1244 logger.info("found %s text in setup.py", installer)
1245 return True
1246 logger.warning("no %s text found in setup.py", installer)
1247 return False
1248
1249
1250def _has_required_metadata(setup_cfg):
1251 config = RawConfigParser()
1252 config.read([setup_cfg], encoding='utf8')
1253 return (config.has_section('metadata') and
1254 'name' in config.options('metadata') and
1255 'version' in config.options('metadata'))
1256
1257
1258def _has_pkg_info(srcdir):
1259 pkg_info = os.path.join(srcdir, 'PKG-INFO')
1260 has_pkg_info = os.path.isfile(pkg_info)
1261 if has_pkg_info:
1262 logger.info("PKG-INFO file found")
1263 logger.warning("no PKG-INFO file found")
1264 return has_pkg_info
1265
1266
1267def _has_setup_py(srcdir):
1268 setup_py = os.path.join(srcdir, 'setup.py')
1269 if os.path.isfile(setup_py):
1270 logger.info('setup.py file found')
1271 return True
1272 return False
1273
1274
1275def _has_setup_cfg(srcdir):
1276 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1277 if os.path.isfile(setup_cfg):
1278 logger.info('setup.cfg file found')
1279 return True
1280 logger.warning("no setup.cfg file found")
1281 return False
1282
1283
1284def is_setuptools(path):
1285 """Check if the project is based on setuptools.
1286
1287 :param path: path to source directory containing a setup.py script.
1288
1289 Return True if the project requires setuptools to install, else False.
1290 """
1291 srcdir = os.path.abspath(path)
1292 setup_py = os.path.join(srcdir, 'setup.py')
1293
1294 return _has_setup_py(srcdir) and (_has_egg_info(srcdir) or
1295 _has_setuptools_text(setup_py))
1296
1297
1298def is_distutils(path):
1299 """Check if the project is based on distutils.
1300
1301 :param path: path to source directory containing a setup.py script.
1302
1303 Return True if the project requires distutils to install, else False.
1304 """
1305 srcdir = os.path.abspath(path)
1306 setup_py = os.path.join(srcdir, 'setup.py')
1307
1308 return _has_setup_py(srcdir) and (_has_pkg_info(srcdir) or
1309 _has_distutils_text(setup_py))
1310
1311
1312def is_packaging(path):
1313 """Check if the project is based on packaging
1314
1315 :param path: path to source directory containing a setup.cfg file.
1316
1317 Return True if the project has a valid setup.cfg, else False.
1318 """
1319 srcdir = os.path.abspath(path)
1320 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1321
1322 return _has_setup_cfg(srcdir) and _has_required_metadata(setup_cfg)
1323
1324
1325def get_install_method(path):
1326 """Check if the project is based on packaging, setuptools, or distutils
1327
1328 :param path: path to source directory containing a setup.cfg file,
1329 or setup.py.
1330
1331 Returns a string representing the best install method to use.
1332 """
1333 if is_packaging(path):
1334 return "packaging"
1335 elif is_setuptools(path):
1336 return "setuptools"
1337 elif is_distutils(path):
1338 return "distutils"
1339 else:
1340 raise InstallationException('Cannot detect install method')
1341
1342
1343# XXX to be replaced by shutil.copytree
1344def copy_tree(src, dst, preserve_mode=True, preserve_times=True,
1345 preserve_symlinks=False, update=False, verbose=True,
1346 dry_run=False):
1347 from distutils.file_util import copy_file
1348
1349 if not dry_run and not os.path.isdir(src):
1350 raise PackagingFileError(
1351 "cannot copy tree '%s': not a directory" % src)
1352 try:
1353 names = os.listdir(src)
1354 except os.error as e:
1355 errstr = e[1]
1356 if dry_run:
1357 names = []
1358 else:
1359 raise PackagingFileError(
1360 "error listing files in '%s': %s" % (src, errstr))
1361
1362 if not dry_run:
1363 _mkpath(dst, verbose=verbose)
1364
1365 outputs = []
1366
1367 for n in names:
1368 src_name = os.path.join(src, n)
1369 dst_name = os.path.join(dst, n)
1370
1371 if preserve_symlinks and os.path.islink(src_name):
1372 link_dest = os.readlink(src_name)
1373 if verbose >= 1:
1374 logger.info("linking %s -> %s", dst_name, link_dest)
1375 if not dry_run:
1376 os.symlink(link_dest, dst_name)
1377 outputs.append(dst_name)
1378
1379 elif os.path.isdir(src_name):
1380 outputs.extend(
1381 copy_tree(src_name, dst_name, preserve_mode,
1382 preserve_times, preserve_symlinks, update,
1383 verbose=verbose, dry_run=dry_run))
1384 else:
1385 copy_file(src_name, dst_name, preserve_mode,
1386 preserve_times, update, verbose=verbose,
1387 dry_run=dry_run)
1388 outputs.append(dst_name)
1389
1390 return outputs
1391
1392# cache for by mkpath() -- in addition to cheapening redundant calls,
1393# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
1394_path_created = set()
1395
1396
1397# I don't use os.makedirs because a) it's new to Python 1.5.2, and
1398# b) it blows up if the directory already exists (I want to silently
1399# succeed in that case).
1400def _mkpath(name, mode=0o777, verbose=True, dry_run=False):
1401 # Detect a common bug -- name is None
1402 if not isinstance(name, str):
1403 raise PackagingInternalError(
1404 "mkpath: 'name' must be a string (got %r)" % (name,))
1405
1406 # XXX what's the better way to handle verbosity? print as we create
1407 # each directory in the path (the current behaviour), or only announce
1408 # the creation of the whole path? (quite easy to do the latter since
1409 # we're not using a recursive algorithm)
1410
1411 name = os.path.normpath(name)
1412 created_dirs = []
1413 if os.path.isdir(name) or name == '':
1414 return created_dirs
1415 if os.path.abspath(name) in _path_created:
1416 return created_dirs
1417
1418 head, tail = os.path.split(name)
1419 tails = [tail] # stack of lone dirs to create
1420
1421 while head and tail and not os.path.isdir(head):
1422 head, tail = os.path.split(head)
1423 tails.insert(0, tail) # push next higher dir onto stack
1424
1425 # now 'head' contains the deepest directory that already exists
1426 # (that is, the child of 'head' in 'name' is the highest directory
1427 # that does *not* exist)
1428 for d in tails:
1429 head = os.path.join(head, d)
1430 abs_head = os.path.abspath(head)
1431
1432 if abs_head in _path_created:
1433 continue
1434
1435 if verbose >= 1:
1436 logger.info("creating %s", head)
1437
1438 if not dry_run:
1439 try:
1440 os.mkdir(head, mode)
1441 except OSError as exc:
1442 if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
1443 raise PackagingFileError(
1444 "could not create '%s': %s" % (head, exc.args[-1]))
1445 created_dirs.append(head)
1446
1447 _path_created.add(abs_head)
1448 return created_dirs