blob: 2af11496697953a0235796daace6c66e58c70eb0 [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
Éric Araujo8ccd18f2011-10-19 06:46:13 +0200633 This functions supports packages and attributes without depth limitation:
634 ``package.package.module.class.class.function.attr`` is valid input.
635 However, looking up builtins is not directly supported: use
636 ``builtins.name``.
637
638 Raises ImportError if importing the module fails or if one requested
639 attribute is not found.
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200640 """
Éric Araujo8ccd18f2011-10-19 06:46:13 +0200641 if '.' not in name:
642 # shortcut
643 __import__(name)
644 return sys.modules[name]
645
646 # FIXME clean up this code!
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200647 parts = name.split('.')
648 cursor = len(parts)
649 module_name = parts[:cursor]
Éric Araujo8ccd18f2011-10-19 06:46:13 +0200650 ret = ''
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200651
652 while cursor > 0:
653 try:
654 ret = __import__('.'.join(module_name))
655 break
656 except ImportError:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200657 cursor -= 1
658 module_name = parts[:cursor]
Éric Araujo8ccd18f2011-10-19 06:46:13 +0200659
660 if ret == '':
661 raise ImportError(parts[0])
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200662
663 for part in parts[1:]:
664 try:
665 ret = getattr(ret, part)
666 except AttributeError as exc:
667 raise ImportError(exc)
668
669 return ret
670
671
672def splitext(path):
673 """Like os.path.splitext, but take off .tar too"""
674 base, ext = posixpath.splitext(path)
675 if base.lower().endswith('.tar'):
676 ext = base[-4:] + ext
677 base = base[:-4]
678 return base, ext
679
680
Ned Deilyfceb4122011-06-28 20:04:24 -0700681if sys.platform == 'darwin':
682 _cfg_target = None
683 _cfg_target_split = None
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200684
Éric Araujo95fc53f2011-09-01 05:11:29 +0200685
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200686def spawn(cmd, search_path=True, verbose=0, dry_run=False, env=None):
687 """Run another program specified as a command list 'cmd' in a new process.
688
689 'cmd' is just the argument list for the new process, ie.
690 cmd[0] is the program to run and cmd[1:] are the rest of its arguments.
691 There is no way to run a program with a name different from that of its
692 executable.
693
694 If 'search_path' is true (the default), the system's executable
695 search path will be used to find the program; otherwise, cmd[0]
696 must be the exact path to the executable. If 'dry_run' is true,
697 the command will not actually be run.
698
699 If 'env' is given, it's a environment dictionary used for the execution
700 environment.
701
702 Raise PackagingExecError if running the program fails in any way; just
703 return on success.
704 """
Éric Araujo62806062011-06-11 09:46:07 +0200705 logger.debug('spawn: running %r', cmd)
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200706 if dry_run:
Éric Araujo29f62972011-08-04 17:17:07 +0200707 logger.debug('dry run, no process actually spawned')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200708 return
Ned Deilyfceb4122011-06-28 20:04:24 -0700709 if sys.platform == 'darwin':
710 global _cfg_target, _cfg_target_split
711 if _cfg_target is None:
712 _cfg_target = sysconfig.get_config_var(
713 'MACOSX_DEPLOYMENT_TARGET') or ''
714 if _cfg_target:
715 _cfg_target_split = [int(x) for x in _cfg_target.split('.')]
716 if _cfg_target:
717 # ensure that the deployment target of build process is not less
718 # than that used when the interpreter was built. This ensures
719 # extension modules are built with correct compatibility values
720 env = env or os.environ
721 cur_target = env.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target)
722 if _cfg_target_split > [int(x) for x in cur_target.split('.')]:
723 my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: '
724 'now "%s" but "%s" during configure'
725 % (cur_target, _cfg_target))
726 raise PackagingPlatformError(my_msg)
727 env = dict(env, MACOSX_DEPLOYMENT_TARGET=cur_target)
728
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200729 exit_status = subprocess.call(cmd, env=env)
730 if exit_status != 0:
Éric Araujo62806062011-06-11 09:46:07 +0200731 msg = "command %r failed with exit status %d"
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200732 raise PackagingExecError(msg % (cmd, exit_status))
733
734
735def find_executable(executable, path=None):
736 """Try to find 'executable' in the directories listed in 'path'.
737
738 *path* is a string listing directories separated by 'os.pathsep' and
739 defaults to os.environ['PATH']. Returns the complete filename or None
740 if not found.
741 """
742 if path is None:
743 path = os.environ['PATH']
744 paths = path.split(os.pathsep)
745 base, ext = os.path.splitext(executable)
746
747 if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
748 executable = executable + '.exe'
749
750 if not os.path.isfile(executable):
751 for p in paths:
752 f = os.path.join(p, executable)
753 if os.path.isfile(f):
754 # the file exists, we have a shot at spawn working
755 return f
756 return None
757 else:
758 return executable
759
760
761DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
762DEFAULT_REALM = 'pypi'
763DEFAULT_PYPIRC = """\
764[distutils]
765index-servers =
766 pypi
767
768[pypi]
769username:%s
770password:%s
771"""
772
773
774def get_pypirc_path():
775 """Return path to pypirc config file."""
776 return os.path.join(os.path.expanduser('~'), '.pypirc')
777
778
779def generate_pypirc(username, password):
780 """Create a default .pypirc file."""
781 rc = get_pypirc_path()
782 with open(rc, 'w') as f:
783 f.write(DEFAULT_PYPIRC % (username, password))
784 try:
785 os.chmod(rc, 0o600)
786 except OSError:
787 # should do something better here
788 pass
789
790
791def read_pypirc(repository=DEFAULT_REPOSITORY, realm=DEFAULT_REALM):
792 """Read the .pypirc file."""
793 rc = get_pypirc_path()
794 if os.path.exists(rc):
795 config = RawConfigParser()
796 config.read(rc)
797 sections = config.sections()
798 if 'distutils' in sections:
799 # let's get the list of servers
800 index_servers = config.get('distutils', 'index-servers')
801 _servers = [server.strip() for server in
802 index_servers.split('\n')
803 if server.strip() != '']
804 if _servers == []:
805 # nothing set, let's try to get the default pypi
806 if 'pypi' in sections:
807 _servers = ['pypi']
808 else:
809 # the file is not properly defined, returning
810 # an empty dict
811 return {}
812 for server in _servers:
813 current = {'server': server}
814 current['username'] = config.get(server, 'username')
815
816 # optional params
817 for key, default in (('repository', DEFAULT_REPOSITORY),
818 ('realm', DEFAULT_REALM),
819 ('password', None)):
820 if config.has_option(server, key):
821 current[key] = config.get(server, key)
822 else:
823 current[key] = default
824 if (current['server'] == repository or
825 current['repository'] == repository):
826 return current
827 elif 'server-login' in sections:
828 # old format
829 server = 'server-login'
830 if config.has_option(server, 'repository'):
831 repository = config.get(server, 'repository')
832 else:
833 repository = DEFAULT_REPOSITORY
834
835 return {'username': config.get(server, 'username'),
836 'password': config.get(server, 'password'),
837 'repository': repository,
838 'server': server,
839 'realm': DEFAULT_REALM}
840
841 return {}
842
843
844# utility functions for 2to3 support
845
846def run_2to3(files, doctests_only=False, fixer_names=None,
847 options=None, explicit=None):
848 """ Wrapper function around the refactor() class which
849 performs the conversions on a list of python files.
850 Invoke 2to3 on a list of Python files. The files should all come
851 from the build area, as the modification is done in-place."""
852
853 #if not files:
854 # return
855
856 # Make this class local, to delay import of 2to3
857 from lib2to3.refactor import get_fixers_from_package, RefactoringTool
858 fixers = []
859 fixers = get_fixers_from_package('lib2to3.fixes')
860
861 if fixer_names:
862 for fixername in fixer_names:
863 fixers.extend(fixer for fixer in
864 get_fixers_from_package(fixername))
865 r = RefactoringTool(fixers, options=options)
866 r.refactor(files, write=True, doctests_only=doctests_only)
867
868
869class Mixin2to3:
870 """ Wrapper class for commands that run 2to3.
871 To configure 2to3, setup scripts may either change
872 the class variables, or inherit from this class
873 to override how 2to3 is invoked.
874 """
875 # provide list of fixers to run.
876 # defaults to all from lib2to3.fixers
877 fixer_names = None
878
879 # options dictionary
880 options = None
881
882 # list of fixers to invoke even though they are marked as explicit
883 explicit = None
884
885 def run_2to3(self, files, doctests_only=False):
886 """ Issues a call to util.run_2to3. """
887 return run_2to3(files, doctests_only, self.fixer_names,
888 self.options, self.explicit)
889
890RICH_GLOB = re.compile(r'\{([^}]*)\}')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200891_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200892_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
893
894
895def iglob(path_glob):
896 """Extended globbing function that supports ** and {opt1,opt2,opt3}."""
897 if _CHECK_RECURSIVE_GLOB.search(path_glob):
898 msg = """invalid glob %r: recursive glob "**" must be used alone"""
899 raise ValueError(msg % path_glob)
900 if _CHECK_MISMATCH_SET.search(path_glob):
901 msg = """invalid glob %r: mismatching set marker '{' or '}'"""
902 raise ValueError(msg % path_glob)
903 return _iglob(path_glob)
904
905
906def _iglob(path_glob):
907 rich_path_glob = RICH_GLOB.split(path_glob, 1)
908 if len(rich_path_glob) > 1:
909 assert len(rich_path_glob) == 3, rich_path_glob
910 prefix, set, suffix = rich_path_glob
911 for item in set.split(','):
912 for path in _iglob(''.join((prefix, item, suffix))):
913 yield path
914 else:
915 if '**' not in path_glob:
916 for item in std_iglob(path_glob):
917 yield item
918 else:
919 prefix, radical = path_glob.split('**', 1)
920 if prefix == '':
921 prefix = '.'
922 if radical == '':
923 radical = '*'
924 else:
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200925 # we support both
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200926 radical = radical.lstrip('/')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200927 radical = radical.lstrip('\\')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200928 for path, dir, files in os.walk(prefix):
929 path = os.path.normpath(path)
930 for file in _iglob(os.path.join(path, radical)):
931 yield file
932
933
934def cfg_to_args(path='setup.cfg'):
935 """Compatibility helper to use setup.cfg in setup.py.
936
937 This functions uses an existing setup.cfg to generate a dictionnary of
938 keywords that can be used by distutils.core.setup(**kwargs). It is used
939 by generate_setup_py.
940
941 *file* is the path to the setup.cfg file. If it doesn't exist,
942 PackagingFileError is raised.
943 """
944 # We need to declare the following constants here so that it's easier to
945 # generate the setup.py afterwards, using inspect.getsource.
946
947 # XXX ** == needs testing
948 D1_D2_SETUP_ARGS = {"name": ("metadata",),
949 "version": ("metadata",),
950 "author": ("metadata",),
951 "author_email": ("metadata",),
952 "maintainer": ("metadata",),
953 "maintainer_email": ("metadata",),
954 "url": ("metadata", "home_page"),
955 "description": ("metadata", "summary"),
956 "long_description": ("metadata", "description"),
957 "download-url": ("metadata",),
958 "classifiers": ("metadata", "classifier"),
959 "platforms": ("metadata", "platform"), # **
960 "license": ("metadata",),
961 "requires": ("metadata", "requires_dist"),
962 "provides": ("metadata", "provides_dist"), # **
963 "obsoletes": ("metadata", "obsoletes_dist"), # **
Éric Araujo36050302011-06-10 23:52:26 +0200964 "package_dir": ("files", 'packages_root'),
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200965 "packages": ("files",),
966 "scripts": ("files",),
967 "py_modules": ("files", "modules"), # **
968 }
969
970 MULTI_FIELDS = ("classifiers",
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200971 "platforms",
Éric Araujo36050302011-06-10 23:52:26 +0200972 "requires",
973 "provides",
974 "obsoletes",
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200975 "packages",
Éric Araujo36050302011-06-10 23:52:26 +0200976 "scripts",
977 "py_modules")
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200978
979 def has_get_option(config, section, option):
980 if config.has_option(section, option):
981 return config.get(section, option)
982 elif config.has_option(section, option.replace('_', '-')):
983 return config.get(section, option.replace('_', '-'))
984 else:
985 return False
986
987 # The real code starts here
988 config = RawConfigParser()
Éric Araujo36050302011-06-10 23:52:26 +0200989 if not os.path.exists(path):
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200990 raise PackagingFileError("file '%s' does not exist" %
Éric Araujo36050302011-06-10 23:52:26 +0200991 os.path.abspath(path))
Éric Araujo8d233f22011-06-12 23:02:57 +0200992 config.read(path, encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200993
994 kwargs = {}
995 for arg in D1_D2_SETUP_ARGS:
996 if len(D1_D2_SETUP_ARGS[arg]) == 2:
997 # The distutils field name is different than packaging's
998 section, option = D1_D2_SETUP_ARGS[arg]
999
1000 else:
1001 # The distutils field name is the same thant packaging's
1002 section = D1_D2_SETUP_ARGS[arg][0]
1003 option = arg
1004
1005 in_cfg_value = has_get_option(config, section, option)
1006 if not in_cfg_value:
1007 # There is no such option in the setup.cfg
Éric Araujo36050302011-06-10 23:52:26 +02001008 if arg == 'long_description':
1009 filenames = has_get_option(config, section, 'description-file')
1010 if filenames:
1011 filenames = split_multiline(filenames)
1012 in_cfg_value = []
1013 for filename in filenames:
1014 with open(filename) as fp:
1015 in_cfg_value.append(fp.read())
1016 in_cfg_value = '\n\n'.join(in_cfg_value)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001017 else:
1018 continue
1019
Éric Araujo36050302011-06-10 23:52:26 +02001020 if arg == 'package_dir' and in_cfg_value:
1021 in_cfg_value = {'': in_cfg_value}
1022
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001023 if arg in MULTI_FIELDS:
1024 # support multiline options
Éric Araujo36050302011-06-10 23:52:26 +02001025 in_cfg_value = split_multiline(in_cfg_value)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001026
1027 kwargs[arg] = in_cfg_value
1028
1029 return kwargs
1030
1031
1032_SETUP_TMPL = """\
1033# This script was automatically generated by packaging
1034import os
1035from distutils.core import setup
1036from ConfigParser import RawConfigParser
1037
1038%(func)s
1039
1040setup(**cfg_to_args())
1041"""
1042
1043
1044def generate_setup_py():
1045 """Generate a distutils compatible setup.py using an existing setup.cfg.
1046
1047 Raises a PackagingFileError when a setup.py already exists.
1048 """
1049 if os.path.exists("setup.py"):
Tarek Ziade721ccd02011-06-02 12:00:44 +02001050 raise PackagingFileError("a setup.py file already exists")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001051
Victor Stinner9cf6d132011-05-19 21:42:47 +02001052 with open("setup.py", "w", encoding='utf-8') as fp:
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001053 fp.write(_SETUP_TMPL % {'func': getsource(cfg_to_args)})
1054
1055
1056# Taken from the pip project
1057# https://github.com/pypa/pip/blob/master/pip/util.py
1058def ask(message, options):
1059 """Prompt the user with *message*; *options* contains allowed responses."""
1060 while True:
1061 response = input(message)
1062 response = response.strip().lower()
1063 if response not in options:
Éric Araujo3cab2f12011-06-08 04:10:57 +02001064 print('invalid response:', repr(response))
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001065 print('choose one of', ', '.join(repr(o) for o in options))
1066 else:
1067 return response
1068
1069
1070def _parse_record_file(record_file):
1071 distinfo, extra_metadata, installed = ({}, [], [])
1072 with open(record_file, 'r') as rfile:
1073 for path in rfile:
1074 path = path.strip()
1075 if path.endswith('egg-info') and os.path.isfile(path):
1076 distinfo_dir = path.replace('egg-info', 'dist-info')
1077 metadata = path
1078 egginfo = path
1079 elif path.endswith('egg-info') and os.path.isdir(path):
1080 distinfo_dir = path.replace('egg-info', 'dist-info')
1081 egginfo = path
1082 for metadata_file in os.listdir(path):
1083 metadata_fpath = os.path.join(path, metadata_file)
1084 if metadata_file == 'PKG-INFO':
1085 metadata = metadata_fpath
1086 else:
1087 extra_metadata.append(metadata_fpath)
1088 elif 'egg-info' in path and os.path.isfile(path):
1089 # skip extra metadata files
1090 continue
1091 else:
1092 installed.append(path)
1093
1094 distinfo['egginfo'] = egginfo
1095 distinfo['metadata'] = metadata
1096 distinfo['distinfo_dir'] = distinfo_dir
1097 distinfo['installer_path'] = os.path.join(distinfo_dir, 'INSTALLER')
1098 distinfo['metadata_path'] = os.path.join(distinfo_dir, 'METADATA')
1099 distinfo['record_path'] = os.path.join(distinfo_dir, 'RECORD')
1100 distinfo['requested_path'] = os.path.join(distinfo_dir, 'REQUESTED')
1101 installed.extend([distinfo['installer_path'], distinfo['metadata_path']])
1102 distinfo['installed'] = installed
1103 distinfo['extra_metadata'] = extra_metadata
1104 return distinfo
1105
1106
1107def _write_record_file(record_path, installed_files):
1108 with open(record_path, 'w', encoding='utf-8') as f:
1109 writer = csv.writer(f, delimiter=',', lineterminator=os.linesep,
1110 quotechar='"')
1111
1112 for fpath in installed_files:
1113 if fpath.endswith('.pyc') or fpath.endswith('.pyo'):
1114 # do not put size and md5 hash, as in PEP-376
1115 writer.writerow((fpath, '', ''))
1116 else:
1117 hash = hashlib.md5()
1118 with open(fpath, 'rb') as fp:
1119 hash.update(fp.read())
1120 md5sum = hash.hexdigest()
1121 size = os.path.getsize(fpath)
1122 writer.writerow((fpath, md5sum, size))
1123
1124 # add the RECORD file itself
1125 writer.writerow((record_path, '', ''))
1126 return record_path
1127
1128
1129def egginfo_to_distinfo(record_file, installer=_DEFAULT_INSTALLER,
1130 requested=False, remove_egginfo=False):
1131 """Create files and directories required for PEP 376
1132
1133 :param record_file: path to RECORD file as produced by setup.py --record
1134 :param installer: installer name
1135 :param requested: True if not installed as a dependency
1136 :param remove_egginfo: delete egginfo dir?
1137 """
1138 distinfo = _parse_record_file(record_file)
1139 distinfo_dir = distinfo['distinfo_dir']
1140 if os.path.isdir(distinfo_dir) and not os.path.islink(distinfo_dir):
1141 shutil.rmtree(distinfo_dir)
1142 elif os.path.exists(distinfo_dir):
1143 os.unlink(distinfo_dir)
1144
1145 os.makedirs(distinfo_dir)
1146
1147 # copy setuptools extra metadata files
1148 if distinfo['extra_metadata']:
1149 for path in distinfo['extra_metadata']:
1150 shutil.copy2(path, distinfo_dir)
1151 new_path = path.replace('egg-info', 'dist-info')
1152 distinfo['installed'].append(new_path)
1153
1154 metadata_path = distinfo['metadata_path']
1155 logger.info('creating %s', metadata_path)
1156 shutil.copy2(distinfo['metadata'], metadata_path)
1157
1158 installer_path = distinfo['installer_path']
1159 logger.info('creating %s', installer_path)
1160 with open(installer_path, 'w') as f:
1161 f.write(installer)
1162
1163 if requested:
1164 requested_path = distinfo['requested_path']
1165 logger.info('creating %s', requested_path)
Victor Stinner4c9706b2011-05-19 15:52:59 +02001166 open(requested_path, 'wb').close()
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001167 distinfo['installed'].append(requested_path)
1168
1169 record_path = distinfo['record_path']
1170 logger.info('creating %s', record_path)
1171 _write_record_file(record_path, distinfo['installed'])
1172
1173 if remove_egginfo:
1174 egginfo = distinfo['egginfo']
1175 logger.info('removing %s', egginfo)
1176 if os.path.isfile(egginfo):
1177 os.remove(egginfo)
1178 else:
1179 shutil.rmtree(egginfo)
1180
1181
1182def _has_egg_info(srcdir):
1183 if os.path.isdir(srcdir):
1184 for item in os.listdir(srcdir):
1185 full_path = os.path.join(srcdir, item)
1186 if item.endswith('.egg-info') and os.path.isdir(full_path):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001187 logger.debug("Found egg-info directory.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001188 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001189 logger.debug("No egg-info directory found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001190 return False
1191
1192
1193def _has_setuptools_text(setup_py):
1194 return _has_text(setup_py, 'setuptools')
1195
1196
1197def _has_distutils_text(setup_py):
1198 return _has_text(setup_py, 'distutils')
1199
1200
1201def _has_text(setup_py, installer):
1202 installer_pattern = re.compile('import {0}|from {0}'.format(installer))
1203 with open(setup_py, 'r', encoding='utf-8') as setup:
1204 for line in setup:
1205 if re.search(installer_pattern, line):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001206 logger.debug("Found %s text in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001207 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001208 logger.debug("No %s text found in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001209 return False
1210
1211
1212def _has_required_metadata(setup_cfg):
1213 config = RawConfigParser()
1214 config.read([setup_cfg], encoding='utf8')
1215 return (config.has_section('metadata') and
1216 'name' in config.options('metadata') and
1217 'version' in config.options('metadata'))
1218
1219
1220def _has_pkg_info(srcdir):
1221 pkg_info = os.path.join(srcdir, 'PKG-INFO')
1222 has_pkg_info = os.path.isfile(pkg_info)
1223 if has_pkg_info:
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001224 logger.debug("PKG-INFO file found.")
1225 else:
1226 logger.debug("No PKG-INFO file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001227 return has_pkg_info
1228
1229
1230def _has_setup_py(srcdir):
1231 setup_py = os.path.join(srcdir, 'setup.py')
1232 if os.path.isfile(setup_py):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001233 logger.debug('setup.py file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001234 return True
1235 return False
1236
1237
1238def _has_setup_cfg(srcdir):
1239 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1240 if os.path.isfile(setup_cfg):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001241 logger.debug('setup.cfg file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001242 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001243 logger.debug("No setup.cfg file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001244 return False
1245
1246
1247def is_setuptools(path):
1248 """Check if the project is based on setuptools.
1249
1250 :param path: path to source directory containing a setup.py script.
1251
1252 Return True if the project requires setuptools to install, else False.
1253 """
1254 srcdir = os.path.abspath(path)
1255 setup_py = os.path.join(srcdir, 'setup.py')
1256
1257 return _has_setup_py(srcdir) and (_has_egg_info(srcdir) or
1258 _has_setuptools_text(setup_py))
1259
1260
1261def is_distutils(path):
1262 """Check if the project is based on distutils.
1263
1264 :param path: path to source directory containing a setup.py script.
1265
1266 Return True if the project requires distutils to install, else False.
1267 """
1268 srcdir = os.path.abspath(path)
1269 setup_py = os.path.join(srcdir, 'setup.py')
1270
1271 return _has_setup_py(srcdir) and (_has_pkg_info(srcdir) or
1272 _has_distutils_text(setup_py))
1273
1274
1275def is_packaging(path):
1276 """Check if the project is based on packaging
1277
1278 :param path: path to source directory containing a setup.cfg file.
1279
1280 Return True if the project has a valid setup.cfg, else False.
1281 """
1282 srcdir = os.path.abspath(path)
1283 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1284
1285 return _has_setup_cfg(srcdir) and _has_required_metadata(setup_cfg)
1286
1287
1288def get_install_method(path):
1289 """Check if the project is based on packaging, setuptools, or distutils
1290
1291 :param path: path to source directory containing a setup.cfg file,
1292 or setup.py.
1293
1294 Returns a string representing the best install method to use.
1295 """
1296 if is_packaging(path):
1297 return "packaging"
1298 elif is_setuptools(path):
1299 return "setuptools"
1300 elif is_distutils(path):
1301 return "distutils"
1302 else:
1303 raise InstallationException('Cannot detect install method')
1304
1305
1306# XXX to be replaced by shutil.copytree
1307def copy_tree(src, dst, preserve_mode=True, preserve_times=True,
1308 preserve_symlinks=False, update=False, verbose=True,
1309 dry_run=False):
1310 from distutils.file_util import copy_file
1311
1312 if not dry_run and not os.path.isdir(src):
1313 raise PackagingFileError(
1314 "cannot copy tree '%s': not a directory" % src)
1315 try:
1316 names = os.listdir(src)
1317 except os.error as e:
1318 errstr = e[1]
1319 if dry_run:
1320 names = []
1321 else:
1322 raise PackagingFileError(
1323 "error listing files in '%s': %s" % (src, errstr))
1324
1325 if not dry_run:
1326 _mkpath(dst, verbose=verbose)
1327
1328 outputs = []
1329
1330 for n in names:
1331 src_name = os.path.join(src, n)
1332 dst_name = os.path.join(dst, n)
1333
1334 if preserve_symlinks and os.path.islink(src_name):
1335 link_dest = os.readlink(src_name)
1336 if verbose >= 1:
1337 logger.info("linking %s -> %s", dst_name, link_dest)
1338 if not dry_run:
1339 os.symlink(link_dest, dst_name)
1340 outputs.append(dst_name)
1341
1342 elif os.path.isdir(src_name):
1343 outputs.extend(
1344 copy_tree(src_name, dst_name, preserve_mode,
1345 preserve_times, preserve_symlinks, update,
1346 verbose=verbose, dry_run=dry_run))
1347 else:
1348 copy_file(src_name, dst_name, preserve_mode,
1349 preserve_times, update, verbose=verbose,
1350 dry_run=dry_run)
1351 outputs.append(dst_name)
1352
1353 return outputs
1354
1355# cache for by mkpath() -- in addition to cheapening redundant calls,
1356# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
1357_path_created = set()
1358
1359
1360# I don't use os.makedirs because a) it's new to Python 1.5.2, and
1361# b) it blows up if the directory already exists (I want to silently
1362# succeed in that case).
1363def _mkpath(name, mode=0o777, verbose=True, dry_run=False):
1364 # Detect a common bug -- name is None
1365 if not isinstance(name, str):
1366 raise PackagingInternalError(
1367 "mkpath: 'name' must be a string (got %r)" % (name,))
1368
1369 # XXX what's the better way to handle verbosity? print as we create
1370 # each directory in the path (the current behaviour), or only announce
1371 # the creation of the whole path? (quite easy to do the latter since
1372 # we're not using a recursive algorithm)
1373
1374 name = os.path.normpath(name)
1375 created_dirs = []
1376 if os.path.isdir(name) or name == '':
1377 return created_dirs
1378 if os.path.abspath(name) in _path_created:
1379 return created_dirs
1380
1381 head, tail = os.path.split(name)
1382 tails = [tail] # stack of lone dirs to create
1383
1384 while head and tail and not os.path.isdir(head):
1385 head, tail = os.path.split(head)
1386 tails.insert(0, tail) # push next higher dir onto stack
1387
1388 # now 'head' contains the deepest directory that already exists
1389 # (that is, the child of 'head' in 'name' is the highest directory
1390 # that does *not* exist)
1391 for d in tails:
1392 head = os.path.join(head, d)
1393 abs_head = os.path.abspath(head)
1394
1395 if abs_head in _path_created:
1396 continue
1397
1398 if verbose >= 1:
1399 logger.info("creating %s", head)
1400
1401 if not dry_run:
1402 try:
1403 os.mkdir(head, mode)
1404 except OSError as exc:
1405 if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
1406 raise PackagingFileError(
1407 "could not create '%s': %s" % (head, exc.args[-1]))
1408 created_dirs.append(head)
1409
1410 _path_created.add(abs_head)
1411 return created_dirs
Éric Araujoce5fe832011-07-08 16:27:12 +02001412
1413
1414def encode_multipart(fields, files, boundary=None):
1415 """Prepare a multipart HTTP request.
1416
1417 *fields* is a sequence of (name: str, value: str) elements for regular
1418 form fields, *files* is a sequence of (name: str, filename: str, value:
1419 bytes) elements for data to be uploaded as files.
1420
1421 Returns (content_type: bytes, body: bytes) ready for http.client.HTTP.
1422 """
1423 # Taken from
1424 # http://code.activestate.com/recipes/146306-http-client-to-post-using-multipartform-data/
1425
1426 if boundary is None:
1427 boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
1428 elif not isinstance(boundary, bytes):
1429 raise TypeError('boundary must be bytes, not %r' % type(boundary))
1430
1431 l = []
1432 for key, values in fields:
1433 # handle multiple entries for the same name
1434 if not isinstance(values, (tuple, list)):
Éric Araujo95fc53f2011-09-01 05:11:29 +02001435 values = [values]
Éric Araujoce5fe832011-07-08 16:27:12 +02001436
1437 for value in values:
1438 l.extend((
1439 b'--' + boundary,
1440 ('Content-Disposition: form-data; name="%s"' %
1441 key).encode('utf-8'),
1442 b'',
1443 value.encode('utf-8')))
1444
1445 for key, filename, value in files:
1446 l.extend((
1447 b'--' + boundary,
1448 ('Content-Disposition: form-data; name="%s"; filename="%s"' %
1449 (key, filename)).encode('utf-8'),
1450 b'',
1451 value))
1452
1453 l.append(b'--' + boundary + b'--')
1454 l.append(b'')
1455
1456 body = b'\r\n'.join(l)
1457 content_type = b'multipart/form-data; boundary=' + boundary
1458 return content_type, body