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