blob: f8a805844e3c0a8e231b298295cd1db1426fc189 [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
Éric Araujoa29e4f62011-10-08 04:09:15 +02006import imp
Éric Araujo35a4d012011-06-04 22:24:59 +02007import sys
8import errno
Tarek Ziade1231a4e2011-05-19 13:07:25 +02009import shutil
10import string
Éric Araujo35a4d012011-06-04 22:24:59 +020011import hashlib
Tarek Ziade1231a4e2011-05-19 13:07:25 +020012import posixpath
Tarek Ziade1231a4e2011-05-19 13:07:25 +020013import subprocess
Éric Araujo35a4d012011-06-04 22:24:59 +020014import sysconfig
Tarek Ziade1231a4e2011-05-19 13:07:25 +020015from glob import iglob as std_iglob
16from fnmatch import fnmatchcase
17from inspect import getsource
18from configparser import RawConfigParser
19
20from packaging import logger
21from packaging.errors import (PackagingPlatformError, PackagingFileError,
22 PackagingByteCompileError, PackagingExecError,
23 InstallationException, PackagingInternalError)
24
Éric Araujo95fc53f2011-09-01 05:11:29 +020025__all__ = [
26 # file dependencies
27 'newer', 'newer_group',
28 # helpers for commands (dry-run system)
29 'execute', 'write_file',
30 # spawning programs
31 'find_executable', 'spawn',
32 # path manipulation
33 'convert_path', 'change_root',
34 # 2to3 conversion
35 'Mixin2to3', 'run_2to3',
36 # packaging compatibility helpers
37 'cfg_to_args', 'generate_setup_py',
38 'egginfo_to_distinfo',
39 'get_install_method',
40 # misc
41 'ask', 'check_environ', 'encode_multipart', 'resolve_name',
42 # querying for information TODO move to sysconfig
43 'get_compiler_versions', 'get_platform', 'set_platform',
44 # configuration TODO move to packaging.config
45 'get_pypirc_path', 'read_pypirc', 'generate_pypirc',
46 'strtobool', 'split_multiline',
47]
48
Tarek Ziade1231a4e2011-05-19 13:07:25 +020049_PLATFORM = None
50_DEFAULT_INSTALLER = 'packaging'
51
52
53def newer(source, target):
54 """Tell if the target is newer than the source.
55
56 Returns true if 'source' exists and is more recently modified than
57 'target', or if 'source' exists and 'target' doesn't.
58
59 Returns false if both exist and 'target' is the same age or younger
60 than 'source'. Raise PackagingFileError if 'source' does not exist.
61
62 Note that this test is not very accurate: files created in the same second
63 will have the same "age".
64 """
65 if not os.path.exists(source):
66 raise PackagingFileError("file '%s' does not exist" %
67 os.path.abspath(source))
68 if not os.path.exists(target):
69 return True
70
71 return os.stat(source).st_mtime > os.stat(target).st_mtime
72
73
74def get_platform():
75 """Return a string that identifies the current platform.
76
77 By default, will return the value returned by sysconfig.get_platform(),
78 but it can be changed by calling set_platform().
79 """
80 global _PLATFORM
81 if _PLATFORM is None:
82 _PLATFORM = sysconfig.get_platform()
83 return _PLATFORM
84
85
86def set_platform(identifier):
87 """Set the platform string identifier returned by get_platform().
88
89 Note that this change doesn't impact the value returned by
90 sysconfig.get_platform(); it is local to packaging.
91 """
92 global _PLATFORM
93 _PLATFORM = identifier
94
95
96def convert_path(pathname):
97 """Return 'pathname' as a name that will work on the native filesystem.
98
99 The path is split on '/' and put back together again using the current
100 directory separator. Needed because filenames in the setup script are
101 always supplied in Unix style, and have to be converted to the local
102 convention before we can actually use them in the filesystem. Raises
103 ValueError on non-Unix-ish systems if 'pathname' either starts or
104 ends with a slash.
105 """
106 if os.sep == '/':
107 return pathname
108 if not pathname:
109 return pathname
110 if pathname[0] == '/':
111 raise ValueError("path '%s' cannot be absolute" % pathname)
112 if pathname[-1] == '/':
113 raise ValueError("path '%s' cannot end with '/'" % pathname)
114
115 paths = pathname.split('/')
116 while os.curdir in paths:
117 paths.remove(os.curdir)
118 if not paths:
119 return os.curdir
120 return os.path.join(*paths)
121
122
123def change_root(new_root, pathname):
124 """Return 'pathname' with 'new_root' prepended.
125
126 If 'pathname' is relative, this is equivalent to
127 os.path.join(new_root,pathname). Otherwise, it requires making 'pathname'
128 relative and then joining the two, which is tricky on DOS/Windows.
129 """
130 if os.name == 'posix':
131 if not os.path.isabs(pathname):
132 return os.path.join(new_root, pathname)
133 else:
134 return os.path.join(new_root, pathname[1:])
135
136 elif os.name == 'nt':
137 drive, path = os.path.splitdrive(pathname)
138 if path[0] == '\\':
139 path = path[1:]
140 return os.path.join(new_root, path)
141
142 elif os.name == 'os2':
143 drive, path = os.path.splitdrive(pathname)
144 if path[0] == os.sep:
145 path = path[1:]
146 return os.path.join(new_root, path)
147
148 else:
149 raise PackagingPlatformError("nothing known about "
150 "platform '%s'" % os.name)
151
152_environ_checked = False
153
154
155def check_environ():
156 """Ensure that 'os.environ' has all the environment variables needed.
157
158 We guarantee that users can use in config files, command-line options,
159 etc. Currently this includes:
160 HOME - user's home directory (Unix only)
161 PLAT - description of the current platform, including hardware
162 and OS (see 'get_platform()')
163 """
164 global _environ_checked
165 if _environ_checked:
166 return
167
168 if os.name == 'posix' and 'HOME' not in os.environ:
169 import pwd
170 os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]
171
172 if 'PLAT' not in os.environ:
173 os.environ['PLAT'] = sysconfig.get_platform()
174
175 _environ_checked = True
176
177
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200178# Needed by 'split_quoted()'
179_wordchars_re = _squote_re = _dquote_re = None
180
181
182def _init_regex():
183 global _wordchars_re, _squote_re, _dquote_re
184 _wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)
185 _squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")
186 _dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
187
188
Éric Araujo95fc53f2011-09-01 05:11:29 +0200189# TODO replace with shlex.split after testing
190
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200191def 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
Éric Araujoa29e4f62011-10-08 04:09:15 +0200300 or .pyo files in a __pycache__ subdirectory.
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200301
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
Éric Araujo7724a6c2011-09-17 03:31:51 +0200330 if sys.dont_write_bytecode:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200331 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
Éric Araujo7724a6c2011-09-17 03:31:51 +0200350 # XXX use something better than mkstemp
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200351 script_fd, script_name = mkstemp(".py")
Éric Araujo7724a6c2011-09-17 03:31:51 +0200352 os.close(script_fd)
353 script_fd = None
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200354 logger.info("writing byte-compilation script '%s'", script_name)
355 if not dry_run:
356 if script_fd is not None:
Victor Stinner9cf6d132011-05-19 21:42:47 +0200357 script = os.fdopen(script_fd, "w", encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200358 else:
Victor Stinner9cf6d132011-05-19 21:42:47 +0200359 script = open(script_name, "w", encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200360
Victor Stinner21a9c742011-05-19 15:51:27 +0200361 with script:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200362 script.write("""\
363from packaging.util import byte_compile
364files = [
365""")
366
367 # XXX would be nice to write absolute filenames, just for
368 # safety's sake (script should be more robust in the face of
369 # chdir'ing before running it). But this requires abspath'ing
370 # 'prefix' as well, and that breaks the hack in build_lib's
371 # 'byte_compile()' method that carefully tacks on a trailing
372 # slash (os.sep really) to make sure the prefix here is "just
373 # right". This whole prefix business is rather delicate -- the
374 # problem is that it's really a directory, but I'm treating it
375 # as a dumb string, so trailing slashes and so forth matter.
376
377 #py_files = map(os.path.abspath, py_files)
378 #if prefix:
379 # prefix = os.path.abspath(prefix)
380
381 script.write(",\n".join(map(repr, py_files)) + "]\n")
382 script.write("""
383byte_compile(files, optimize=%r, force=%r,
384 prefix=%r, base_dir=%r,
385 verbose=%r, dry_run=False,
386 direct=True)
387""" % (optimize, force, prefix, base_dir, verbose))
388
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200389 cmd = [sys.executable, script_name]
390 if optimize == 1:
391 cmd.insert(1, "-O")
392 elif optimize == 2:
393 cmd.insert(1, "-OO")
394
Éric Araujo088025f2011-06-04 18:45:40 +0200395 env = os.environ.copy()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200396 env['PYTHONPATH'] = os.path.pathsep.join(sys.path)
397 try:
398 spawn(cmd, env=env)
399 finally:
400 execute(os.remove, (script_name,), "removing %s" % script_name,
401 dry_run=dry_run)
402
403 # "Direct" byte-compilation: use the py_compile module to compile
404 # right here, right now. Note that the script generated in indirect
405 # mode simply calls 'byte_compile()' in direct mode, a weird sort of
406 # cross-process recursion. Hey, it works!
407 else:
408 from py_compile import compile
409
410 for file in py_files:
411 if file[-3:] != ".py":
412 # This lets us be lazy and not filter filenames in
413 # the "install_lib" command.
414 continue
415
416 # Terminology from the py_compile module:
417 # cfile - byte-compiled file
418 # dfile - purported source filename (same as 'file' by default)
Éric Araujoa29e4f62011-10-08 04:09:15 +0200419 if optimize >= 0:
420 cfile = imp.cache_from_source(file, debug_override=not optimize)
421 else:
422 cfile = imp.cache_from_source(file)
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200423 dfile = file
424 if prefix:
425 if file[:len(prefix)] != prefix:
426 raise ValueError("invalid prefix: filename %r doesn't "
427 "start with %r" % (file, prefix))
428 dfile = dfile[len(prefix):]
429 if base_dir:
430 dfile = os.path.join(base_dir, dfile)
431
432 cfile_base = os.path.basename(cfile)
433 if direct:
434 if force or newer(file, cfile):
435 logger.info("byte-compiling %s to %s", file, cfile_base)
436 if not dry_run:
437 compile(file, cfile, dfile)
438 else:
439 logger.debug("skipping byte-compilation of %s to %s",
440 file, cfile_base)
441
442
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200443_RE_VERSION = re.compile('(\d+\.\d+(\.\d+)*)')
444_MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld '
445 'PROJECT:ld64-((\d+)(\.\d+)*)')
446
447
448def _find_ld_version():
449 """Find the ld version. The version scheme differs under Mac OS X."""
450 if sys.platform == 'darwin':
451 return _find_exe_version('ld -v', _MAC_OS_X_LD_VERSION)
452 else:
453 return _find_exe_version('ld -v')
454
455
456def _find_exe_version(cmd, pattern=_RE_VERSION):
457 """Find the version of an executable by running `cmd` in the shell.
458
459 `pattern` is a compiled regular expression. If not provided, defaults
460 to _RE_VERSION. If the command is not found, or the output does not
461 match the mattern, returns None.
462 """
463 from subprocess import Popen, PIPE
464 executable = cmd.split()[0]
465 if find_executable(executable) is None:
466 return None
467 pipe = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
468 try:
Victor Stinner9904b222011-05-21 02:20:36 +0200469 stdout, stderr = pipe.communicate()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200470 finally:
471 pipe.stdout.close()
472 pipe.stderr.close()
473 # some commands like ld under MacOS X, will give the
474 # output in the stderr, rather than stdout.
475 if stdout != '':
476 out_string = stdout
477 else:
478 out_string = stderr
479
480 result = pattern.search(out_string)
481 if result is None:
482 return None
483 return result.group(1)
484
485
486def get_compiler_versions():
487 """Return a tuple providing the versions of gcc, ld and dllwrap
488
489 For each command, if a command is not found, None is returned.
490 Otherwise a string with the version is returned.
491 """
492 gcc = _find_exe_version('gcc -dumpversion')
493 ld = _find_ld_version()
494 dllwrap = _find_exe_version('dllwrap --version')
495 return gcc, ld, dllwrap
496
497
498def newer_group(sources, target, missing='error'):
499 """Return true if 'target' is out-of-date with respect to any file
500 listed in 'sources'.
501
502 In other words, if 'target' exists and is newer
503 than every file in 'sources', return false; otherwise return true.
504 'missing' controls what we do when a source file is missing; the
505 default ("error") is to blow up with an OSError from inside 'stat()';
506 if it is "ignore", we silently drop any missing source files; if it is
507 "newer", any missing source files make us assume that 'target' is
508 out-of-date (this is handy in "dry-run" mode: it'll make you pretend to
509 carry out commands that wouldn't work because inputs are missing, but
510 that doesn't matter because you're not actually going to run the
511 commands).
512 """
513 # If the target doesn't even exist, then it's definitely out-of-date.
514 if not os.path.exists(target):
515 return True
516
517 # Otherwise we have to find out the hard way: if *any* source file
518 # is more recent than 'target', then 'target' is out-of-date and
519 # we can immediately return true. If we fall through to the end
520 # of the loop, then 'target' is up-to-date and we return false.
521 target_mtime = os.stat(target).st_mtime
522
523 for source in sources:
524 if not os.path.exists(source):
525 if missing == 'error': # blow up when we stat() the file
526 pass
527 elif missing == 'ignore': # missing source dropped from
528 continue # target's dependency list
529 elif missing == 'newer': # missing source means target is
530 return True # out-of-date
531
532 if os.stat(source).st_mtime > target_mtime:
533 return True
534
535 return False
536
537
538def write_file(filename, contents):
539 """Create *filename* and write *contents* to it.
540
541 *contents* is a sequence of strings without line terminators.
Éric Araujo95fc53f2011-09-01 05:11:29 +0200542
543 This functions is not intended to replace the usual with open + write
544 idiom in all cases, only with Command.execute, which runs depending on
545 the dry_run argument and also logs its arguments).
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200546 """
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):
Éric Araujo95fc53f2011-09-01 05:11:29 +0200565 # XXX use os.path
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200566 path = path.split(os.sep)
567 root = root.split(os.sep)
568 if len(root) > len(path):
569 return False
570 for pos, part in enumerate(root):
571 if path[pos] != part:
572 return False
573 return True
574
575
576def _package_name(root_path, path):
577 # Return a dotted package name, given a subpath
578 if not _under(path, root_path):
579 raise ValueError('"%s" is not a subpath of "%s"' % (path, root_path))
580 return path[len(root_path) + 1:].replace(os.sep, '.')
581
582
583def find_packages(paths=(os.curdir,), exclude=()):
584 """Return a list all Python packages found recursively within
585 directories 'paths'
586
587 'paths' should be supplied as a sequence of "cross-platform"
588 (i.e. URL-style) path; it will be converted to the appropriate local
589 path syntax.
590
591 'exclude' is a sequence of package names to exclude; '*' can be used as
592 a wildcard in the names, such that 'foo.*' will exclude all subpackages
593 of 'foo' (but not 'foo' itself).
594 """
595 packages = []
596 discarded = []
597
598 def _discarded(path):
599 for discard in discarded:
600 if _under(path, discard):
601 return True
602 return False
603
604 for path in paths:
605 path = convert_path(path)
606 for root, dirs, files in os.walk(path):
607 for dir_ in dirs:
608 fullpath = os.path.join(root, dir_)
609 if _discarded(fullpath):
610 continue
611 # we work only with Python packages
612 if not _is_package(fullpath):
613 discarded.append(fullpath)
614 continue
615 # see if it's excluded
616 excluded = False
617 package_name = _package_name(path, fullpath)
618 for pattern in exclude:
619 if fnmatchcase(package_name, pattern):
620 excluded = True
621 break
622 if excluded:
623 continue
624
625 # adding it to the list
626 packages.append(package_name)
627 return packages
628
629
630def resolve_name(name):
631 """Resolve a name like ``module.object`` to an object and return it.
632
633 Raise ImportError if the module or name is not found.
634 """
635 parts = name.split('.')
636 cursor = len(parts)
637 module_name = parts[:cursor]
638
639 while cursor > 0:
640 try:
641 ret = __import__('.'.join(module_name))
642 break
643 except ImportError:
644 if cursor == 0:
645 raise
646 cursor -= 1
647 module_name = parts[:cursor]
648 ret = ''
649
650 for part in parts[1:]:
651 try:
652 ret = getattr(ret, part)
653 except AttributeError as exc:
654 raise ImportError(exc)
655
656 return ret
657
658
659def splitext(path):
660 """Like os.path.splitext, but take off .tar too"""
661 base, ext = posixpath.splitext(path)
662 if base.lower().endswith('.tar'):
663 ext = base[-4:] + ext
664 base = base[:-4]
665 return base, ext
666
667
Ned Deilyfceb4122011-06-28 20:04:24 -0700668if sys.platform == 'darwin':
669 _cfg_target = None
670 _cfg_target_split = None
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200671
Éric Araujo95fc53f2011-09-01 05:11:29 +0200672
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200673def spawn(cmd, search_path=True, verbose=0, dry_run=False, env=None):
674 """Run another program specified as a command list 'cmd' in a new process.
675
676 'cmd' is just the argument list for the new process, ie.
677 cmd[0] is the program to run and cmd[1:] are the rest of its arguments.
678 There is no way to run a program with a name different from that of its
679 executable.
680
681 If 'search_path' is true (the default), the system's executable
682 search path will be used to find the program; otherwise, cmd[0]
683 must be the exact path to the executable. If 'dry_run' is true,
684 the command will not actually be run.
685
686 If 'env' is given, it's a environment dictionary used for the execution
687 environment.
688
689 Raise PackagingExecError if running the program fails in any way; just
690 return on success.
691 """
Éric Araujo62806062011-06-11 09:46:07 +0200692 logger.debug('spawn: running %r', cmd)
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200693 if dry_run:
Éric Araujo29f62972011-08-04 17:17:07 +0200694 logger.debug('dry run, no process actually spawned')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200695 return
Ned Deilyfceb4122011-06-28 20:04:24 -0700696 if sys.platform == 'darwin':
697 global _cfg_target, _cfg_target_split
698 if _cfg_target is None:
699 _cfg_target = sysconfig.get_config_var(
700 'MACOSX_DEPLOYMENT_TARGET') or ''
701 if _cfg_target:
702 _cfg_target_split = [int(x) for x in _cfg_target.split('.')]
703 if _cfg_target:
704 # ensure that the deployment target of build process is not less
705 # than that used when the interpreter was built. This ensures
706 # extension modules are built with correct compatibility values
707 env = env or os.environ
708 cur_target = env.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target)
709 if _cfg_target_split > [int(x) for x in cur_target.split('.')]:
710 my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: '
711 'now "%s" but "%s" during configure'
712 % (cur_target, _cfg_target))
713 raise PackagingPlatformError(my_msg)
714 env = dict(env, MACOSX_DEPLOYMENT_TARGET=cur_target)
715
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200716 exit_status = subprocess.call(cmd, env=env)
717 if exit_status != 0:
Éric Araujo62806062011-06-11 09:46:07 +0200718 msg = "command %r failed with exit status %d"
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200719 raise PackagingExecError(msg % (cmd, exit_status))
720
721
722def find_executable(executable, path=None):
723 """Try to find 'executable' in the directories listed in 'path'.
724
725 *path* is a string listing directories separated by 'os.pathsep' and
726 defaults to os.environ['PATH']. Returns the complete filename or None
727 if not found.
728 """
729 if path is None:
730 path = os.environ['PATH']
731 paths = path.split(os.pathsep)
732 base, ext = os.path.splitext(executable)
733
734 if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
735 executable = executable + '.exe'
736
737 if not os.path.isfile(executable):
738 for p in paths:
739 f = os.path.join(p, executable)
740 if os.path.isfile(f):
741 # the file exists, we have a shot at spawn working
742 return f
743 return None
744 else:
745 return executable
746
747
748DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
749DEFAULT_REALM = 'pypi'
750DEFAULT_PYPIRC = """\
751[distutils]
752index-servers =
753 pypi
754
755[pypi]
756username:%s
757password:%s
758"""
759
760
761def get_pypirc_path():
762 """Return path to pypirc config file."""
763 return os.path.join(os.path.expanduser('~'), '.pypirc')
764
765
766def generate_pypirc(username, password):
767 """Create a default .pypirc file."""
768 rc = get_pypirc_path()
769 with open(rc, 'w') as f:
770 f.write(DEFAULT_PYPIRC % (username, password))
771 try:
772 os.chmod(rc, 0o600)
773 except OSError:
774 # should do something better here
775 pass
776
777
778def read_pypirc(repository=DEFAULT_REPOSITORY, realm=DEFAULT_REALM):
779 """Read the .pypirc file."""
780 rc = get_pypirc_path()
781 if os.path.exists(rc):
782 config = RawConfigParser()
783 config.read(rc)
784 sections = config.sections()
785 if 'distutils' in sections:
786 # let's get the list of servers
787 index_servers = config.get('distutils', 'index-servers')
788 _servers = [server.strip() for server in
789 index_servers.split('\n')
790 if server.strip() != '']
791 if _servers == []:
792 # nothing set, let's try to get the default pypi
793 if 'pypi' in sections:
794 _servers = ['pypi']
795 else:
796 # the file is not properly defined, returning
797 # an empty dict
798 return {}
799 for server in _servers:
800 current = {'server': server}
801 current['username'] = config.get(server, 'username')
802
803 # optional params
804 for key, default in (('repository', DEFAULT_REPOSITORY),
805 ('realm', DEFAULT_REALM),
806 ('password', None)):
807 if config.has_option(server, key):
808 current[key] = config.get(server, key)
809 else:
810 current[key] = default
811 if (current['server'] == repository or
812 current['repository'] == repository):
813 return current
814 elif 'server-login' in sections:
815 # old format
816 server = 'server-login'
817 if config.has_option(server, 'repository'):
818 repository = config.get(server, 'repository')
819 else:
820 repository = DEFAULT_REPOSITORY
821
822 return {'username': config.get(server, 'username'),
823 'password': config.get(server, 'password'),
824 'repository': repository,
825 'server': server,
826 'realm': DEFAULT_REALM}
827
828 return {}
829
830
831# utility functions for 2to3 support
832
833def run_2to3(files, doctests_only=False, fixer_names=None,
834 options=None, explicit=None):
835 """ Wrapper function around the refactor() class which
836 performs the conversions on a list of python files.
837 Invoke 2to3 on a list of Python files. The files should all come
838 from the build area, as the modification is done in-place."""
839
840 #if not files:
841 # return
842
843 # Make this class local, to delay import of 2to3
844 from lib2to3.refactor import get_fixers_from_package, RefactoringTool
845 fixers = []
846 fixers = get_fixers_from_package('lib2to3.fixes')
847
848 if fixer_names:
849 for fixername in fixer_names:
850 fixers.extend(fixer for fixer in
851 get_fixers_from_package(fixername))
852 r = RefactoringTool(fixers, options=options)
853 r.refactor(files, write=True, doctests_only=doctests_only)
854
855
856class Mixin2to3:
857 """ Wrapper class for commands that run 2to3.
858 To configure 2to3, setup scripts may either change
859 the class variables, or inherit from this class
860 to override how 2to3 is invoked.
861 """
862 # provide list of fixers to run.
863 # defaults to all from lib2to3.fixers
864 fixer_names = None
865
866 # options dictionary
867 options = None
868
869 # list of fixers to invoke even though they are marked as explicit
870 explicit = None
871
872 def run_2to3(self, files, doctests_only=False):
873 """ Issues a call to util.run_2to3. """
874 return run_2to3(files, doctests_only, self.fixer_names,
875 self.options, self.explicit)
876
877RICH_GLOB = re.compile(r'\{([^}]*)\}')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200878_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200879_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
880
881
882def iglob(path_glob):
883 """Extended globbing function that supports ** and {opt1,opt2,opt3}."""
884 if _CHECK_RECURSIVE_GLOB.search(path_glob):
885 msg = """invalid glob %r: recursive glob "**" must be used alone"""
886 raise ValueError(msg % path_glob)
887 if _CHECK_MISMATCH_SET.search(path_glob):
888 msg = """invalid glob %r: mismatching set marker '{' or '}'"""
889 raise ValueError(msg % path_glob)
890 return _iglob(path_glob)
891
892
893def _iglob(path_glob):
894 rich_path_glob = RICH_GLOB.split(path_glob, 1)
895 if len(rich_path_glob) > 1:
896 assert len(rich_path_glob) == 3, rich_path_glob
897 prefix, set, suffix = rich_path_glob
898 for item in set.split(','):
899 for path in _iglob(''.join((prefix, item, suffix))):
900 yield path
901 else:
902 if '**' not in path_glob:
903 for item in std_iglob(path_glob):
904 yield item
905 else:
906 prefix, radical = path_glob.split('**', 1)
907 if prefix == '':
908 prefix = '.'
909 if radical == '':
910 radical = '*'
911 else:
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200912 # we support both
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200913 radical = radical.lstrip('/')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200914 radical = radical.lstrip('\\')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200915 for path, dir, files in os.walk(prefix):
916 path = os.path.normpath(path)
917 for file in _iglob(os.path.join(path, radical)):
918 yield file
919
920
921def cfg_to_args(path='setup.cfg'):
922 """Compatibility helper to use setup.cfg in setup.py.
923
924 This functions uses an existing setup.cfg to generate a dictionnary of
925 keywords that can be used by distutils.core.setup(**kwargs). It is used
926 by generate_setup_py.
927
928 *file* is the path to the setup.cfg file. If it doesn't exist,
929 PackagingFileError is raised.
930 """
931 # We need to declare the following constants here so that it's easier to
932 # generate the setup.py afterwards, using inspect.getsource.
933
934 # XXX ** == needs testing
935 D1_D2_SETUP_ARGS = {"name": ("metadata",),
936 "version": ("metadata",),
937 "author": ("metadata",),
938 "author_email": ("metadata",),
939 "maintainer": ("metadata",),
940 "maintainer_email": ("metadata",),
941 "url": ("metadata", "home_page"),
942 "description": ("metadata", "summary"),
943 "long_description": ("metadata", "description"),
944 "download-url": ("metadata",),
945 "classifiers": ("metadata", "classifier"),
946 "platforms": ("metadata", "platform"), # **
947 "license": ("metadata",),
948 "requires": ("metadata", "requires_dist"),
949 "provides": ("metadata", "provides_dist"), # **
950 "obsoletes": ("metadata", "obsoletes_dist"), # **
Éric Araujo36050302011-06-10 23:52:26 +0200951 "package_dir": ("files", 'packages_root'),
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200952 "packages": ("files",),
953 "scripts": ("files",),
954 "py_modules": ("files", "modules"), # **
955 }
956
957 MULTI_FIELDS = ("classifiers",
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200958 "platforms",
Éric Araujo36050302011-06-10 23:52:26 +0200959 "requires",
960 "provides",
961 "obsoletes",
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200962 "packages",
Éric Araujo36050302011-06-10 23:52:26 +0200963 "scripts",
964 "py_modules")
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200965
966 def has_get_option(config, section, option):
967 if config.has_option(section, option):
968 return config.get(section, option)
969 elif config.has_option(section, option.replace('_', '-')):
970 return config.get(section, option.replace('_', '-'))
971 else:
972 return False
973
974 # The real code starts here
975 config = RawConfigParser()
Éric Araujo36050302011-06-10 23:52:26 +0200976 if not os.path.exists(path):
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200977 raise PackagingFileError("file '%s' does not exist" %
Éric Araujo36050302011-06-10 23:52:26 +0200978 os.path.abspath(path))
Éric Araujo8d233f22011-06-12 23:02:57 +0200979 config.read(path, encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200980
981 kwargs = {}
982 for arg in D1_D2_SETUP_ARGS:
983 if len(D1_D2_SETUP_ARGS[arg]) == 2:
984 # The distutils field name is different than packaging's
985 section, option = D1_D2_SETUP_ARGS[arg]
986
987 else:
988 # The distutils field name is the same thant packaging's
989 section = D1_D2_SETUP_ARGS[arg][0]
990 option = arg
991
992 in_cfg_value = has_get_option(config, section, option)
993 if not in_cfg_value:
994 # There is no such option in the setup.cfg
Éric Araujo36050302011-06-10 23:52:26 +0200995 if arg == 'long_description':
996 filenames = has_get_option(config, section, 'description-file')
997 if filenames:
998 filenames = split_multiline(filenames)
999 in_cfg_value = []
1000 for filename in filenames:
1001 with open(filename) as fp:
1002 in_cfg_value.append(fp.read())
1003 in_cfg_value = '\n\n'.join(in_cfg_value)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001004 else:
1005 continue
1006
Éric Araujo36050302011-06-10 23:52:26 +02001007 if arg == 'package_dir' and in_cfg_value:
1008 in_cfg_value = {'': in_cfg_value}
1009
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001010 if arg in MULTI_FIELDS:
1011 # support multiline options
Éric Araujo36050302011-06-10 23:52:26 +02001012 in_cfg_value = split_multiline(in_cfg_value)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001013
1014 kwargs[arg] = in_cfg_value
1015
1016 return kwargs
1017
1018
1019_SETUP_TMPL = """\
1020# This script was automatically generated by packaging
1021import os
1022from distutils.core import setup
1023from ConfigParser import RawConfigParser
1024
1025%(func)s
1026
1027setup(**cfg_to_args())
1028"""
1029
1030
1031def generate_setup_py():
1032 """Generate a distutils compatible setup.py using an existing setup.cfg.
1033
1034 Raises a PackagingFileError when a setup.py already exists.
1035 """
1036 if os.path.exists("setup.py"):
Tarek Ziade721ccd02011-06-02 12:00:44 +02001037 raise PackagingFileError("a setup.py file already exists")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001038
Victor Stinner9cf6d132011-05-19 21:42:47 +02001039 with open("setup.py", "w", encoding='utf-8') as fp:
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001040 fp.write(_SETUP_TMPL % {'func': getsource(cfg_to_args)})
1041
1042
1043# Taken from the pip project
1044# https://github.com/pypa/pip/blob/master/pip/util.py
1045def ask(message, options):
1046 """Prompt the user with *message*; *options* contains allowed responses."""
1047 while True:
1048 response = input(message)
1049 response = response.strip().lower()
1050 if response not in options:
Éric Araujo3cab2f12011-06-08 04:10:57 +02001051 print('invalid response:', repr(response))
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001052 print('choose one of', ', '.join(repr(o) for o in options))
1053 else:
1054 return response
1055
1056
1057def _parse_record_file(record_file):
1058 distinfo, extra_metadata, installed = ({}, [], [])
1059 with open(record_file, 'r') as rfile:
1060 for path in rfile:
1061 path = path.strip()
1062 if path.endswith('egg-info') and os.path.isfile(path):
1063 distinfo_dir = path.replace('egg-info', 'dist-info')
1064 metadata = path
1065 egginfo = path
1066 elif path.endswith('egg-info') and os.path.isdir(path):
1067 distinfo_dir = path.replace('egg-info', 'dist-info')
1068 egginfo = path
1069 for metadata_file in os.listdir(path):
1070 metadata_fpath = os.path.join(path, metadata_file)
1071 if metadata_file == 'PKG-INFO':
1072 metadata = metadata_fpath
1073 else:
1074 extra_metadata.append(metadata_fpath)
1075 elif 'egg-info' in path and os.path.isfile(path):
1076 # skip extra metadata files
1077 continue
1078 else:
1079 installed.append(path)
1080
1081 distinfo['egginfo'] = egginfo
1082 distinfo['metadata'] = metadata
1083 distinfo['distinfo_dir'] = distinfo_dir
1084 distinfo['installer_path'] = os.path.join(distinfo_dir, 'INSTALLER')
1085 distinfo['metadata_path'] = os.path.join(distinfo_dir, 'METADATA')
1086 distinfo['record_path'] = os.path.join(distinfo_dir, 'RECORD')
1087 distinfo['requested_path'] = os.path.join(distinfo_dir, 'REQUESTED')
1088 installed.extend([distinfo['installer_path'], distinfo['metadata_path']])
1089 distinfo['installed'] = installed
1090 distinfo['extra_metadata'] = extra_metadata
1091 return distinfo
1092
1093
1094def _write_record_file(record_path, installed_files):
1095 with open(record_path, 'w', encoding='utf-8') as f:
1096 writer = csv.writer(f, delimiter=',', lineterminator=os.linesep,
1097 quotechar='"')
1098
1099 for fpath in installed_files:
1100 if fpath.endswith('.pyc') or fpath.endswith('.pyo'):
1101 # do not put size and md5 hash, as in PEP-376
1102 writer.writerow((fpath, '', ''))
1103 else:
1104 hash = hashlib.md5()
1105 with open(fpath, 'rb') as fp:
1106 hash.update(fp.read())
1107 md5sum = hash.hexdigest()
1108 size = os.path.getsize(fpath)
1109 writer.writerow((fpath, md5sum, size))
1110
1111 # add the RECORD file itself
1112 writer.writerow((record_path, '', ''))
1113 return record_path
1114
1115
1116def egginfo_to_distinfo(record_file, installer=_DEFAULT_INSTALLER,
1117 requested=False, remove_egginfo=False):
1118 """Create files and directories required for PEP 376
1119
1120 :param record_file: path to RECORD file as produced by setup.py --record
1121 :param installer: installer name
1122 :param requested: True if not installed as a dependency
1123 :param remove_egginfo: delete egginfo dir?
1124 """
1125 distinfo = _parse_record_file(record_file)
1126 distinfo_dir = distinfo['distinfo_dir']
1127 if os.path.isdir(distinfo_dir) and not os.path.islink(distinfo_dir):
1128 shutil.rmtree(distinfo_dir)
1129 elif os.path.exists(distinfo_dir):
1130 os.unlink(distinfo_dir)
1131
1132 os.makedirs(distinfo_dir)
1133
1134 # copy setuptools extra metadata files
1135 if distinfo['extra_metadata']:
1136 for path in distinfo['extra_metadata']:
1137 shutil.copy2(path, distinfo_dir)
1138 new_path = path.replace('egg-info', 'dist-info')
1139 distinfo['installed'].append(new_path)
1140
1141 metadata_path = distinfo['metadata_path']
1142 logger.info('creating %s', metadata_path)
1143 shutil.copy2(distinfo['metadata'], metadata_path)
1144
1145 installer_path = distinfo['installer_path']
1146 logger.info('creating %s', installer_path)
1147 with open(installer_path, 'w') as f:
1148 f.write(installer)
1149
1150 if requested:
1151 requested_path = distinfo['requested_path']
1152 logger.info('creating %s', requested_path)
Victor Stinner4c9706b2011-05-19 15:52:59 +02001153 open(requested_path, 'wb').close()
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001154 distinfo['installed'].append(requested_path)
1155
1156 record_path = distinfo['record_path']
1157 logger.info('creating %s', record_path)
1158 _write_record_file(record_path, distinfo['installed'])
1159
1160 if remove_egginfo:
1161 egginfo = distinfo['egginfo']
1162 logger.info('removing %s', egginfo)
1163 if os.path.isfile(egginfo):
1164 os.remove(egginfo)
1165 else:
1166 shutil.rmtree(egginfo)
1167
1168
1169def _has_egg_info(srcdir):
1170 if os.path.isdir(srcdir):
1171 for item in os.listdir(srcdir):
1172 full_path = os.path.join(srcdir, item)
1173 if item.endswith('.egg-info') and os.path.isdir(full_path):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001174 logger.debug("Found egg-info directory.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001175 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001176 logger.debug("No egg-info directory found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001177 return False
1178
1179
1180def _has_setuptools_text(setup_py):
1181 return _has_text(setup_py, 'setuptools')
1182
1183
1184def _has_distutils_text(setup_py):
1185 return _has_text(setup_py, 'distutils')
1186
1187
1188def _has_text(setup_py, installer):
1189 installer_pattern = re.compile('import {0}|from {0}'.format(installer))
1190 with open(setup_py, 'r', encoding='utf-8') as setup:
1191 for line in setup:
1192 if re.search(installer_pattern, line):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001193 logger.debug("Found %s text in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001194 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001195 logger.debug("No %s text found in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001196 return False
1197
1198
1199def _has_required_metadata(setup_cfg):
1200 config = RawConfigParser()
1201 config.read([setup_cfg], encoding='utf8')
1202 return (config.has_section('metadata') and
1203 'name' in config.options('metadata') and
1204 'version' in config.options('metadata'))
1205
1206
1207def _has_pkg_info(srcdir):
1208 pkg_info = os.path.join(srcdir, 'PKG-INFO')
1209 has_pkg_info = os.path.isfile(pkg_info)
1210 if has_pkg_info:
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001211 logger.debug("PKG-INFO file found.")
1212 else:
1213 logger.debug("No PKG-INFO file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001214 return has_pkg_info
1215
1216
1217def _has_setup_py(srcdir):
1218 setup_py = os.path.join(srcdir, 'setup.py')
1219 if os.path.isfile(setup_py):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001220 logger.debug('setup.py file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001221 return True
1222 return False
1223
1224
1225def _has_setup_cfg(srcdir):
1226 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1227 if os.path.isfile(setup_cfg):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001228 logger.debug('setup.cfg file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001229 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001230 logger.debug("No setup.cfg file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001231 return False
1232
1233
1234def is_setuptools(path):
1235 """Check if the project is based on setuptools.
1236
1237 :param path: path to source directory containing a setup.py script.
1238
1239 Return True if the project requires setuptools to install, else False.
1240 """
1241 srcdir = os.path.abspath(path)
1242 setup_py = os.path.join(srcdir, 'setup.py')
1243
1244 return _has_setup_py(srcdir) and (_has_egg_info(srcdir) or
1245 _has_setuptools_text(setup_py))
1246
1247
1248def is_distutils(path):
1249 """Check if the project is based on distutils.
1250
1251 :param path: path to source directory containing a setup.py script.
1252
1253 Return True if the project requires distutils to install, else False.
1254 """
1255 srcdir = os.path.abspath(path)
1256 setup_py = os.path.join(srcdir, 'setup.py')
1257
1258 return _has_setup_py(srcdir) and (_has_pkg_info(srcdir) or
1259 _has_distutils_text(setup_py))
1260
1261
1262def is_packaging(path):
1263 """Check if the project is based on packaging
1264
1265 :param path: path to source directory containing a setup.cfg file.
1266
1267 Return True if the project has a valid setup.cfg, else False.
1268 """
1269 srcdir = os.path.abspath(path)
1270 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1271
1272 return _has_setup_cfg(srcdir) and _has_required_metadata(setup_cfg)
1273
1274
1275def get_install_method(path):
1276 """Check if the project is based on packaging, setuptools, or distutils
1277
1278 :param path: path to source directory containing a setup.cfg file,
1279 or setup.py.
1280
1281 Returns a string representing the best install method to use.
1282 """
1283 if is_packaging(path):
1284 return "packaging"
1285 elif is_setuptools(path):
1286 return "setuptools"
1287 elif is_distutils(path):
1288 return "distutils"
1289 else:
1290 raise InstallationException('Cannot detect install method')
1291
1292
1293# XXX to be replaced by shutil.copytree
1294def copy_tree(src, dst, preserve_mode=True, preserve_times=True,
1295 preserve_symlinks=False, update=False, verbose=True,
1296 dry_run=False):
1297 from distutils.file_util import copy_file
1298
1299 if not dry_run and not os.path.isdir(src):
1300 raise PackagingFileError(
1301 "cannot copy tree '%s': not a directory" % src)
1302 try:
1303 names = os.listdir(src)
1304 except os.error as e:
1305 errstr = e[1]
1306 if dry_run:
1307 names = []
1308 else:
1309 raise PackagingFileError(
1310 "error listing files in '%s': %s" % (src, errstr))
1311
1312 if not dry_run:
1313 _mkpath(dst, verbose=verbose)
1314
1315 outputs = []
1316
1317 for n in names:
1318 src_name = os.path.join(src, n)
1319 dst_name = os.path.join(dst, n)
1320
1321 if preserve_symlinks and os.path.islink(src_name):
1322 link_dest = os.readlink(src_name)
1323 if verbose >= 1:
1324 logger.info("linking %s -> %s", dst_name, link_dest)
1325 if not dry_run:
1326 os.symlink(link_dest, dst_name)
1327 outputs.append(dst_name)
1328
1329 elif os.path.isdir(src_name):
1330 outputs.extend(
1331 copy_tree(src_name, dst_name, preserve_mode,
1332 preserve_times, preserve_symlinks, update,
1333 verbose=verbose, dry_run=dry_run))
1334 else:
1335 copy_file(src_name, dst_name, preserve_mode,
1336 preserve_times, update, verbose=verbose,
1337 dry_run=dry_run)
1338 outputs.append(dst_name)
1339
1340 return outputs
1341
1342# cache for by mkpath() -- in addition to cheapening redundant calls,
1343# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
1344_path_created = set()
1345
1346
1347# I don't use os.makedirs because a) it's new to Python 1.5.2, and
1348# b) it blows up if the directory already exists (I want to silently
1349# succeed in that case).
1350def _mkpath(name, mode=0o777, verbose=True, dry_run=False):
1351 # Detect a common bug -- name is None
1352 if not isinstance(name, str):
1353 raise PackagingInternalError(
1354 "mkpath: 'name' must be a string (got %r)" % (name,))
1355
1356 # XXX what's the better way to handle verbosity? print as we create
1357 # each directory in the path (the current behaviour), or only announce
1358 # the creation of the whole path? (quite easy to do the latter since
1359 # we're not using a recursive algorithm)
1360
1361 name = os.path.normpath(name)
1362 created_dirs = []
1363 if os.path.isdir(name) or name == '':
1364 return created_dirs
1365 if os.path.abspath(name) in _path_created:
1366 return created_dirs
1367
1368 head, tail = os.path.split(name)
1369 tails = [tail] # stack of lone dirs to create
1370
1371 while head and tail and not os.path.isdir(head):
1372 head, tail = os.path.split(head)
1373 tails.insert(0, tail) # push next higher dir onto stack
1374
1375 # now 'head' contains the deepest directory that already exists
1376 # (that is, the child of 'head' in 'name' is the highest directory
1377 # that does *not* exist)
1378 for d in tails:
1379 head = os.path.join(head, d)
1380 abs_head = os.path.abspath(head)
1381
1382 if abs_head in _path_created:
1383 continue
1384
1385 if verbose >= 1:
1386 logger.info("creating %s", head)
1387
1388 if not dry_run:
1389 try:
1390 os.mkdir(head, mode)
1391 except OSError as exc:
1392 if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
1393 raise PackagingFileError(
1394 "could not create '%s': %s" % (head, exc.args[-1]))
1395 created_dirs.append(head)
1396
1397 _path_created.add(abs_head)
1398 return created_dirs
Éric Araujoce5fe832011-07-08 16:27:12 +02001399
1400
1401def encode_multipart(fields, files, boundary=None):
1402 """Prepare a multipart HTTP request.
1403
1404 *fields* is a sequence of (name: str, value: str) elements for regular
1405 form fields, *files* is a sequence of (name: str, filename: str, value:
1406 bytes) elements for data to be uploaded as files.
1407
1408 Returns (content_type: bytes, body: bytes) ready for http.client.HTTP.
1409 """
1410 # Taken from
1411 # http://code.activestate.com/recipes/146306-http-client-to-post-using-multipartform-data/
1412
1413 if boundary is None:
1414 boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
1415 elif not isinstance(boundary, bytes):
1416 raise TypeError('boundary must be bytes, not %r' % type(boundary))
1417
1418 l = []
1419 for key, values in fields:
1420 # handle multiple entries for the same name
1421 if not isinstance(values, (tuple, list)):
Éric Araujo95fc53f2011-09-01 05:11:29 +02001422 values = [values]
Éric Araujoce5fe832011-07-08 16:27:12 +02001423
1424 for value in values:
1425 l.extend((
1426 b'--' + boundary,
1427 ('Content-Disposition: form-data; name="%s"' %
1428 key).encode('utf-8'),
1429 b'',
1430 value.encode('utf-8')))
1431
1432 for key, filename, value in files:
1433 l.extend((
1434 b'--' + boundary,
1435 ('Content-Disposition: form-data; name="%s"; filename="%s"' %
1436 (key, filename)).encode('utf-8'),
1437 b'',
1438 value))
1439
1440 l.append(b'--' + boundary + b'--')
1441 l.append(b'')
1442
1443 body = b'\r\n'.join(l)
1444 content_type = b'multipart/form-data; boundary=' + boundary
1445 return content_type, body