blob: dddfb3f32e7e8a3308a2e390f78815f9398c9794 [file] [log] [blame]
Éric Araujo35a4d012011-06-04 22:24:59 +02001"""Miscellaneous utility functions."""
2
Tarek Ziade1231a4e2011-05-19 13:07:25 +02003import os
Tarek Ziade1231a4e2011-05-19 13:07:25 +02004import re
Éric Araujo35a4d012011-06-04 22:24:59 +02005import csv
6import sys
7import errno
Tarek Ziade1231a4e2011-05-19 13:07:25 +02008import shutil
9import string
Éric Araujo35a4d012011-06-04 22:24:59 +020010import hashlib
Tarek Ziade1231a4e2011-05-19 13:07:25 +020011import tarfile
12import zipfile
13import posixpath
Tarek Ziade1231a4e2011-05-19 13:07:25 +020014import subprocess
Éric Araujo35a4d012011-06-04 22:24:59 +020015import sysconfig
Tarek Ziade1231a4e2011-05-19 13:07:25 +020016from glob import iglob as std_iglob
17from fnmatch import fnmatchcase
18from inspect import getsource
19from configparser import RawConfigParser
20
21from packaging import logger
22from packaging.errors import (PackagingPlatformError, PackagingFileError,
23 PackagingByteCompileError, PackagingExecError,
24 InstallationException, PackagingInternalError)
25
26_PLATFORM = None
27_DEFAULT_INSTALLER = 'packaging'
28
29
30def newer(source, target):
31 """Tell if the target is newer than the source.
32
33 Returns true if 'source' exists and is more recently modified than
34 'target', or if 'source' exists and 'target' doesn't.
35
36 Returns false if both exist and 'target' is the same age or younger
37 than 'source'. Raise PackagingFileError if 'source' does not exist.
38
39 Note that this test is not very accurate: files created in the same second
40 will have the same "age".
41 """
42 if not os.path.exists(source):
43 raise PackagingFileError("file '%s' does not exist" %
44 os.path.abspath(source))
45 if not os.path.exists(target):
46 return True
47
48 return os.stat(source).st_mtime > os.stat(target).st_mtime
49
50
51def get_platform():
52 """Return a string that identifies the current platform.
53
54 By default, will return the value returned by sysconfig.get_platform(),
55 but it can be changed by calling set_platform().
56 """
57 global _PLATFORM
58 if _PLATFORM is None:
59 _PLATFORM = sysconfig.get_platform()
60 return _PLATFORM
61
62
63def set_platform(identifier):
64 """Set the platform string identifier returned by get_platform().
65
66 Note that this change doesn't impact the value returned by
67 sysconfig.get_platform(); it is local to packaging.
68 """
69 global _PLATFORM
70 _PLATFORM = identifier
71
72
73def convert_path(pathname):
74 """Return 'pathname' as a name that will work on the native filesystem.
75
76 The path is split on '/' and put back together again using the current
77 directory separator. Needed because filenames in the setup script are
78 always supplied in Unix style, and have to be converted to the local
79 convention before we can actually use them in the filesystem. Raises
80 ValueError on non-Unix-ish systems if 'pathname' either starts or
81 ends with a slash.
82 """
83 if os.sep == '/':
84 return pathname
85 if not pathname:
86 return pathname
87 if pathname[0] == '/':
88 raise ValueError("path '%s' cannot be absolute" % pathname)
89 if pathname[-1] == '/':
90 raise ValueError("path '%s' cannot end with '/'" % pathname)
91
92 paths = pathname.split('/')
93 while os.curdir in paths:
94 paths.remove(os.curdir)
95 if not paths:
96 return os.curdir
97 return os.path.join(*paths)
98
99
100def change_root(new_root, pathname):
101 """Return 'pathname' with 'new_root' prepended.
102
103 If 'pathname' is relative, this is equivalent to
104 os.path.join(new_root,pathname). Otherwise, it requires making 'pathname'
105 relative and then joining the two, which is tricky on DOS/Windows.
106 """
107 if os.name == 'posix':
108 if not os.path.isabs(pathname):
109 return os.path.join(new_root, pathname)
110 else:
111 return os.path.join(new_root, pathname[1:])
112
113 elif os.name == 'nt':
114 drive, path = os.path.splitdrive(pathname)
115 if path[0] == '\\':
116 path = path[1:]
117 return os.path.join(new_root, path)
118
119 elif os.name == 'os2':
120 drive, path = os.path.splitdrive(pathname)
121 if path[0] == os.sep:
122 path = path[1:]
123 return os.path.join(new_root, path)
124
125 else:
126 raise PackagingPlatformError("nothing known about "
127 "platform '%s'" % os.name)
128
129_environ_checked = False
130
131
132def check_environ():
133 """Ensure that 'os.environ' has all the environment variables needed.
134
135 We guarantee that users can use in config files, command-line options,
136 etc. Currently this includes:
137 HOME - user's home directory (Unix only)
138 PLAT - description of the current platform, including hardware
139 and OS (see 'get_platform()')
140 """
141 global _environ_checked
142 if _environ_checked:
143 return
144
145 if os.name == 'posix' and 'HOME' not in os.environ:
146 import pwd
147 os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]
148
149 if 'PLAT' not in os.environ:
150 os.environ['PLAT'] = sysconfig.get_platform()
151
152 _environ_checked = True
153
154
155def subst_vars(s, local_vars):
156 """Perform shell/Perl-style variable substitution on 'string'.
157
158 Every occurrence of '$' followed by a name is considered a variable, and
159 variable is substituted by the value found in the 'local_vars'
160 dictionary, or in 'os.environ' if it's not in 'local_vars'.
161 'os.environ' is first checked/augmented to guarantee that it contains
162 certain values: see 'check_environ()'. Raise ValueError for any
163 variables not found in either 'local_vars' or 'os.environ'.
164 """
165 check_environ()
166
167 def _subst(match, local_vars=local_vars):
168 var_name = match.group(1)
169 if var_name in local_vars:
170 return str(local_vars[var_name])
171 else:
172 return os.environ[var_name]
173
174 try:
175 return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s)
176 except KeyError as var:
177 raise ValueError("invalid variable '$%s'" % var)
178
179
180# Needed by 'split_quoted()'
181_wordchars_re = _squote_re = _dquote_re = None
182
183
184def _init_regex():
185 global _wordchars_re, _squote_re, _dquote_re
186 _wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)
187 _squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")
188 _dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
189
190
191def split_quoted(s):
192 """Split a string up according to Unix shell-like rules for quotes and
193 backslashes.
194
195 In short: words are delimited by spaces, as long as those
196 spaces are not escaped by a backslash, or inside a quoted string.
197 Single and double quotes are equivalent, and the quote characters can
198 be backslash-escaped. The backslash is stripped from any two-character
199 escape sequence, leaving only the escaped character. The quote
200 characters are stripped from any quoted string. Returns a list of
201 words.
202 """
203 # This is a nice algorithm for splitting up a single string, since it
204 # doesn't require character-by-character examination. It was a little
205 # bit of a brain-bender to get it working right, though...
206 if _wordchars_re is None:
207 _init_regex()
208
209 s = s.strip()
210 words = []
211 pos = 0
212
213 while s:
214 m = _wordchars_re.match(s, pos)
215 end = m.end()
216 if end == len(s):
217 words.append(s[:end])
218 break
219
220 if s[end] in string.whitespace: # unescaped, unquoted whitespace: now
221 words.append(s[:end]) # we definitely have a word delimiter
222 s = s[end:].lstrip()
223 pos = 0
224
225 elif s[end] == '\\': # preserve whatever is being escaped;
226 # will become part of the current word
227 s = s[:end] + s[end + 1:]
228 pos = end + 1
229
230 else:
231 if s[end] == "'": # slurp singly-quoted string
232 m = _squote_re.match(s, end)
233 elif s[end] == '"': # slurp doubly-quoted string
234 m = _dquote_re.match(s, end)
235 else:
236 raise RuntimeError("this can't happen "
237 "(bad char '%c')" % s[end])
238
239 if m is None:
240 raise ValueError("bad string (mismatched %s quotes?)" % s[end])
241
242 beg, end = m.span()
243 s = s[:beg] + s[beg + 1:end - 1] + s[end:]
244 pos = m.end() - 2
245
246 if pos >= len(s):
247 words.append(s)
248 break
249
250 return words
251
252
Éric Araujo1c1d9a52011-06-10 23:26:31 +0200253def split_multiline(value):
254 """Split a multiline string into a list, excluding blank lines."""
255
256 return [element for element in
257 (line.strip() for line in value.split('\n'))
258 if element]
259
260
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200261def execute(func, args, msg=None, verbose=0, dry_run=False):
262 """Perform some action that affects the outside world.
263
264 Some actions (e.g. writing to the filesystem) are special because
265 they are disabled by the 'dry_run' flag. This method takes care of all
266 that bureaucracy for you; all you have to do is supply the
267 function to call and an argument tuple for it (to embody the
268 "external action" being performed), and an optional message to
269 print.
270 """
271 if msg is None:
272 msg = "%s%r" % (func.__name__, args)
273 if msg[-2:] == ',)': # correct for singleton tuple
274 msg = msg[0:-2] + ')'
275
276 logger.info(msg)
277 if not dry_run:
278 func(*args)
279
280
281def strtobool(val):
Éric Araujod5d831b2011-06-06 01:13:48 +0200282 """Convert a string representation of truth to a boolean.
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200283
284 True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
285 are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
286 'val' is anything else.
287 """
288 val = val.lower()
289 if val in ('y', 'yes', 't', 'true', 'on', '1'):
290 return True
291 elif val in ('n', 'no', 'f', 'false', 'off', '0'):
292 return False
293 else:
294 raise ValueError("invalid truth value %r" % (val,))
295
296
297def byte_compile(py_files, optimize=0, force=False, prefix=None,
298 base_dir=None, verbose=0, dry_run=False, direct=None):
299 """Byte-compile a collection of Python source files to either .pyc
300 or .pyo files in the same directory.
301
302 'py_files' is a list of files to compile; any files that don't end in
303 ".py" are silently skipped. 'optimize' must be one of the following:
304 0 - don't optimize (generate .pyc)
305 1 - normal optimization (like "python -O")
306 2 - extra optimization (like "python -OO")
307 If 'force' is true, all files are recompiled regardless of
308 timestamps.
309
310 The source filename encoded in each bytecode file defaults to the
311 filenames listed in 'py_files'; you can modify these with 'prefix' and
312 'basedir'. 'prefix' is a string that will be stripped off of each
313 source filename, and 'base_dir' is a directory name that will be
314 prepended (after 'prefix' is stripped). You can supply either or both
315 (or neither) of 'prefix' and 'base_dir', as you wish.
316
317 If 'dry_run' is true, doesn't actually do anything that would
318 affect the filesystem.
319
320 Byte-compilation is either done directly in this interpreter process
321 with the standard py_compile module, or indirectly by writing a
322 temporary script and executing it. Normally, you should let
323 'byte_compile()' figure out to use direct compilation or not (see
324 the source for details). The 'direct' flag is used by the script
325 generated in indirect mode; unless you know what you're doing, leave
326 it set to None.
327 """
328 # nothing is done if sys.dont_write_bytecode is True
329 # FIXME this should not raise an error
330 if hasattr(sys, 'dont_write_bytecode') and sys.dont_write_bytecode:
331 raise PackagingByteCompileError('byte-compiling is disabled.')
332
333 # First, if the caller didn't force us into direct or indirect mode,
334 # figure out which mode we should be in. We take a conservative
335 # approach: choose direct mode *only* if the current interpreter is
336 # in debug mode and optimize is 0. If we're not in debug mode (-O
337 # or -OO), we don't know which level of optimization this
338 # interpreter is running with, so we can't do direct
339 # byte-compilation and be certain that it's the right thing. Thus,
340 # always compile indirectly if the current interpreter is in either
341 # optimize mode, or if either optimization level was requested by
342 # the caller.
343 if direct is None:
344 direct = (__debug__ and optimize == 0)
345
346 # "Indirect" byte-compilation: write a temporary script and then
347 # run it with the appropriate flags.
348 if not direct:
349 from tempfile import mkstemp
350 # XXX script_fd may leak, use something better than mkstemp
351 script_fd, script_name = mkstemp(".py")
352 logger.info("writing byte-compilation script '%s'", script_name)
353 if not dry_run:
354 if script_fd is not None:
Victor Stinner9cf6d132011-05-19 21:42:47 +0200355 script = os.fdopen(script_fd, "w", encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200356 else:
Victor Stinner9cf6d132011-05-19 21:42:47 +0200357 script = open(script_name, "w", encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200358
Victor Stinner21a9c742011-05-19 15:51:27 +0200359 with script:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200360 script.write("""\
361from packaging.util import byte_compile
362files = [
363""")
364
365 # XXX would be nice to write absolute filenames, just for
366 # safety's sake (script should be more robust in the face of
367 # chdir'ing before running it). But this requires abspath'ing
368 # 'prefix' as well, and that breaks the hack in build_lib's
369 # 'byte_compile()' method that carefully tacks on a trailing
370 # slash (os.sep really) to make sure the prefix here is "just
371 # right". This whole prefix business is rather delicate -- the
372 # problem is that it's really a directory, but I'm treating it
373 # as a dumb string, so trailing slashes and so forth matter.
374
375 #py_files = map(os.path.abspath, py_files)
376 #if prefix:
377 # prefix = os.path.abspath(prefix)
378
379 script.write(",\n".join(map(repr, py_files)) + "]\n")
380 script.write("""
381byte_compile(files, optimize=%r, force=%r,
382 prefix=%r, base_dir=%r,
383 verbose=%r, dry_run=False,
384 direct=True)
385""" % (optimize, force, prefix, base_dir, verbose))
386
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200387 cmd = [sys.executable, script_name]
388 if optimize == 1:
389 cmd.insert(1, "-O")
390 elif optimize == 2:
391 cmd.insert(1, "-OO")
392
Éric Araujo088025f2011-06-04 18:45:40 +0200393 env = os.environ.copy()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200394 env['PYTHONPATH'] = os.path.pathsep.join(sys.path)
395 try:
396 spawn(cmd, env=env)
397 finally:
398 execute(os.remove, (script_name,), "removing %s" % script_name,
399 dry_run=dry_run)
400
401 # "Direct" byte-compilation: use the py_compile module to compile
402 # right here, right now. Note that the script generated in indirect
403 # mode simply calls 'byte_compile()' in direct mode, a weird sort of
404 # cross-process recursion. Hey, it works!
405 else:
406 from py_compile import compile
407
408 for file in py_files:
409 if file[-3:] != ".py":
410 # This lets us be lazy and not filter filenames in
411 # the "install_lib" command.
412 continue
413
414 # Terminology from the py_compile module:
415 # cfile - byte-compiled file
416 # dfile - purported source filename (same as 'file' by default)
417 cfile = file + (__debug__ and "c" or "o")
418 dfile = file
419 if prefix:
420 if file[:len(prefix)] != prefix:
421 raise ValueError("invalid prefix: filename %r doesn't "
422 "start with %r" % (file, prefix))
423 dfile = dfile[len(prefix):]
424 if base_dir:
425 dfile = os.path.join(base_dir, dfile)
426
427 cfile_base = os.path.basename(cfile)
428 if direct:
429 if force or newer(file, cfile):
430 logger.info("byte-compiling %s to %s", file, cfile_base)
431 if not dry_run:
432 compile(file, cfile, dfile)
433 else:
434 logger.debug("skipping byte-compilation of %s to %s",
435 file, cfile_base)
436
437
438def rfc822_escape(header):
439 """Return a form of *header* suitable for inclusion in an RFC 822-header.
440
441 This function ensures there are 8 spaces after each newline.
442 """
443 lines = header.split('\n')
444 sep = '\n' + 8 * ' '
445 return sep.join(lines)
446
447_RE_VERSION = re.compile('(\d+\.\d+(\.\d+)*)')
448_MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld '
449 'PROJECT:ld64-((\d+)(\.\d+)*)')
450
451
452def _find_ld_version():
453 """Find the ld version. The version scheme differs under Mac OS X."""
454 if sys.platform == 'darwin':
455 return _find_exe_version('ld -v', _MAC_OS_X_LD_VERSION)
456 else:
457 return _find_exe_version('ld -v')
458
459
460def _find_exe_version(cmd, pattern=_RE_VERSION):
461 """Find the version of an executable by running `cmd` in the shell.
462
463 `pattern` is a compiled regular expression. If not provided, defaults
464 to _RE_VERSION. If the command is not found, or the output does not
465 match the mattern, returns None.
466 """
467 from subprocess import Popen, PIPE
468 executable = cmd.split()[0]
469 if find_executable(executable) is None:
470 return None
471 pipe = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
472 try:
Victor Stinner9904b222011-05-21 02:20:36 +0200473 stdout, stderr = pipe.communicate()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200474 finally:
475 pipe.stdout.close()
476 pipe.stderr.close()
477 # some commands like ld under MacOS X, will give the
478 # output in the stderr, rather than stdout.
479 if stdout != '':
480 out_string = stdout
481 else:
482 out_string = stderr
483
484 result = pattern.search(out_string)
485 if result is None:
486 return None
487 return result.group(1)
488
489
490def get_compiler_versions():
491 """Return a tuple providing the versions of gcc, ld and dllwrap
492
493 For each command, if a command is not found, None is returned.
494 Otherwise a string with the version is returned.
495 """
496 gcc = _find_exe_version('gcc -dumpversion')
497 ld = _find_ld_version()
498 dllwrap = _find_exe_version('dllwrap --version')
499 return gcc, ld, dllwrap
500
501
502def newer_group(sources, target, missing='error'):
503 """Return true if 'target' is out-of-date with respect to any file
504 listed in 'sources'.
505
506 In other words, if 'target' exists and is newer
507 than every file in 'sources', return false; otherwise return true.
508 'missing' controls what we do when a source file is missing; the
509 default ("error") is to blow up with an OSError from inside 'stat()';
510 if it is "ignore", we silently drop any missing source files; if it is
511 "newer", any missing source files make us assume that 'target' is
512 out-of-date (this is handy in "dry-run" mode: it'll make you pretend to
513 carry out commands that wouldn't work because inputs are missing, but
514 that doesn't matter because you're not actually going to run the
515 commands).
516 """
517 # If the target doesn't even exist, then it's definitely out-of-date.
518 if not os.path.exists(target):
519 return True
520
521 # Otherwise we have to find out the hard way: if *any* source file
522 # is more recent than 'target', then 'target' is out-of-date and
523 # we can immediately return true. If we fall through to the end
524 # of the loop, then 'target' is up-to-date and we return false.
525 target_mtime = os.stat(target).st_mtime
526
527 for source in sources:
528 if not os.path.exists(source):
529 if missing == 'error': # blow up when we stat() the file
530 pass
531 elif missing == 'ignore': # missing source dropped from
532 continue # target's dependency list
533 elif missing == 'newer': # missing source means target is
534 return True # out-of-date
535
536 if os.stat(source).st_mtime > target_mtime:
537 return True
538
539 return False
540
541
542def write_file(filename, contents):
543 """Create *filename* and write *contents* to it.
544
545 *contents* is a sequence of strings without line terminators.
546 """
547 with open(filename, "w") as f:
548 for line in contents:
549 f.write(line + "\n")
550
551
552def _is_package(path):
Éric Araujo1c1d9a52011-06-10 23:26:31 +0200553 return os.path.isdir(path) and os.path.isfile(
554 os.path.join(path, '__init__.py'))
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200555
556
557# Code taken from the pip project
558def _is_archive_file(name):
559 archives = ('.zip', '.tar.gz', '.tar.bz2', '.tgz', '.tar')
560 ext = splitext(name)[1].lower()
Éric Araujo1c1d9a52011-06-10 23:26:31 +0200561 return ext in archives
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200562
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'\{([^}]*)\}')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200945_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200946_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:
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200979 # we support both
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200980 radical = radical.lstrip('/')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200981 radical = radical.lstrip('\\')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200982 for path, dir, files in os.walk(prefix):
983 path = os.path.normpath(path)
984 for file in _iglob(os.path.join(path, radical)):
985 yield file
986
987
988def cfg_to_args(path='setup.cfg'):
989 """Compatibility helper to use setup.cfg in setup.py.
990
991 This functions uses an existing setup.cfg to generate a dictionnary of
992 keywords that can be used by distutils.core.setup(**kwargs). It is used
993 by generate_setup_py.
994
995 *file* is the path to the setup.cfg file. If it doesn't exist,
996 PackagingFileError is raised.
997 """
998 # We need to declare the following constants here so that it's easier to
999 # generate the setup.py afterwards, using inspect.getsource.
1000
1001 # XXX ** == needs testing
1002 D1_D2_SETUP_ARGS = {"name": ("metadata",),
1003 "version": ("metadata",),
1004 "author": ("metadata",),
1005 "author_email": ("metadata",),
1006 "maintainer": ("metadata",),
1007 "maintainer_email": ("metadata",),
1008 "url": ("metadata", "home_page"),
1009 "description": ("metadata", "summary"),
1010 "long_description": ("metadata", "description"),
1011 "download-url": ("metadata",),
1012 "classifiers": ("metadata", "classifier"),
1013 "platforms": ("metadata", "platform"), # **
1014 "license": ("metadata",),
1015 "requires": ("metadata", "requires_dist"),
1016 "provides": ("metadata", "provides_dist"), # **
1017 "obsoletes": ("metadata", "obsoletes_dist"), # **
1018 "packages": ("files",),
1019 "scripts": ("files",),
1020 "py_modules": ("files", "modules"), # **
1021 }
1022
1023 MULTI_FIELDS = ("classifiers",
1024 "requires",
1025 "platforms",
1026 "packages",
1027 "scripts")
1028
1029 def has_get_option(config, section, option):
1030 if config.has_option(section, option):
1031 return config.get(section, option)
1032 elif config.has_option(section, option.replace('_', '-')):
1033 return config.get(section, option.replace('_', '-'))
1034 else:
1035 return False
1036
1037 # The real code starts here
1038 config = RawConfigParser()
1039 if not os.path.exists(file):
1040 raise PackagingFileError("file '%s' does not exist" %
1041 os.path.abspath(file))
1042 config.read(path)
1043
1044 kwargs = {}
1045 for arg in D1_D2_SETUP_ARGS:
1046 if len(D1_D2_SETUP_ARGS[arg]) == 2:
1047 # The distutils field name is different than packaging's
1048 section, option = D1_D2_SETUP_ARGS[arg]
1049
1050 else:
1051 # The distutils field name is the same thant packaging's
1052 section = D1_D2_SETUP_ARGS[arg][0]
1053 option = arg
1054
1055 in_cfg_value = has_get_option(config, section, option)
1056 if not in_cfg_value:
1057 # There is no such option in the setup.cfg
1058 if arg == "long_description":
1059 filename = has_get_option(config, section, "description_file")
1060 if filename:
1061 with open(filename) as fp:
1062 in_cfg_value = fp.read()
1063 else:
1064 continue
1065
1066 if arg in MULTI_FIELDS:
1067 # support multiline options
1068 in_cfg_value = in_cfg_value.strip().split('\n')
1069
1070 kwargs[arg] = in_cfg_value
1071
1072 return kwargs
1073
1074
1075_SETUP_TMPL = """\
1076# This script was automatically generated by packaging
1077import os
1078from distutils.core import setup
1079from ConfigParser import RawConfigParser
1080
1081%(func)s
1082
1083setup(**cfg_to_args())
1084"""
1085
1086
1087def generate_setup_py():
1088 """Generate a distutils compatible setup.py using an existing setup.cfg.
1089
1090 Raises a PackagingFileError when a setup.py already exists.
1091 """
1092 if os.path.exists("setup.py"):
Tarek Ziade721ccd02011-06-02 12:00:44 +02001093 raise PackagingFileError("a setup.py file already exists")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001094
Victor Stinner9cf6d132011-05-19 21:42:47 +02001095 with open("setup.py", "w", encoding='utf-8') as fp:
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001096 fp.write(_SETUP_TMPL % {'func': getsource(cfg_to_args)})
1097
1098
1099# Taken from the pip project
1100# https://github.com/pypa/pip/blob/master/pip/util.py
1101def ask(message, options):
1102 """Prompt the user with *message*; *options* contains allowed responses."""
1103 while True:
1104 response = input(message)
1105 response = response.strip().lower()
1106 if response not in options:
Éric Araujo3cab2f12011-06-08 04:10:57 +02001107 print('invalid response:', repr(response))
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001108 print('choose one of', ', '.join(repr(o) for o in options))
1109 else:
1110 return response
1111
1112
1113def _parse_record_file(record_file):
1114 distinfo, extra_metadata, installed = ({}, [], [])
1115 with open(record_file, 'r') as rfile:
1116 for path in rfile:
1117 path = path.strip()
1118 if path.endswith('egg-info') and os.path.isfile(path):
1119 distinfo_dir = path.replace('egg-info', 'dist-info')
1120 metadata = path
1121 egginfo = path
1122 elif path.endswith('egg-info') and os.path.isdir(path):
1123 distinfo_dir = path.replace('egg-info', 'dist-info')
1124 egginfo = path
1125 for metadata_file in os.listdir(path):
1126 metadata_fpath = os.path.join(path, metadata_file)
1127 if metadata_file == 'PKG-INFO':
1128 metadata = metadata_fpath
1129 else:
1130 extra_metadata.append(metadata_fpath)
1131 elif 'egg-info' in path and os.path.isfile(path):
1132 # skip extra metadata files
1133 continue
1134 else:
1135 installed.append(path)
1136
1137 distinfo['egginfo'] = egginfo
1138 distinfo['metadata'] = metadata
1139 distinfo['distinfo_dir'] = distinfo_dir
1140 distinfo['installer_path'] = os.path.join(distinfo_dir, 'INSTALLER')
1141 distinfo['metadata_path'] = os.path.join(distinfo_dir, 'METADATA')
1142 distinfo['record_path'] = os.path.join(distinfo_dir, 'RECORD')
1143 distinfo['requested_path'] = os.path.join(distinfo_dir, 'REQUESTED')
1144 installed.extend([distinfo['installer_path'], distinfo['metadata_path']])
1145 distinfo['installed'] = installed
1146 distinfo['extra_metadata'] = extra_metadata
1147 return distinfo
1148
1149
1150def _write_record_file(record_path, installed_files):
1151 with open(record_path, 'w', encoding='utf-8') as f:
1152 writer = csv.writer(f, delimiter=',', lineterminator=os.linesep,
1153 quotechar='"')
1154
1155 for fpath in installed_files:
1156 if fpath.endswith('.pyc') or fpath.endswith('.pyo'):
1157 # do not put size and md5 hash, as in PEP-376
1158 writer.writerow((fpath, '', ''))
1159 else:
1160 hash = hashlib.md5()
1161 with open(fpath, 'rb') as fp:
1162 hash.update(fp.read())
1163 md5sum = hash.hexdigest()
1164 size = os.path.getsize(fpath)
1165 writer.writerow((fpath, md5sum, size))
1166
1167 # add the RECORD file itself
1168 writer.writerow((record_path, '', ''))
1169 return record_path
1170
1171
1172def egginfo_to_distinfo(record_file, installer=_DEFAULT_INSTALLER,
1173 requested=False, remove_egginfo=False):
1174 """Create files and directories required for PEP 376
1175
1176 :param record_file: path to RECORD file as produced by setup.py --record
1177 :param installer: installer name
1178 :param requested: True if not installed as a dependency
1179 :param remove_egginfo: delete egginfo dir?
1180 """
1181 distinfo = _parse_record_file(record_file)
1182 distinfo_dir = distinfo['distinfo_dir']
1183 if os.path.isdir(distinfo_dir) and not os.path.islink(distinfo_dir):
1184 shutil.rmtree(distinfo_dir)
1185 elif os.path.exists(distinfo_dir):
1186 os.unlink(distinfo_dir)
1187
1188 os.makedirs(distinfo_dir)
1189
1190 # copy setuptools extra metadata files
1191 if distinfo['extra_metadata']:
1192 for path in distinfo['extra_metadata']:
1193 shutil.copy2(path, distinfo_dir)
1194 new_path = path.replace('egg-info', 'dist-info')
1195 distinfo['installed'].append(new_path)
1196
1197 metadata_path = distinfo['metadata_path']
1198 logger.info('creating %s', metadata_path)
1199 shutil.copy2(distinfo['metadata'], metadata_path)
1200
1201 installer_path = distinfo['installer_path']
1202 logger.info('creating %s', installer_path)
1203 with open(installer_path, 'w') as f:
1204 f.write(installer)
1205
1206 if requested:
1207 requested_path = distinfo['requested_path']
1208 logger.info('creating %s', requested_path)
Victor Stinner4c9706b2011-05-19 15:52:59 +02001209 open(requested_path, 'wb').close()
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001210 distinfo['installed'].append(requested_path)
1211
1212 record_path = distinfo['record_path']
1213 logger.info('creating %s', record_path)
1214 _write_record_file(record_path, distinfo['installed'])
1215
1216 if remove_egginfo:
1217 egginfo = distinfo['egginfo']
1218 logger.info('removing %s', egginfo)
1219 if os.path.isfile(egginfo):
1220 os.remove(egginfo)
1221 else:
1222 shutil.rmtree(egginfo)
1223
1224
1225def _has_egg_info(srcdir):
1226 if os.path.isdir(srcdir):
1227 for item in os.listdir(srcdir):
1228 full_path = os.path.join(srcdir, item)
1229 if item.endswith('.egg-info') and os.path.isdir(full_path):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001230 logger.debug("Found egg-info directory.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001231 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001232 logger.debug("No egg-info directory found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001233 return False
1234
1235
1236def _has_setuptools_text(setup_py):
1237 return _has_text(setup_py, 'setuptools')
1238
1239
1240def _has_distutils_text(setup_py):
1241 return _has_text(setup_py, 'distutils')
1242
1243
1244def _has_text(setup_py, installer):
1245 installer_pattern = re.compile('import {0}|from {0}'.format(installer))
1246 with open(setup_py, 'r', encoding='utf-8') as setup:
1247 for line in setup:
1248 if re.search(installer_pattern, line):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001249 logger.debug("Found %s text in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001250 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001251 logger.debug("No %s text found in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001252 return False
1253
1254
1255def _has_required_metadata(setup_cfg):
1256 config = RawConfigParser()
1257 config.read([setup_cfg], encoding='utf8')
1258 return (config.has_section('metadata') and
1259 'name' in config.options('metadata') and
1260 'version' in config.options('metadata'))
1261
1262
1263def _has_pkg_info(srcdir):
1264 pkg_info = os.path.join(srcdir, 'PKG-INFO')
1265 has_pkg_info = os.path.isfile(pkg_info)
1266 if has_pkg_info:
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001267 logger.debug("PKG-INFO file found.")
1268 else:
1269 logger.debug("No PKG-INFO file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001270 return has_pkg_info
1271
1272
1273def _has_setup_py(srcdir):
1274 setup_py = os.path.join(srcdir, 'setup.py')
1275 if os.path.isfile(setup_py):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001276 logger.debug('setup.py file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001277 return True
1278 return False
1279
1280
1281def _has_setup_cfg(srcdir):
1282 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1283 if os.path.isfile(setup_cfg):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001284 logger.debug('setup.cfg file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001285 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001286 logger.debug("No setup.cfg file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001287 return False
1288
1289
1290def is_setuptools(path):
1291 """Check if the project is based on setuptools.
1292
1293 :param path: path to source directory containing a setup.py script.
1294
1295 Return True if the project requires setuptools to install, else False.
1296 """
1297 srcdir = os.path.abspath(path)
1298 setup_py = os.path.join(srcdir, 'setup.py')
1299
1300 return _has_setup_py(srcdir) and (_has_egg_info(srcdir) or
1301 _has_setuptools_text(setup_py))
1302
1303
1304def is_distutils(path):
1305 """Check if the project is based on distutils.
1306
1307 :param path: path to source directory containing a setup.py script.
1308
1309 Return True if the project requires distutils to install, else False.
1310 """
1311 srcdir = os.path.abspath(path)
1312 setup_py = os.path.join(srcdir, 'setup.py')
1313
1314 return _has_setup_py(srcdir) and (_has_pkg_info(srcdir) or
1315 _has_distutils_text(setup_py))
1316
1317
1318def is_packaging(path):
1319 """Check if the project is based on packaging
1320
1321 :param path: path to source directory containing a setup.cfg file.
1322
1323 Return True if the project has a valid setup.cfg, else False.
1324 """
1325 srcdir = os.path.abspath(path)
1326 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1327
1328 return _has_setup_cfg(srcdir) and _has_required_metadata(setup_cfg)
1329
1330
1331def get_install_method(path):
1332 """Check if the project is based on packaging, setuptools, or distutils
1333
1334 :param path: path to source directory containing a setup.cfg file,
1335 or setup.py.
1336
1337 Returns a string representing the best install method to use.
1338 """
1339 if is_packaging(path):
1340 return "packaging"
1341 elif is_setuptools(path):
1342 return "setuptools"
1343 elif is_distutils(path):
1344 return "distutils"
1345 else:
1346 raise InstallationException('Cannot detect install method')
1347
1348
1349# XXX to be replaced by shutil.copytree
1350def copy_tree(src, dst, preserve_mode=True, preserve_times=True,
1351 preserve_symlinks=False, update=False, verbose=True,
1352 dry_run=False):
1353 from distutils.file_util import copy_file
1354
1355 if not dry_run and not os.path.isdir(src):
1356 raise PackagingFileError(
1357 "cannot copy tree '%s': not a directory" % src)
1358 try:
1359 names = os.listdir(src)
1360 except os.error as e:
1361 errstr = e[1]
1362 if dry_run:
1363 names = []
1364 else:
1365 raise PackagingFileError(
1366 "error listing files in '%s': %s" % (src, errstr))
1367
1368 if not dry_run:
1369 _mkpath(dst, verbose=verbose)
1370
1371 outputs = []
1372
1373 for n in names:
1374 src_name = os.path.join(src, n)
1375 dst_name = os.path.join(dst, n)
1376
1377 if preserve_symlinks and os.path.islink(src_name):
1378 link_dest = os.readlink(src_name)
1379 if verbose >= 1:
1380 logger.info("linking %s -> %s", dst_name, link_dest)
1381 if not dry_run:
1382 os.symlink(link_dest, dst_name)
1383 outputs.append(dst_name)
1384
1385 elif os.path.isdir(src_name):
1386 outputs.extend(
1387 copy_tree(src_name, dst_name, preserve_mode,
1388 preserve_times, preserve_symlinks, update,
1389 verbose=verbose, dry_run=dry_run))
1390 else:
1391 copy_file(src_name, dst_name, preserve_mode,
1392 preserve_times, update, verbose=verbose,
1393 dry_run=dry_run)
1394 outputs.append(dst_name)
1395
1396 return outputs
1397
1398# cache for by mkpath() -- in addition to cheapening redundant calls,
1399# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
1400_path_created = set()
1401
1402
1403# I don't use os.makedirs because a) it's new to Python 1.5.2, and
1404# b) it blows up if the directory already exists (I want to silently
1405# succeed in that case).
1406def _mkpath(name, mode=0o777, verbose=True, dry_run=False):
1407 # Detect a common bug -- name is None
1408 if not isinstance(name, str):
1409 raise PackagingInternalError(
1410 "mkpath: 'name' must be a string (got %r)" % (name,))
1411
1412 # XXX what's the better way to handle verbosity? print as we create
1413 # each directory in the path (the current behaviour), or only announce
1414 # the creation of the whole path? (quite easy to do the latter since
1415 # we're not using a recursive algorithm)
1416
1417 name = os.path.normpath(name)
1418 created_dirs = []
1419 if os.path.isdir(name) or name == '':
1420 return created_dirs
1421 if os.path.abspath(name) in _path_created:
1422 return created_dirs
1423
1424 head, tail = os.path.split(name)
1425 tails = [tail] # stack of lone dirs to create
1426
1427 while head and tail and not os.path.isdir(head):
1428 head, tail = os.path.split(head)
1429 tails.insert(0, tail) # push next higher dir onto stack
1430
1431 # now 'head' contains the deepest directory that already exists
1432 # (that is, the child of 'head' in 'name' is the highest directory
1433 # that does *not* exist)
1434 for d in tails:
1435 head = os.path.join(head, d)
1436 abs_head = os.path.abspath(head)
1437
1438 if abs_head in _path_created:
1439 continue
1440
1441 if verbose >= 1:
1442 logger.info("creating %s", head)
1443
1444 if not dry_run:
1445 try:
1446 os.mkdir(head, mode)
1447 except OSError as exc:
1448 if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
1449 raise PackagingFileError(
1450 "could not create '%s': %s" % (head, exc.args[-1]))
1451 created_dirs.append(head)
1452
1453 _path_created.add(abs_head)
1454 return created_dirs