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