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