blob: d4aae418dd86b2c4f01baa22e148075eb553a936 [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
253def execute(func, args, msg=None, verbose=0, dry_run=False):
254 """Perform some action that affects the outside world.
255
256 Some actions (e.g. writing to the filesystem) are special because
257 they are disabled by the 'dry_run' flag. This method takes care of all
258 that bureaucracy for you; all you have to do is supply the
259 function to call and an argument tuple for it (to embody the
260 "external action" being performed), and an optional message to
261 print.
262 """
263 if msg is None:
264 msg = "%s%r" % (func.__name__, args)
265 if msg[-2:] == ',)': # correct for singleton tuple
266 msg = msg[0:-2] + ')'
267
268 logger.info(msg)
269 if not dry_run:
270 func(*args)
271
272
273def strtobool(val):
Éric Araujod5d831b2011-06-06 01:13:48 +0200274 """Convert a string representation of truth to a boolean.
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200275
276 True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
277 are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
278 'val' is anything else.
279 """
280 val = val.lower()
281 if val in ('y', 'yes', 't', 'true', 'on', '1'):
282 return True
283 elif val in ('n', 'no', 'f', 'false', 'off', '0'):
284 return False
285 else:
286 raise ValueError("invalid truth value %r" % (val,))
287
288
289def byte_compile(py_files, optimize=0, force=False, prefix=None,
290 base_dir=None, verbose=0, dry_run=False, direct=None):
291 """Byte-compile a collection of Python source files to either .pyc
292 or .pyo files in the same directory.
293
294 'py_files' is a list of files to compile; any files that don't end in
295 ".py" are silently skipped. 'optimize' must be one of the following:
296 0 - don't optimize (generate .pyc)
297 1 - normal optimization (like "python -O")
298 2 - extra optimization (like "python -OO")
299 If 'force' is true, all files are recompiled regardless of
300 timestamps.
301
302 The source filename encoded in each bytecode file defaults to the
303 filenames listed in 'py_files'; you can modify these with 'prefix' and
304 'basedir'. 'prefix' is a string that will be stripped off of each
305 source filename, and 'base_dir' is a directory name that will be
306 prepended (after 'prefix' is stripped). You can supply either or both
307 (or neither) of 'prefix' and 'base_dir', as you wish.
308
309 If 'dry_run' is true, doesn't actually do anything that would
310 affect the filesystem.
311
312 Byte-compilation is either done directly in this interpreter process
313 with the standard py_compile module, or indirectly by writing a
314 temporary script and executing it. Normally, you should let
315 'byte_compile()' figure out to use direct compilation or not (see
316 the source for details). The 'direct' flag is used by the script
317 generated in indirect mode; unless you know what you're doing, leave
318 it set to None.
319 """
320 # nothing is done if sys.dont_write_bytecode is True
321 # FIXME this should not raise an error
322 if hasattr(sys, 'dont_write_bytecode') and sys.dont_write_bytecode:
323 raise PackagingByteCompileError('byte-compiling is disabled.')
324
325 # First, if the caller didn't force us into direct or indirect mode,
326 # figure out which mode we should be in. We take a conservative
327 # approach: choose direct mode *only* if the current interpreter is
328 # in debug mode and optimize is 0. If we're not in debug mode (-O
329 # or -OO), we don't know which level of optimization this
330 # interpreter is running with, so we can't do direct
331 # byte-compilation and be certain that it's the right thing. Thus,
332 # always compile indirectly if the current interpreter is in either
333 # optimize mode, or if either optimization level was requested by
334 # the caller.
335 if direct is None:
336 direct = (__debug__ and optimize == 0)
337
338 # "Indirect" byte-compilation: write a temporary script and then
339 # run it with the appropriate flags.
340 if not direct:
341 from tempfile import mkstemp
342 # XXX script_fd may leak, use something better than mkstemp
343 script_fd, script_name = mkstemp(".py")
344 logger.info("writing byte-compilation script '%s'", script_name)
345 if not dry_run:
346 if script_fd is not None:
Victor Stinner9cf6d132011-05-19 21:42:47 +0200347 script = os.fdopen(script_fd, "w", encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200348 else:
Victor Stinner9cf6d132011-05-19 21:42:47 +0200349 script = open(script_name, "w", encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200350
Victor Stinner21a9c742011-05-19 15:51:27 +0200351 with script:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200352 script.write("""\
353from packaging.util import byte_compile
354files = [
355""")
356
357 # XXX would be nice to write absolute filenames, just for
358 # safety's sake (script should be more robust in the face of
359 # chdir'ing before running it). But this requires abspath'ing
360 # 'prefix' as well, and that breaks the hack in build_lib's
361 # 'byte_compile()' method that carefully tacks on a trailing
362 # slash (os.sep really) to make sure the prefix here is "just
363 # right". This whole prefix business is rather delicate -- the
364 # problem is that it's really a directory, but I'm treating it
365 # as a dumb string, so trailing slashes and so forth matter.
366
367 #py_files = map(os.path.abspath, py_files)
368 #if prefix:
369 # prefix = os.path.abspath(prefix)
370
371 script.write(",\n".join(map(repr, py_files)) + "]\n")
372 script.write("""
373byte_compile(files, optimize=%r, force=%r,
374 prefix=%r, base_dir=%r,
375 verbose=%r, dry_run=False,
376 direct=True)
377""" % (optimize, force, prefix, base_dir, verbose))
378
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200379 cmd = [sys.executable, script_name]
380 if optimize == 1:
381 cmd.insert(1, "-O")
382 elif optimize == 2:
383 cmd.insert(1, "-OO")
384
Éric Araujo088025f2011-06-04 18:45:40 +0200385 env = os.environ.copy()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200386 env['PYTHONPATH'] = os.path.pathsep.join(sys.path)
387 try:
388 spawn(cmd, env=env)
389 finally:
390 execute(os.remove, (script_name,), "removing %s" % script_name,
391 dry_run=dry_run)
392
393 # "Direct" byte-compilation: use the py_compile module to compile
394 # right here, right now. Note that the script generated in indirect
395 # mode simply calls 'byte_compile()' in direct mode, a weird sort of
396 # cross-process recursion. Hey, it works!
397 else:
398 from py_compile import compile
399
400 for file in py_files:
401 if file[-3:] != ".py":
402 # This lets us be lazy and not filter filenames in
403 # the "install_lib" command.
404 continue
405
406 # Terminology from the py_compile module:
407 # cfile - byte-compiled file
408 # dfile - purported source filename (same as 'file' by default)
409 cfile = file + (__debug__ and "c" or "o")
410 dfile = file
411 if prefix:
412 if file[:len(prefix)] != prefix:
413 raise ValueError("invalid prefix: filename %r doesn't "
414 "start with %r" % (file, prefix))
415 dfile = dfile[len(prefix):]
416 if base_dir:
417 dfile = os.path.join(base_dir, dfile)
418
419 cfile_base = os.path.basename(cfile)
420 if direct:
421 if force or newer(file, cfile):
422 logger.info("byte-compiling %s to %s", file, cfile_base)
423 if not dry_run:
424 compile(file, cfile, dfile)
425 else:
426 logger.debug("skipping byte-compilation of %s to %s",
427 file, cfile_base)
428
429
430def rfc822_escape(header):
431 """Return a form of *header* suitable for inclusion in an RFC 822-header.
432
433 This function ensures there are 8 spaces after each newline.
434 """
435 lines = header.split('\n')
436 sep = '\n' + 8 * ' '
437 return sep.join(lines)
438
439_RE_VERSION = re.compile('(\d+\.\d+(\.\d+)*)')
440_MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld '
441 'PROJECT:ld64-((\d+)(\.\d+)*)')
442
443
444def _find_ld_version():
445 """Find the ld version. The version scheme differs under Mac OS X."""
446 if sys.platform == 'darwin':
447 return _find_exe_version('ld -v', _MAC_OS_X_LD_VERSION)
448 else:
449 return _find_exe_version('ld -v')
450
451
452def _find_exe_version(cmd, pattern=_RE_VERSION):
453 """Find the version of an executable by running `cmd` in the shell.
454
455 `pattern` is a compiled regular expression. If not provided, defaults
456 to _RE_VERSION. If the command is not found, or the output does not
457 match the mattern, returns None.
458 """
459 from subprocess import Popen, PIPE
460 executable = cmd.split()[0]
461 if find_executable(executable) is None:
462 return None
463 pipe = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
464 try:
Victor Stinner9904b222011-05-21 02:20:36 +0200465 stdout, stderr = pipe.communicate()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200466 finally:
467 pipe.stdout.close()
468 pipe.stderr.close()
469 # some commands like ld under MacOS X, will give the
470 # output in the stderr, rather than stdout.
471 if stdout != '':
472 out_string = stdout
473 else:
474 out_string = stderr
475
476 result = pattern.search(out_string)
477 if result is None:
478 return None
479 return result.group(1)
480
481
482def get_compiler_versions():
483 """Return a tuple providing the versions of gcc, ld and dllwrap
484
485 For each command, if a command is not found, None is returned.
486 Otherwise a string with the version is returned.
487 """
488 gcc = _find_exe_version('gcc -dumpversion')
489 ld = _find_ld_version()
490 dllwrap = _find_exe_version('dllwrap --version')
491 return gcc, ld, dllwrap
492
493
494def newer_group(sources, target, missing='error'):
495 """Return true if 'target' is out-of-date with respect to any file
496 listed in 'sources'.
497
498 In other words, if 'target' exists and is newer
499 than every file in 'sources', return false; otherwise return true.
500 'missing' controls what we do when a source file is missing; the
501 default ("error") is to blow up with an OSError from inside 'stat()';
502 if it is "ignore", we silently drop any missing source files; if it is
503 "newer", any missing source files make us assume that 'target' is
504 out-of-date (this is handy in "dry-run" mode: it'll make you pretend to
505 carry out commands that wouldn't work because inputs are missing, but
506 that doesn't matter because you're not actually going to run the
507 commands).
508 """
509 # If the target doesn't even exist, then it's definitely out-of-date.
510 if not os.path.exists(target):
511 return True
512
513 # Otherwise we have to find out the hard way: if *any* source file
514 # is more recent than 'target', then 'target' is out-of-date and
515 # we can immediately return true. If we fall through to the end
516 # of the loop, then 'target' is up-to-date and we return false.
517 target_mtime = os.stat(target).st_mtime
518
519 for source in sources:
520 if not os.path.exists(source):
521 if missing == 'error': # blow up when we stat() the file
522 pass
523 elif missing == 'ignore': # missing source dropped from
524 continue # target's dependency list
525 elif missing == 'newer': # missing source means target is
526 return True # out-of-date
527
528 if os.stat(source).st_mtime > target_mtime:
529 return True
530
531 return False
532
533
534def write_file(filename, contents):
535 """Create *filename* and write *contents* to it.
536
537 *contents* is a sequence of strings without line terminators.
538 """
539 with open(filename, "w") as f:
540 for line in contents:
541 f.write(line + "\n")
542
543
544def _is_package(path):
545 if not os.path.isdir(path):
546 return False
547 return os.path.isfile(os.path.join(path, '__init__.py'))
548
549
550# Code taken from the pip project
551def _is_archive_file(name):
552 archives = ('.zip', '.tar.gz', '.tar.bz2', '.tgz', '.tar')
553 ext = splitext(name)[1].lower()
554 if ext in archives:
555 return True
556 return False
557
558
559def _under(path, root):
560 path = path.split(os.sep)
561 root = root.split(os.sep)
562 if len(root) > len(path):
563 return False
564 for pos, part in enumerate(root):
565 if path[pos] != part:
566 return False
567 return True
568
569
570def _package_name(root_path, path):
571 # Return a dotted package name, given a subpath
572 if not _under(path, root_path):
573 raise ValueError('"%s" is not a subpath of "%s"' % (path, root_path))
574 return path[len(root_path) + 1:].replace(os.sep, '.')
575
576
577def find_packages(paths=(os.curdir,), exclude=()):
578 """Return a list all Python packages found recursively within
579 directories 'paths'
580
581 'paths' should be supplied as a sequence of "cross-platform"
582 (i.e. URL-style) path; it will be converted to the appropriate local
583 path syntax.
584
585 'exclude' is a sequence of package names to exclude; '*' can be used as
586 a wildcard in the names, such that 'foo.*' will exclude all subpackages
587 of 'foo' (but not 'foo' itself).
588 """
589 packages = []
590 discarded = []
591
592 def _discarded(path):
593 for discard in discarded:
594 if _under(path, discard):
595 return True
596 return False
597
598 for path in paths:
599 path = convert_path(path)
600 for root, dirs, files in os.walk(path):
601 for dir_ in dirs:
602 fullpath = os.path.join(root, dir_)
603 if _discarded(fullpath):
604 continue
605 # we work only with Python packages
606 if not _is_package(fullpath):
607 discarded.append(fullpath)
608 continue
609 # see if it's excluded
610 excluded = False
611 package_name = _package_name(path, fullpath)
612 for pattern in exclude:
613 if fnmatchcase(package_name, pattern):
614 excluded = True
615 break
616 if excluded:
617 continue
618
619 # adding it to the list
620 packages.append(package_name)
621 return packages
622
623
624def resolve_name(name):
625 """Resolve a name like ``module.object`` to an object and return it.
626
627 Raise ImportError if the module or name is not found.
628 """
629 parts = name.split('.')
630 cursor = len(parts)
631 module_name = parts[:cursor]
632
633 while cursor > 0:
634 try:
635 ret = __import__('.'.join(module_name))
636 break
637 except ImportError:
638 if cursor == 0:
639 raise
640 cursor -= 1
641 module_name = parts[:cursor]
642 ret = ''
643
644 for part in parts[1:]:
645 try:
646 ret = getattr(ret, part)
647 except AttributeError as exc:
648 raise ImportError(exc)
649
650 return ret
651
652
653def splitext(path):
654 """Like os.path.splitext, but take off .tar too"""
655 base, ext = posixpath.splitext(path)
656 if base.lower().endswith('.tar'):
657 ext = base[-4:] + ext
658 base = base[:-4]
659 return base, ext
660
661
662def unzip_file(filename, location, flatten=True):
663 """Unzip the file *filename* into the *location* directory."""
664 if not os.path.exists(location):
665 os.makedirs(location)
666 with open(filename, 'rb') as zipfp:
667 zip = zipfile.ZipFile(zipfp)
668 leading = has_leading_dir(zip.namelist()) and flatten
669 for name in zip.namelist():
670 data = zip.read(name)
671 fn = name
672 if leading:
673 fn = split_leading_dir(name)[1]
674 fn = os.path.join(location, fn)
675 dir = os.path.dirname(fn)
676 if not os.path.exists(dir):
677 os.makedirs(dir)
678 if fn.endswith('/') or fn.endswith('\\'):
679 # A directory
680 if not os.path.exists(fn):
681 os.makedirs(fn)
682 else:
683 with open(fn, 'wb') as fp:
684 fp.write(data)
685
686
687def untar_file(filename, location):
688 """Untar the file *filename* into the *location* directory."""
689 if not os.path.exists(location):
690 os.makedirs(location)
691 if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):
692 mode = 'r:gz'
693 elif (filename.lower().endswith('.bz2')
694 or filename.lower().endswith('.tbz')):
695 mode = 'r:bz2'
696 elif filename.lower().endswith('.tar'):
697 mode = 'r'
698 else:
699 mode = 'r:*'
700 with tarfile.open(filename, mode) as tar:
701 leading = has_leading_dir(member.name for member in tar.getmembers())
702 for member in tar.getmembers():
703 fn = member.name
704 if leading:
705 fn = split_leading_dir(fn)[1]
706 path = os.path.join(location, fn)
707 if member.isdir():
708 if not os.path.exists(path):
709 os.makedirs(path)
710 else:
711 try:
712 fp = tar.extractfile(member)
713 except (KeyError, AttributeError):
714 # Some corrupt tar files seem to produce this
715 # (specifically bad symlinks)
716 continue
717 try:
718 if not os.path.exists(os.path.dirname(path)):
719 os.makedirs(os.path.dirname(path))
720 with open(path, 'wb') as destfp:
721 shutil.copyfileobj(fp, destfp)
722 finally:
723 fp.close()
724
725
726def has_leading_dir(paths):
727 """Return true if all the paths have the same leading path name.
728
729 In other words, check that everything is in one subdirectory in an
730 archive.
731 """
732 common_prefix = None
733 for path in paths:
734 prefix, rest = split_leading_dir(path)
735 if not prefix:
736 return False
737 elif common_prefix is None:
738 common_prefix = prefix
739 elif prefix != common_prefix:
740 return False
741 return True
742
743
744def split_leading_dir(path):
745 path = str(path)
746 path = path.lstrip('/').lstrip('\\')
747 if '/' in path and (('\\' in path and path.find('/') < path.find('\\'))
748 or '\\' not in path):
749 return path.split('/', 1)
750 elif '\\' in path:
751 return path.split('\\', 1)
752 else:
753 return path, ''
754
755
756def spawn(cmd, search_path=True, verbose=0, dry_run=False, env=None):
757 """Run another program specified as a command list 'cmd' in a new process.
758
759 'cmd' is just the argument list for the new process, ie.
760 cmd[0] is the program to run and cmd[1:] are the rest of its arguments.
761 There is no way to run a program with a name different from that of its
762 executable.
763
764 If 'search_path' is true (the default), the system's executable
765 search path will be used to find the program; otherwise, cmd[0]
766 must be the exact path to the executable. If 'dry_run' is true,
767 the command will not actually be run.
768
769 If 'env' is given, it's a environment dictionary used for the execution
770 environment.
771
772 Raise PackagingExecError if running the program fails in any way; just
773 return on success.
774 """
775 logger.info(' '.join(cmd))
776 if dry_run:
777 return
778 exit_status = subprocess.call(cmd, env=env)
779 if exit_status != 0:
780 msg = "command '%s' failed with exit status %d"
781 raise PackagingExecError(msg % (cmd, exit_status))
782
783
784def find_executable(executable, path=None):
785 """Try to find 'executable' in the directories listed in 'path'.
786
787 *path* is a string listing directories separated by 'os.pathsep' and
788 defaults to os.environ['PATH']. Returns the complete filename or None
789 if not found.
790 """
791 if path is None:
792 path = os.environ['PATH']
793 paths = path.split(os.pathsep)
794 base, ext = os.path.splitext(executable)
795
796 if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
797 executable = executable + '.exe'
798
799 if not os.path.isfile(executable):
800 for p in paths:
801 f = os.path.join(p, executable)
802 if os.path.isfile(f):
803 # the file exists, we have a shot at spawn working
804 return f
805 return None
806 else:
807 return executable
808
809
810DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
811DEFAULT_REALM = 'pypi'
812DEFAULT_PYPIRC = """\
813[distutils]
814index-servers =
815 pypi
816
817[pypi]
818username:%s
819password:%s
820"""
821
822
823def get_pypirc_path():
824 """Return path to pypirc config file."""
825 return os.path.join(os.path.expanduser('~'), '.pypirc')
826
827
828def generate_pypirc(username, password):
829 """Create a default .pypirc file."""
830 rc = get_pypirc_path()
831 with open(rc, 'w') as f:
832 f.write(DEFAULT_PYPIRC % (username, password))
833 try:
834 os.chmod(rc, 0o600)
835 except OSError:
836 # should do something better here
837 pass
838
839
840def read_pypirc(repository=DEFAULT_REPOSITORY, realm=DEFAULT_REALM):
841 """Read the .pypirc file."""
842 rc = get_pypirc_path()
843 if os.path.exists(rc):
844 config = RawConfigParser()
845 config.read(rc)
846 sections = config.sections()
847 if 'distutils' in sections:
848 # let's get the list of servers
849 index_servers = config.get('distutils', 'index-servers')
850 _servers = [server.strip() for server in
851 index_servers.split('\n')
852 if server.strip() != '']
853 if _servers == []:
854 # nothing set, let's try to get the default pypi
855 if 'pypi' in sections:
856 _servers = ['pypi']
857 else:
858 # the file is not properly defined, returning
859 # an empty dict
860 return {}
861 for server in _servers:
862 current = {'server': server}
863 current['username'] = config.get(server, 'username')
864
865 # optional params
866 for key, default in (('repository', DEFAULT_REPOSITORY),
867 ('realm', DEFAULT_REALM),
868 ('password', None)):
869 if config.has_option(server, key):
870 current[key] = config.get(server, key)
871 else:
872 current[key] = default
873 if (current['server'] == repository or
874 current['repository'] == repository):
875 return current
876 elif 'server-login' in sections:
877 # old format
878 server = 'server-login'
879 if config.has_option(server, 'repository'):
880 repository = config.get(server, 'repository')
881 else:
882 repository = DEFAULT_REPOSITORY
883
884 return {'username': config.get(server, 'username'),
885 'password': config.get(server, 'password'),
886 'repository': repository,
887 'server': server,
888 'realm': DEFAULT_REALM}
889
890 return {}
891
892
893# utility functions for 2to3 support
894
895def run_2to3(files, doctests_only=False, fixer_names=None,
896 options=None, explicit=None):
897 """ Wrapper function around the refactor() class which
898 performs the conversions on a list of python files.
899 Invoke 2to3 on a list of Python files. The files should all come
900 from the build area, as the modification is done in-place."""
901
902 #if not files:
903 # return
904
905 # Make this class local, to delay import of 2to3
906 from lib2to3.refactor import get_fixers_from_package, RefactoringTool
907 fixers = []
908 fixers = get_fixers_from_package('lib2to3.fixes')
909
910 if fixer_names:
911 for fixername in fixer_names:
912 fixers.extend(fixer for fixer in
913 get_fixers_from_package(fixername))
914 r = RefactoringTool(fixers, options=options)
915 r.refactor(files, write=True, doctests_only=doctests_only)
916
917
918class Mixin2to3:
919 """ Wrapper class for commands that run 2to3.
920 To configure 2to3, setup scripts may either change
921 the class variables, or inherit from this class
922 to override how 2to3 is invoked.
923 """
924 # provide list of fixers to run.
925 # defaults to all from lib2to3.fixers
926 fixer_names = None
927
928 # options dictionary
929 options = None
930
931 # list of fixers to invoke even though they are marked as explicit
932 explicit = None
933
934 def run_2to3(self, files, doctests_only=False):
935 """ Issues a call to util.run_2to3. """
936 return run_2to3(files, doctests_only, self.fixer_names,
937 self.options, self.explicit)
938
939RICH_GLOB = re.compile(r'\{([^}]*)\}')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200940_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200941_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
942
943
944def iglob(path_glob):
945 """Extended globbing function that supports ** and {opt1,opt2,opt3}."""
946 if _CHECK_RECURSIVE_GLOB.search(path_glob):
947 msg = """invalid glob %r: recursive glob "**" must be used alone"""
948 raise ValueError(msg % path_glob)
949 if _CHECK_MISMATCH_SET.search(path_glob):
950 msg = """invalid glob %r: mismatching set marker '{' or '}'"""
951 raise ValueError(msg % path_glob)
952 return _iglob(path_glob)
953
954
955def _iglob(path_glob):
956 rich_path_glob = RICH_GLOB.split(path_glob, 1)
957 if len(rich_path_glob) > 1:
958 assert len(rich_path_glob) == 3, rich_path_glob
959 prefix, set, suffix = rich_path_glob
960 for item in set.split(','):
961 for path in _iglob(''.join((prefix, item, suffix))):
962 yield path
963 else:
964 if '**' not in path_glob:
965 for item in std_iglob(path_glob):
966 yield item
967 else:
968 prefix, radical = path_glob.split('**', 1)
969 if prefix == '':
970 prefix = '.'
971 if radical == '':
972 radical = '*'
973 else:
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200974 # we support both
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200975 radical = radical.lstrip('/')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200976 radical = radical.lstrip('\\')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200977 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"):
Tarek Ziade721ccd02011-06-02 12:00:44 +02001088 raise PackagingFileError("a setup.py file already exists")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001089
Victor Stinner9cf6d132011-05-19 21:42:47 +02001090 with open("setup.py", "w", encoding='utf-8') as fp:
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001091 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):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001225 logger.debug("Found egg-info directory.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001226 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001227 logger.debug("No egg-info directory found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001228 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):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001244 logger.debug("Found %s text in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001245 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001246 logger.debug("No %s text found in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001247 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:
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001262 logger.debug("PKG-INFO file found.")
1263 else:
1264 logger.debug("No PKG-INFO file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001265 return has_pkg_info
1266
1267
1268def _has_setup_py(srcdir):
1269 setup_py = os.path.join(srcdir, 'setup.py')
1270 if os.path.isfile(setup_py):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001271 logger.debug('setup.py file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001272 return True
1273 return False
1274
1275
1276def _has_setup_cfg(srcdir):
1277 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1278 if os.path.isfile(setup_cfg):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001279 logger.debug('setup.cfg file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001280 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001281 logger.debug("No setup.cfg file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001282 return False
1283
1284
1285def is_setuptools(path):
1286 """Check if the project is based on setuptools.
1287
1288 :param path: path to source directory containing a setup.py script.
1289
1290 Return True if the project requires setuptools to install, else False.
1291 """
1292 srcdir = os.path.abspath(path)
1293 setup_py = os.path.join(srcdir, 'setup.py')
1294
1295 return _has_setup_py(srcdir) and (_has_egg_info(srcdir) or
1296 _has_setuptools_text(setup_py))
1297
1298
1299def is_distutils(path):
1300 """Check if the project is based on distutils.
1301
1302 :param path: path to source directory containing a setup.py script.
1303
1304 Return True if the project requires distutils to install, else False.
1305 """
1306 srcdir = os.path.abspath(path)
1307 setup_py = os.path.join(srcdir, 'setup.py')
1308
1309 return _has_setup_py(srcdir) and (_has_pkg_info(srcdir) or
1310 _has_distutils_text(setup_py))
1311
1312
1313def is_packaging(path):
1314 """Check if the project is based on packaging
1315
1316 :param path: path to source directory containing a setup.cfg file.
1317
1318 Return True if the project has a valid setup.cfg, else False.
1319 """
1320 srcdir = os.path.abspath(path)
1321 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1322
1323 return _has_setup_cfg(srcdir) and _has_required_metadata(setup_cfg)
1324
1325
1326def get_install_method(path):
1327 """Check if the project is based on packaging, setuptools, or distutils
1328
1329 :param path: path to source directory containing a setup.cfg file,
1330 or setup.py.
1331
1332 Returns a string representing the best install method to use.
1333 """
1334 if is_packaging(path):
1335 return "packaging"
1336 elif is_setuptools(path):
1337 return "setuptools"
1338 elif is_distutils(path):
1339 return "distutils"
1340 else:
1341 raise InstallationException('Cannot detect install method')
1342
1343
1344# XXX to be replaced by shutil.copytree
1345def copy_tree(src, dst, preserve_mode=True, preserve_times=True,
1346 preserve_symlinks=False, update=False, verbose=True,
1347 dry_run=False):
1348 from distutils.file_util import copy_file
1349
1350 if not dry_run and not os.path.isdir(src):
1351 raise PackagingFileError(
1352 "cannot copy tree '%s': not a directory" % src)
1353 try:
1354 names = os.listdir(src)
1355 except os.error as e:
1356 errstr = e[1]
1357 if dry_run:
1358 names = []
1359 else:
1360 raise PackagingFileError(
1361 "error listing files in '%s': %s" % (src, errstr))
1362
1363 if not dry_run:
1364 _mkpath(dst, verbose=verbose)
1365
1366 outputs = []
1367
1368 for n in names:
1369 src_name = os.path.join(src, n)
1370 dst_name = os.path.join(dst, n)
1371
1372 if preserve_symlinks and os.path.islink(src_name):
1373 link_dest = os.readlink(src_name)
1374 if verbose >= 1:
1375 logger.info("linking %s -> %s", dst_name, link_dest)
1376 if not dry_run:
1377 os.symlink(link_dest, dst_name)
1378 outputs.append(dst_name)
1379
1380 elif os.path.isdir(src_name):
1381 outputs.extend(
1382 copy_tree(src_name, dst_name, preserve_mode,
1383 preserve_times, preserve_symlinks, update,
1384 verbose=verbose, dry_run=dry_run))
1385 else:
1386 copy_file(src_name, dst_name, preserve_mode,
1387 preserve_times, update, verbose=verbose,
1388 dry_run=dry_run)
1389 outputs.append(dst_name)
1390
1391 return outputs
1392
1393# cache for by mkpath() -- in addition to cheapening redundant calls,
1394# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
1395_path_created = set()
1396
1397
1398# I don't use os.makedirs because a) it's new to Python 1.5.2, and
1399# b) it blows up if the directory already exists (I want to silently
1400# succeed in that case).
1401def _mkpath(name, mode=0o777, verbose=True, dry_run=False):
1402 # Detect a common bug -- name is None
1403 if not isinstance(name, str):
1404 raise PackagingInternalError(
1405 "mkpath: 'name' must be a string (got %r)" % (name,))
1406
1407 # XXX what's the better way to handle verbosity? print as we create
1408 # each directory in the path (the current behaviour), or only announce
1409 # the creation of the whole path? (quite easy to do the latter since
1410 # we're not using a recursive algorithm)
1411
1412 name = os.path.normpath(name)
1413 created_dirs = []
1414 if os.path.isdir(name) or name == '':
1415 return created_dirs
1416 if os.path.abspath(name) in _path_created:
1417 return created_dirs
1418
1419 head, tail = os.path.split(name)
1420 tails = [tail] # stack of lone dirs to create
1421
1422 while head and tail and not os.path.isdir(head):
1423 head, tail = os.path.split(head)
1424 tails.insert(0, tail) # push next higher dir onto stack
1425
1426 # now 'head' contains the deepest directory that already exists
1427 # (that is, the child of 'head' in 'name' is the highest directory
1428 # that does *not* exist)
1429 for d in tails:
1430 head = os.path.join(head, d)
1431 abs_head = os.path.abspath(head)
1432
1433 if abs_head in _path_created:
1434 continue
1435
1436 if verbose >= 1:
1437 logger.info("creating %s", head)
1438
1439 if not dry_run:
1440 try:
1441 os.mkdir(head, mode)
1442 except OSError as exc:
1443 if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
1444 raise PackagingFileError(
1445 "could not create '%s': %s" % (head, exc.args[-1]))
1446 created_dirs.append(head)
1447
1448 _path_created.add(abs_head)
1449 return created_dirs