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