blob: 4dff547a83550227c9a132b800aa96677ed345f7 [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 """
Éric Araujo62806062011-06-11 09:46:07 +0200780 logger.debug('spawn: running %r', cmd)
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200781 if dry_run:
Éric Araujo62806062011-06-11 09:46:07 +0200782 logging.debug('dry run, no process actually spawned')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200783 return
784 exit_status = subprocess.call(cmd, env=env)
785 if exit_status != 0:
Éric Araujo62806062011-06-11 09:46:07 +0200786 msg = "command %r failed with exit status %d"
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200787 raise PackagingExecError(msg % (cmd, exit_status))
788
789
790def find_executable(executable, path=None):
791 """Try to find 'executable' in the directories listed in 'path'.
792
793 *path* is a string listing directories separated by 'os.pathsep' and
794 defaults to os.environ['PATH']. Returns the complete filename or None
795 if not found.
796 """
797 if path is None:
798 path = os.environ['PATH']
799 paths = path.split(os.pathsep)
800 base, ext = os.path.splitext(executable)
801
802 if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
803 executable = executable + '.exe'
804
805 if not os.path.isfile(executable):
806 for p in paths:
807 f = os.path.join(p, executable)
808 if os.path.isfile(f):
809 # the file exists, we have a shot at spawn working
810 return f
811 return None
812 else:
813 return executable
814
815
816DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
817DEFAULT_REALM = 'pypi'
818DEFAULT_PYPIRC = """\
819[distutils]
820index-servers =
821 pypi
822
823[pypi]
824username:%s
825password:%s
826"""
827
828
829def get_pypirc_path():
830 """Return path to pypirc config file."""
831 return os.path.join(os.path.expanduser('~'), '.pypirc')
832
833
834def generate_pypirc(username, password):
835 """Create a default .pypirc file."""
836 rc = get_pypirc_path()
837 with open(rc, 'w') as f:
838 f.write(DEFAULT_PYPIRC % (username, password))
839 try:
840 os.chmod(rc, 0o600)
841 except OSError:
842 # should do something better here
843 pass
844
845
846def read_pypirc(repository=DEFAULT_REPOSITORY, realm=DEFAULT_REALM):
847 """Read the .pypirc file."""
848 rc = get_pypirc_path()
849 if os.path.exists(rc):
850 config = RawConfigParser()
851 config.read(rc)
852 sections = config.sections()
853 if 'distutils' in sections:
854 # let's get the list of servers
855 index_servers = config.get('distutils', 'index-servers')
856 _servers = [server.strip() for server in
857 index_servers.split('\n')
858 if server.strip() != '']
859 if _servers == []:
860 # nothing set, let's try to get the default pypi
861 if 'pypi' in sections:
862 _servers = ['pypi']
863 else:
864 # the file is not properly defined, returning
865 # an empty dict
866 return {}
867 for server in _servers:
868 current = {'server': server}
869 current['username'] = config.get(server, 'username')
870
871 # optional params
872 for key, default in (('repository', DEFAULT_REPOSITORY),
873 ('realm', DEFAULT_REALM),
874 ('password', None)):
875 if config.has_option(server, key):
876 current[key] = config.get(server, key)
877 else:
878 current[key] = default
879 if (current['server'] == repository or
880 current['repository'] == repository):
881 return current
882 elif 'server-login' in sections:
883 # old format
884 server = 'server-login'
885 if config.has_option(server, 'repository'):
886 repository = config.get(server, 'repository')
887 else:
888 repository = DEFAULT_REPOSITORY
889
890 return {'username': config.get(server, 'username'),
891 'password': config.get(server, 'password'),
892 'repository': repository,
893 'server': server,
894 'realm': DEFAULT_REALM}
895
896 return {}
897
898
899# utility functions for 2to3 support
900
901def run_2to3(files, doctests_only=False, fixer_names=None,
902 options=None, explicit=None):
903 """ Wrapper function around the refactor() class which
904 performs the conversions on a list of python files.
905 Invoke 2to3 on a list of Python files. The files should all come
906 from the build area, as the modification is done in-place."""
907
908 #if not files:
909 # return
910
911 # Make this class local, to delay import of 2to3
912 from lib2to3.refactor import get_fixers_from_package, RefactoringTool
913 fixers = []
914 fixers = get_fixers_from_package('lib2to3.fixes')
915
916 if fixer_names:
917 for fixername in fixer_names:
918 fixers.extend(fixer for fixer in
919 get_fixers_from_package(fixername))
920 r = RefactoringTool(fixers, options=options)
921 r.refactor(files, write=True, doctests_only=doctests_only)
922
923
924class Mixin2to3:
925 """ Wrapper class for commands that run 2to3.
926 To configure 2to3, setup scripts may either change
927 the class variables, or inherit from this class
928 to override how 2to3 is invoked.
929 """
930 # provide list of fixers to run.
931 # defaults to all from lib2to3.fixers
932 fixer_names = None
933
934 # options dictionary
935 options = None
936
937 # list of fixers to invoke even though they are marked as explicit
938 explicit = None
939
940 def run_2to3(self, files, doctests_only=False):
941 """ Issues a call to util.run_2to3. """
942 return run_2to3(files, doctests_only, self.fixer_names,
943 self.options, self.explicit)
944
945RICH_GLOB = re.compile(r'\{([^}]*)\}')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200946_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200947_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
948
949
950def iglob(path_glob):
951 """Extended globbing function that supports ** and {opt1,opt2,opt3}."""
952 if _CHECK_RECURSIVE_GLOB.search(path_glob):
953 msg = """invalid glob %r: recursive glob "**" must be used alone"""
954 raise ValueError(msg % path_glob)
955 if _CHECK_MISMATCH_SET.search(path_glob):
956 msg = """invalid glob %r: mismatching set marker '{' or '}'"""
957 raise ValueError(msg % path_glob)
958 return _iglob(path_glob)
959
960
961def _iglob(path_glob):
962 rich_path_glob = RICH_GLOB.split(path_glob, 1)
963 if len(rich_path_glob) > 1:
964 assert len(rich_path_glob) == 3, rich_path_glob
965 prefix, set, suffix = rich_path_glob
966 for item in set.split(','):
967 for path in _iglob(''.join((prefix, item, suffix))):
968 yield path
969 else:
970 if '**' not in path_glob:
971 for item in std_iglob(path_glob):
972 yield item
973 else:
974 prefix, radical = path_glob.split('**', 1)
975 if prefix == '':
976 prefix = '.'
977 if radical == '':
978 radical = '*'
979 else:
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200980 # we support both
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200981 radical = radical.lstrip('/')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200982 radical = radical.lstrip('\\')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200983 for path, dir, files in os.walk(prefix):
984 path = os.path.normpath(path)
985 for file in _iglob(os.path.join(path, radical)):
986 yield file
987
988
989def cfg_to_args(path='setup.cfg'):
990 """Compatibility helper to use setup.cfg in setup.py.
991
992 This functions uses an existing setup.cfg to generate a dictionnary of
993 keywords that can be used by distutils.core.setup(**kwargs). It is used
994 by generate_setup_py.
995
996 *file* is the path to the setup.cfg file. If it doesn't exist,
997 PackagingFileError is raised.
998 """
999 # We need to declare the following constants here so that it's easier to
1000 # generate the setup.py afterwards, using inspect.getsource.
1001
1002 # XXX ** == needs testing
1003 D1_D2_SETUP_ARGS = {"name": ("metadata",),
1004 "version": ("metadata",),
1005 "author": ("metadata",),
1006 "author_email": ("metadata",),
1007 "maintainer": ("metadata",),
1008 "maintainer_email": ("metadata",),
1009 "url": ("metadata", "home_page"),
1010 "description": ("metadata", "summary"),
1011 "long_description": ("metadata", "description"),
1012 "download-url": ("metadata",),
1013 "classifiers": ("metadata", "classifier"),
1014 "platforms": ("metadata", "platform"), # **
1015 "license": ("metadata",),
1016 "requires": ("metadata", "requires_dist"),
1017 "provides": ("metadata", "provides_dist"), # **
1018 "obsoletes": ("metadata", "obsoletes_dist"), # **
Éric Araujo36050302011-06-10 23:52:26 +02001019 "package_dir": ("files", 'packages_root'),
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001020 "packages": ("files",),
1021 "scripts": ("files",),
1022 "py_modules": ("files", "modules"), # **
1023 }
1024
1025 MULTI_FIELDS = ("classifiers",
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001026 "platforms",
Éric Araujo36050302011-06-10 23:52:26 +02001027 "requires",
1028 "provides",
1029 "obsoletes",
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001030 "packages",
Éric Araujo36050302011-06-10 23:52:26 +02001031 "scripts",
1032 "py_modules")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001033
1034 def has_get_option(config, section, option):
1035 if config.has_option(section, option):
1036 return config.get(section, option)
1037 elif config.has_option(section, option.replace('_', '-')):
1038 return config.get(section, option.replace('_', '-'))
1039 else:
1040 return False
1041
1042 # The real code starts here
1043 config = RawConfigParser()
Éric Araujo36050302011-06-10 23:52:26 +02001044 if not os.path.exists(path):
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001045 raise PackagingFileError("file '%s' does not exist" %
Éric Araujo36050302011-06-10 23:52:26 +02001046 os.path.abspath(path))
Éric Araujo8d233f22011-06-12 23:02:57 +02001047 config.read(path, encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001048
1049 kwargs = {}
1050 for arg in D1_D2_SETUP_ARGS:
1051 if len(D1_D2_SETUP_ARGS[arg]) == 2:
1052 # The distutils field name is different than packaging's
1053 section, option = D1_D2_SETUP_ARGS[arg]
1054
1055 else:
1056 # The distutils field name is the same thant packaging's
1057 section = D1_D2_SETUP_ARGS[arg][0]
1058 option = arg
1059
1060 in_cfg_value = has_get_option(config, section, option)
1061 if not in_cfg_value:
1062 # There is no such option in the setup.cfg
Éric Araujo36050302011-06-10 23:52:26 +02001063 if arg == 'long_description':
1064 filenames = has_get_option(config, section, 'description-file')
1065 if filenames:
1066 filenames = split_multiline(filenames)
1067 in_cfg_value = []
1068 for filename in filenames:
1069 with open(filename) as fp:
1070 in_cfg_value.append(fp.read())
1071 in_cfg_value = '\n\n'.join(in_cfg_value)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001072 else:
1073 continue
1074
Éric Araujo36050302011-06-10 23:52:26 +02001075 if arg == 'package_dir' and in_cfg_value:
1076 in_cfg_value = {'': in_cfg_value}
1077
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001078 if arg in MULTI_FIELDS:
1079 # support multiline options
Éric Araujo36050302011-06-10 23:52:26 +02001080 in_cfg_value = split_multiline(in_cfg_value)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001081
1082 kwargs[arg] = in_cfg_value
1083
1084 return kwargs
1085
1086
1087_SETUP_TMPL = """\
1088# This script was automatically generated by packaging
1089import os
1090from distutils.core import setup
1091from ConfigParser import RawConfigParser
1092
1093%(func)s
1094
1095setup(**cfg_to_args())
1096"""
1097
1098
1099def generate_setup_py():
1100 """Generate a distutils compatible setup.py using an existing setup.cfg.
1101
1102 Raises a PackagingFileError when a setup.py already exists.
1103 """
1104 if os.path.exists("setup.py"):
Tarek Ziade721ccd02011-06-02 12:00:44 +02001105 raise PackagingFileError("a setup.py file already exists")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001106
Victor Stinner9cf6d132011-05-19 21:42:47 +02001107 with open("setup.py", "w", encoding='utf-8') as fp:
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001108 fp.write(_SETUP_TMPL % {'func': getsource(cfg_to_args)})
1109
1110
1111# Taken from the pip project
1112# https://github.com/pypa/pip/blob/master/pip/util.py
1113def ask(message, options):
1114 """Prompt the user with *message*; *options* contains allowed responses."""
1115 while True:
1116 response = input(message)
1117 response = response.strip().lower()
1118 if response not in options:
Éric Araujo3cab2f12011-06-08 04:10:57 +02001119 print('invalid response:', repr(response))
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001120 print('choose one of', ', '.join(repr(o) for o in options))
1121 else:
1122 return response
1123
1124
1125def _parse_record_file(record_file):
1126 distinfo, extra_metadata, installed = ({}, [], [])
1127 with open(record_file, 'r') as rfile:
1128 for path in rfile:
1129 path = path.strip()
1130 if path.endswith('egg-info') and os.path.isfile(path):
1131 distinfo_dir = path.replace('egg-info', 'dist-info')
1132 metadata = path
1133 egginfo = path
1134 elif path.endswith('egg-info') and os.path.isdir(path):
1135 distinfo_dir = path.replace('egg-info', 'dist-info')
1136 egginfo = path
1137 for metadata_file in os.listdir(path):
1138 metadata_fpath = os.path.join(path, metadata_file)
1139 if metadata_file == 'PKG-INFO':
1140 metadata = metadata_fpath
1141 else:
1142 extra_metadata.append(metadata_fpath)
1143 elif 'egg-info' in path and os.path.isfile(path):
1144 # skip extra metadata files
1145 continue
1146 else:
1147 installed.append(path)
1148
1149 distinfo['egginfo'] = egginfo
1150 distinfo['metadata'] = metadata
1151 distinfo['distinfo_dir'] = distinfo_dir
1152 distinfo['installer_path'] = os.path.join(distinfo_dir, 'INSTALLER')
1153 distinfo['metadata_path'] = os.path.join(distinfo_dir, 'METADATA')
1154 distinfo['record_path'] = os.path.join(distinfo_dir, 'RECORD')
1155 distinfo['requested_path'] = os.path.join(distinfo_dir, 'REQUESTED')
1156 installed.extend([distinfo['installer_path'], distinfo['metadata_path']])
1157 distinfo['installed'] = installed
1158 distinfo['extra_metadata'] = extra_metadata
1159 return distinfo
1160
1161
1162def _write_record_file(record_path, installed_files):
1163 with open(record_path, 'w', encoding='utf-8') as f:
1164 writer = csv.writer(f, delimiter=',', lineterminator=os.linesep,
1165 quotechar='"')
1166
1167 for fpath in installed_files:
1168 if fpath.endswith('.pyc') or fpath.endswith('.pyo'):
1169 # do not put size and md5 hash, as in PEP-376
1170 writer.writerow((fpath, '', ''))
1171 else:
1172 hash = hashlib.md5()
1173 with open(fpath, 'rb') as fp:
1174 hash.update(fp.read())
1175 md5sum = hash.hexdigest()
1176 size = os.path.getsize(fpath)
1177 writer.writerow((fpath, md5sum, size))
1178
1179 # add the RECORD file itself
1180 writer.writerow((record_path, '', ''))
1181 return record_path
1182
1183
1184def egginfo_to_distinfo(record_file, installer=_DEFAULT_INSTALLER,
1185 requested=False, remove_egginfo=False):
1186 """Create files and directories required for PEP 376
1187
1188 :param record_file: path to RECORD file as produced by setup.py --record
1189 :param installer: installer name
1190 :param requested: True if not installed as a dependency
1191 :param remove_egginfo: delete egginfo dir?
1192 """
1193 distinfo = _parse_record_file(record_file)
1194 distinfo_dir = distinfo['distinfo_dir']
1195 if os.path.isdir(distinfo_dir) and not os.path.islink(distinfo_dir):
1196 shutil.rmtree(distinfo_dir)
1197 elif os.path.exists(distinfo_dir):
1198 os.unlink(distinfo_dir)
1199
1200 os.makedirs(distinfo_dir)
1201
1202 # copy setuptools extra metadata files
1203 if distinfo['extra_metadata']:
1204 for path in distinfo['extra_metadata']:
1205 shutil.copy2(path, distinfo_dir)
1206 new_path = path.replace('egg-info', 'dist-info')
1207 distinfo['installed'].append(new_path)
1208
1209 metadata_path = distinfo['metadata_path']
1210 logger.info('creating %s', metadata_path)
1211 shutil.copy2(distinfo['metadata'], metadata_path)
1212
1213 installer_path = distinfo['installer_path']
1214 logger.info('creating %s', installer_path)
1215 with open(installer_path, 'w') as f:
1216 f.write(installer)
1217
1218 if requested:
1219 requested_path = distinfo['requested_path']
1220 logger.info('creating %s', requested_path)
Victor Stinner4c9706b2011-05-19 15:52:59 +02001221 open(requested_path, 'wb').close()
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001222 distinfo['installed'].append(requested_path)
1223
1224 record_path = distinfo['record_path']
1225 logger.info('creating %s', record_path)
1226 _write_record_file(record_path, distinfo['installed'])
1227
1228 if remove_egginfo:
1229 egginfo = distinfo['egginfo']
1230 logger.info('removing %s', egginfo)
1231 if os.path.isfile(egginfo):
1232 os.remove(egginfo)
1233 else:
1234 shutil.rmtree(egginfo)
1235
1236
1237def _has_egg_info(srcdir):
1238 if os.path.isdir(srcdir):
1239 for item in os.listdir(srcdir):
1240 full_path = os.path.join(srcdir, item)
1241 if item.endswith('.egg-info') and os.path.isdir(full_path):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001242 logger.debug("Found egg-info directory.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001243 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001244 logger.debug("No egg-info directory found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001245 return False
1246
1247
1248def _has_setuptools_text(setup_py):
1249 return _has_text(setup_py, 'setuptools')
1250
1251
1252def _has_distutils_text(setup_py):
1253 return _has_text(setup_py, 'distutils')
1254
1255
1256def _has_text(setup_py, installer):
1257 installer_pattern = re.compile('import {0}|from {0}'.format(installer))
1258 with open(setup_py, 'r', encoding='utf-8') as setup:
1259 for line in setup:
1260 if re.search(installer_pattern, line):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001261 logger.debug("Found %s text in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001262 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001263 logger.debug("No %s text found in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001264 return False
1265
1266
1267def _has_required_metadata(setup_cfg):
1268 config = RawConfigParser()
1269 config.read([setup_cfg], encoding='utf8')
1270 return (config.has_section('metadata') and
1271 'name' in config.options('metadata') and
1272 'version' in config.options('metadata'))
1273
1274
1275def _has_pkg_info(srcdir):
1276 pkg_info = os.path.join(srcdir, 'PKG-INFO')
1277 has_pkg_info = os.path.isfile(pkg_info)
1278 if has_pkg_info:
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001279 logger.debug("PKG-INFO file found.")
1280 else:
1281 logger.debug("No PKG-INFO file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001282 return has_pkg_info
1283
1284
1285def _has_setup_py(srcdir):
1286 setup_py = os.path.join(srcdir, 'setup.py')
1287 if os.path.isfile(setup_py):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001288 logger.debug('setup.py file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001289 return True
1290 return False
1291
1292
1293def _has_setup_cfg(srcdir):
1294 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1295 if os.path.isfile(setup_cfg):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001296 logger.debug('setup.cfg file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001297 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001298 logger.debug("No setup.cfg file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001299 return False
1300
1301
1302def is_setuptools(path):
1303 """Check if the project is based on setuptools.
1304
1305 :param path: path to source directory containing a setup.py script.
1306
1307 Return True if the project requires setuptools to install, else False.
1308 """
1309 srcdir = os.path.abspath(path)
1310 setup_py = os.path.join(srcdir, 'setup.py')
1311
1312 return _has_setup_py(srcdir) and (_has_egg_info(srcdir) or
1313 _has_setuptools_text(setup_py))
1314
1315
1316def is_distutils(path):
1317 """Check if the project is based on distutils.
1318
1319 :param path: path to source directory containing a setup.py script.
1320
1321 Return True if the project requires distutils to install, else False.
1322 """
1323 srcdir = os.path.abspath(path)
1324 setup_py = os.path.join(srcdir, 'setup.py')
1325
1326 return _has_setup_py(srcdir) and (_has_pkg_info(srcdir) or
1327 _has_distutils_text(setup_py))
1328
1329
1330def is_packaging(path):
1331 """Check if the project is based on packaging
1332
1333 :param path: path to source directory containing a setup.cfg file.
1334
1335 Return True if the project has a valid setup.cfg, else False.
1336 """
1337 srcdir = os.path.abspath(path)
1338 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1339
1340 return _has_setup_cfg(srcdir) and _has_required_metadata(setup_cfg)
1341
1342
1343def get_install_method(path):
1344 """Check if the project is based on packaging, setuptools, or distutils
1345
1346 :param path: path to source directory containing a setup.cfg file,
1347 or setup.py.
1348
1349 Returns a string representing the best install method to use.
1350 """
1351 if is_packaging(path):
1352 return "packaging"
1353 elif is_setuptools(path):
1354 return "setuptools"
1355 elif is_distutils(path):
1356 return "distutils"
1357 else:
1358 raise InstallationException('Cannot detect install method')
1359
1360
1361# XXX to be replaced by shutil.copytree
1362def copy_tree(src, dst, preserve_mode=True, preserve_times=True,
1363 preserve_symlinks=False, update=False, verbose=True,
1364 dry_run=False):
1365 from distutils.file_util import copy_file
1366
1367 if not dry_run and not os.path.isdir(src):
1368 raise PackagingFileError(
1369 "cannot copy tree '%s': not a directory" % src)
1370 try:
1371 names = os.listdir(src)
1372 except os.error as e:
1373 errstr = e[1]
1374 if dry_run:
1375 names = []
1376 else:
1377 raise PackagingFileError(
1378 "error listing files in '%s': %s" % (src, errstr))
1379
1380 if not dry_run:
1381 _mkpath(dst, verbose=verbose)
1382
1383 outputs = []
1384
1385 for n in names:
1386 src_name = os.path.join(src, n)
1387 dst_name = os.path.join(dst, n)
1388
1389 if preserve_symlinks and os.path.islink(src_name):
1390 link_dest = os.readlink(src_name)
1391 if verbose >= 1:
1392 logger.info("linking %s -> %s", dst_name, link_dest)
1393 if not dry_run:
1394 os.symlink(link_dest, dst_name)
1395 outputs.append(dst_name)
1396
1397 elif os.path.isdir(src_name):
1398 outputs.extend(
1399 copy_tree(src_name, dst_name, preserve_mode,
1400 preserve_times, preserve_symlinks, update,
1401 verbose=verbose, dry_run=dry_run))
1402 else:
1403 copy_file(src_name, dst_name, preserve_mode,
1404 preserve_times, update, verbose=verbose,
1405 dry_run=dry_run)
1406 outputs.append(dst_name)
1407
1408 return outputs
1409
1410# cache for by mkpath() -- in addition to cheapening redundant calls,
1411# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
1412_path_created = set()
1413
1414
1415# I don't use os.makedirs because a) it's new to Python 1.5.2, and
1416# b) it blows up if the directory already exists (I want to silently
1417# succeed in that case).
1418def _mkpath(name, mode=0o777, verbose=True, dry_run=False):
1419 # Detect a common bug -- name is None
1420 if not isinstance(name, str):
1421 raise PackagingInternalError(
1422 "mkpath: 'name' must be a string (got %r)" % (name,))
1423
1424 # XXX what's the better way to handle verbosity? print as we create
1425 # each directory in the path (the current behaviour), or only announce
1426 # the creation of the whole path? (quite easy to do the latter since
1427 # we're not using a recursive algorithm)
1428
1429 name = os.path.normpath(name)
1430 created_dirs = []
1431 if os.path.isdir(name) or name == '':
1432 return created_dirs
1433 if os.path.abspath(name) in _path_created:
1434 return created_dirs
1435
1436 head, tail = os.path.split(name)
1437 tails = [tail] # stack of lone dirs to create
1438
1439 while head and tail and not os.path.isdir(head):
1440 head, tail = os.path.split(head)
1441 tails.insert(0, tail) # push next higher dir onto stack
1442
1443 # now 'head' contains the deepest directory that already exists
1444 # (that is, the child of 'head' in 'name' is the highest directory
1445 # that does *not* exist)
1446 for d in tails:
1447 head = os.path.join(head, d)
1448 abs_head = os.path.abspath(head)
1449
1450 if abs_head in _path_created:
1451 continue
1452
1453 if verbose >= 1:
1454 logger.info("creating %s", head)
1455
1456 if not dry_run:
1457 try:
1458 os.mkdir(head, mode)
1459 except OSError as exc:
1460 if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
1461 raise PackagingFileError(
1462 "could not create '%s': %s" % (head, exc.args[-1]))
1463 created_dirs.append(head)
1464
1465 _path_created.add(abs_head)
1466 return created_dirs