blob: 8dd715fe11ac2dcbda30a7349df4befc993f7b38 [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,
23 PackagingByteCompileError, PackagingExecError,
24 InstallationException, PackagingInternalError)
25
É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.
328 """
329 # nothing is done if sys.dont_write_bytecode is True
330 # FIXME this should not raise an error
Éric Araujo7724a6c2011-09-17 03:31:51 +0200331 if sys.dont_write_bytecode:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200332 raise PackagingByteCompileError('byte-compiling is disabled.')
333
334 # First, if the caller didn't force us into direct or indirect mode,
335 # figure out which mode we should be in. We take a conservative
336 # approach: choose direct mode *only* if the current interpreter is
337 # in debug mode and optimize is 0. If we're not in debug mode (-O
338 # or -OO), we don't know which level of optimization this
339 # interpreter is running with, so we can't do direct
340 # byte-compilation and be certain that it's the right thing. Thus,
341 # always compile indirectly if the current interpreter is in either
342 # optimize mode, or if either optimization level was requested by
343 # the caller.
344 if direct is None:
345 direct = (__debug__ and optimize == 0)
346
347 # "Indirect" byte-compilation: write a temporary script and then
348 # run it with the appropriate flags.
349 if not direct:
350 from tempfile import mkstemp
Éric Araujo7724a6c2011-09-17 03:31:51 +0200351 # XXX use something better than mkstemp
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200352 script_fd, script_name = mkstemp(".py")
Éric Araujo7724a6c2011-09-17 03:31:51 +0200353 os.close(script_fd)
354 script_fd = None
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200355 logger.info("writing byte-compilation script '%s'", script_name)
356 if not dry_run:
357 if script_fd is not None:
Victor Stinner9cf6d132011-05-19 21:42:47 +0200358 script = os.fdopen(script_fd, "w", encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200359 else:
Victor Stinner9cf6d132011-05-19 21:42:47 +0200360 script = open(script_name, "w", encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200361
Victor Stinner21a9c742011-05-19 15:51:27 +0200362 with script:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200363 script.write("""\
364from packaging.util import byte_compile
365files = [
366""")
367
368 # XXX would be nice to write absolute filenames, just for
369 # safety's sake (script should be more robust in the face of
370 # chdir'ing before running it). But this requires abspath'ing
371 # 'prefix' as well, and that breaks the hack in build_lib's
372 # 'byte_compile()' method that carefully tacks on a trailing
373 # slash (os.sep really) to make sure the prefix here is "just
374 # right". This whole prefix business is rather delicate -- the
375 # problem is that it's really a directory, but I'm treating it
376 # as a dumb string, so trailing slashes and so forth matter.
377
378 #py_files = map(os.path.abspath, py_files)
379 #if prefix:
380 # prefix = os.path.abspath(prefix)
381
382 script.write(",\n".join(map(repr, py_files)) + "]\n")
383 script.write("""
384byte_compile(files, optimize=%r, force=%r,
385 prefix=%r, base_dir=%r,
386 verbose=%r, dry_run=False,
387 direct=True)
388""" % (optimize, force, prefix, base_dir, verbose))
389
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200390 cmd = [sys.executable, script_name]
391 if optimize == 1:
392 cmd.insert(1, "-O")
393 elif optimize == 2:
394 cmd.insert(1, "-OO")
395
Éric Araujo088025f2011-06-04 18:45:40 +0200396 env = os.environ.copy()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200397 env['PYTHONPATH'] = os.path.pathsep.join(sys.path)
398 try:
399 spawn(cmd, env=env)
400 finally:
401 execute(os.remove, (script_name,), "removing %s" % script_name,
402 dry_run=dry_run)
403
404 # "Direct" byte-compilation: use the py_compile module to compile
405 # right here, right now. Note that the script generated in indirect
406 # mode simply calls 'byte_compile()' in direct mode, a weird sort of
407 # cross-process recursion. Hey, it works!
408 else:
409 from py_compile import compile
410
411 for file in py_files:
412 if file[-3:] != ".py":
413 # This lets us be lazy and not filter filenames in
414 # the "install_lib" command.
415 continue
416
417 # Terminology from the py_compile module:
418 # cfile - byte-compiled file
419 # dfile - purported source filename (same as 'file' by default)
Éric Araujoa29e4f62011-10-08 04:09:15 +0200420 if optimize >= 0:
Éric Araujof89ebdc2011-10-21 06:27:06 +0200421 cfile = imp.cache_from_source(file,
422 debug_override=not optimize)
Éric Araujoa29e4f62011-10-08 04:09:15 +0200423 else:
424 cfile = imp.cache_from_source(file)
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200425 dfile = file
426 if prefix:
427 if file[:len(prefix)] != prefix:
428 raise ValueError("invalid prefix: filename %r doesn't "
429 "start with %r" % (file, prefix))
430 dfile = dfile[len(prefix):]
431 if base_dir:
432 dfile = os.path.join(base_dir, dfile)
433
434 cfile_base = os.path.basename(cfile)
435 if direct:
436 if force or newer(file, cfile):
437 logger.info("byte-compiling %s to %s", file, cfile_base)
438 if not dry_run:
439 compile(file, cfile, dfile)
440 else:
441 logger.debug("skipping byte-compilation of %s to %s",
442 file, cfile_base)
443
444
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200445_RE_VERSION = re.compile('(\d+\.\d+(\.\d+)*)')
446_MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld '
447 'PROJECT:ld64-((\d+)(\.\d+)*)')
448
449
450def _find_ld_version():
451 """Find the ld version. The version scheme differs under Mac OS X."""
452 if sys.platform == 'darwin':
453 return _find_exe_version('ld -v', _MAC_OS_X_LD_VERSION)
454 else:
455 return _find_exe_version('ld -v')
456
457
458def _find_exe_version(cmd, pattern=_RE_VERSION):
459 """Find the version of an executable by running `cmd` in the shell.
460
461 `pattern` is a compiled regular expression. If not provided, defaults
462 to _RE_VERSION. If the command is not found, or the output does not
463 match the mattern, returns None.
464 """
465 from subprocess import Popen, PIPE
466 executable = cmd.split()[0]
467 if find_executable(executable) is None:
468 return None
469 pipe = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
470 try:
Victor Stinner9904b222011-05-21 02:20:36 +0200471 stdout, stderr = pipe.communicate()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200472 finally:
473 pipe.stdout.close()
474 pipe.stderr.close()
475 # some commands like ld under MacOS X, will give the
476 # output in the stderr, rather than stdout.
477 if stdout != '':
478 out_string = stdout
479 else:
480 out_string = stderr
481
482 result = pattern.search(out_string)
483 if result is None:
484 return None
485 return result.group(1)
486
487
488def get_compiler_versions():
489 """Return a tuple providing the versions of gcc, ld and dllwrap
490
491 For each command, if a command is not found, None is returned.
492 Otherwise a string with the version is returned.
493 """
494 gcc = _find_exe_version('gcc -dumpversion')
495 ld = _find_ld_version()
496 dllwrap = _find_exe_version('dllwrap --version')
497 return gcc, ld, dllwrap
498
499
500def newer_group(sources, target, missing='error'):
501 """Return true if 'target' is out-of-date with respect to any file
502 listed in 'sources'.
503
504 In other words, if 'target' exists and is newer
505 than every file in 'sources', return false; otherwise return true.
506 'missing' controls what we do when a source file is missing; the
507 default ("error") is to blow up with an OSError from inside 'stat()';
508 if it is "ignore", we silently drop any missing source files; if it is
509 "newer", any missing source files make us assume that 'target' is
510 out-of-date (this is handy in "dry-run" mode: it'll make you pretend to
511 carry out commands that wouldn't work because inputs are missing, but
512 that doesn't matter because you're not actually going to run the
513 commands).
514 """
515 # If the target doesn't even exist, then it's definitely out-of-date.
516 if not os.path.exists(target):
517 return True
518
519 # Otherwise we have to find out the hard way: if *any* source file
520 # is more recent than 'target', then 'target' is out-of-date and
521 # we can immediately return true. If we fall through to the end
522 # of the loop, then 'target' is up-to-date and we return false.
523 target_mtime = os.stat(target).st_mtime
524
525 for source in sources:
526 if not os.path.exists(source):
527 if missing == 'error': # blow up when we stat() the file
528 pass
529 elif missing == 'ignore': # missing source dropped from
530 continue # target's dependency list
531 elif missing == 'newer': # missing source means target is
532 return True # out-of-date
533
534 if os.stat(source).st_mtime > target_mtime:
535 return True
536
537 return False
538
539
540def write_file(filename, contents):
541 """Create *filename* and write *contents* to it.
542
543 *contents* is a sequence of strings without line terminators.
Éric Araujo95fc53f2011-09-01 05:11:29 +0200544
545 This functions is not intended to replace the usual with open + write
546 idiom in all cases, only with Command.execute, which runs depending on
547 the dry_run argument and also logs its arguments).
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200548 """
549 with open(filename, "w") as f:
550 for line in contents:
551 f.write(line + "\n")
552
553
554def _is_package(path):
Éric Araujo1c1d9a52011-06-10 23:26:31 +0200555 return os.path.isdir(path) and os.path.isfile(
556 os.path.join(path, '__init__.py'))
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200557
558
559# Code taken from the pip project
560def _is_archive_file(name):
561 archives = ('.zip', '.tar.gz', '.tar.bz2', '.tgz', '.tar')
562 ext = splitext(name)[1].lower()
Éric Araujo1c1d9a52011-06-10 23:26:31 +0200563 return ext in archives
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200564
565
566def _under(path, root):
Éric Araujo95fc53f2011-09-01 05:11:29 +0200567 # XXX use os.path
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200568 path = path.split(os.sep)
569 root = root.split(os.sep)
570 if len(root) > len(path):
571 return False
572 for pos, part in enumerate(root):
573 if path[pos] != part:
574 return False
575 return True
576
577
578def _package_name(root_path, path):
579 # Return a dotted package name, given a subpath
580 if not _under(path, root_path):
581 raise ValueError('"%s" is not a subpath of "%s"' % (path, root_path))
582 return path[len(root_path) + 1:].replace(os.sep, '.')
583
584
585def find_packages(paths=(os.curdir,), exclude=()):
586 """Return a list all Python packages found recursively within
587 directories 'paths'
588
589 'paths' should be supplied as a sequence of "cross-platform"
590 (i.e. URL-style) path; it will be converted to the appropriate local
591 path syntax.
592
593 'exclude' is a sequence of package names to exclude; '*' can be used as
594 a wildcard in the names, such that 'foo.*' will exclude all subpackages
595 of 'foo' (but not 'foo' itself).
596 """
597 packages = []
598 discarded = []
599
600 def _discarded(path):
601 for discard in discarded:
602 if _under(path, discard):
603 return True
604 return False
605
606 for path in paths:
607 path = convert_path(path)
608 for root, dirs, files in os.walk(path):
609 for dir_ in dirs:
610 fullpath = os.path.join(root, dir_)
611 if _discarded(fullpath):
612 continue
613 # we work only with Python packages
614 if not _is_package(fullpath):
615 discarded.append(fullpath)
616 continue
617 # see if it's excluded
618 excluded = False
619 package_name = _package_name(path, fullpath)
620 for pattern in exclude:
621 if fnmatchcase(package_name, pattern):
622 excluded = True
623 break
624 if excluded:
625 continue
626
627 # adding it to the list
628 packages.append(package_name)
629 return packages
630
631
632def resolve_name(name):
633 """Resolve a name like ``module.object`` to an object and return it.
634
Éric Araujo8ccd18f2011-10-19 06:46:13 +0200635 This functions supports packages and attributes without depth limitation:
636 ``package.package.module.class.class.function.attr`` is valid input.
637 However, looking up builtins is not directly supported: use
638 ``builtins.name``.
639
640 Raises ImportError if importing the module fails or if one requested
641 attribute is not found.
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200642 """
Éric Araujo8ccd18f2011-10-19 06:46:13 +0200643 if '.' not in name:
644 # shortcut
645 __import__(name)
646 return sys.modules[name]
647
648 # FIXME clean up this code!
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200649 parts = name.split('.')
650 cursor = len(parts)
651 module_name = parts[:cursor]
Éric Araujo8ccd18f2011-10-19 06:46:13 +0200652 ret = ''
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200653
654 while cursor > 0:
655 try:
656 ret = __import__('.'.join(module_name))
657 break
658 except ImportError:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200659 cursor -= 1
660 module_name = parts[:cursor]
Éric Araujo8ccd18f2011-10-19 06:46:13 +0200661
662 if ret == '':
663 raise ImportError(parts[0])
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200664
665 for part in parts[1:]:
666 try:
667 ret = getattr(ret, part)
668 except AttributeError as exc:
669 raise ImportError(exc)
670
671 return ret
672
673
674def splitext(path):
675 """Like os.path.splitext, but take off .tar too"""
676 base, ext = posixpath.splitext(path)
677 if base.lower().endswith('.tar'):
678 ext = base[-4:] + ext
679 base = base[:-4]
680 return base, ext
681
682
Ned Deilyfceb4122011-06-28 20:04:24 -0700683if sys.platform == 'darwin':
684 _cfg_target = None
685 _cfg_target_split = None
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200686
Éric Araujo95fc53f2011-09-01 05:11:29 +0200687
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200688def spawn(cmd, search_path=True, verbose=0, dry_run=False, env=None):
689 """Run another program specified as a command list 'cmd' in a new process.
690
691 'cmd' is just the argument list for the new process, ie.
692 cmd[0] is the program to run and cmd[1:] are the rest of its arguments.
693 There is no way to run a program with a name different from that of its
694 executable.
695
696 If 'search_path' is true (the default), the system's executable
697 search path will be used to find the program; otherwise, cmd[0]
698 must be the exact path to the executable. If 'dry_run' is true,
699 the command will not actually be run.
700
701 If 'env' is given, it's a environment dictionary used for the execution
702 environment.
703
704 Raise PackagingExecError if running the program fails in any way; just
705 return on success.
706 """
Éric Araujo62806062011-06-11 09:46:07 +0200707 logger.debug('spawn: running %r', cmd)
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200708 if dry_run:
Éric Araujo29f62972011-08-04 17:17:07 +0200709 logger.debug('dry run, no process actually spawned')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200710 return
Ned Deilyfceb4122011-06-28 20:04:24 -0700711 if sys.platform == 'darwin':
712 global _cfg_target, _cfg_target_split
713 if _cfg_target is None:
714 _cfg_target = sysconfig.get_config_var(
715 'MACOSX_DEPLOYMENT_TARGET') or ''
716 if _cfg_target:
717 _cfg_target_split = [int(x) for x in _cfg_target.split('.')]
718 if _cfg_target:
719 # ensure that the deployment target of build process is not less
720 # than that used when the interpreter was built. This ensures
721 # extension modules are built with correct compatibility values
722 env = env or os.environ
723 cur_target = env.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target)
724 if _cfg_target_split > [int(x) for x in cur_target.split('.')]:
725 my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: '
726 'now "%s" but "%s" during configure'
727 % (cur_target, _cfg_target))
728 raise PackagingPlatformError(my_msg)
729 env = dict(env, MACOSX_DEPLOYMENT_TARGET=cur_target)
730
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200731 exit_status = subprocess.call(cmd, env=env)
732 if exit_status != 0:
Éric Araujo62806062011-06-11 09:46:07 +0200733 msg = "command %r failed with exit status %d"
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200734 raise PackagingExecError(msg % (cmd, exit_status))
735
736
737def find_executable(executable, path=None):
738 """Try to find 'executable' in the directories listed in 'path'.
739
740 *path* is a string listing directories separated by 'os.pathsep' and
741 defaults to os.environ['PATH']. Returns the complete filename or None
742 if not found.
743 """
744 if path is None:
745 path = os.environ['PATH']
746 paths = path.split(os.pathsep)
747 base, ext = os.path.splitext(executable)
748
749 if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
750 executable = executable + '.exe'
751
752 if not os.path.isfile(executable):
753 for p in paths:
754 f = os.path.join(p, executable)
755 if os.path.isfile(f):
756 # the file exists, we have a shot at spawn working
757 return f
758 return None
759 else:
760 return executable
761
762
763DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
764DEFAULT_REALM = 'pypi'
765DEFAULT_PYPIRC = """\
766[distutils]
767index-servers =
768 pypi
769
770[pypi]
771username:%s
772password:%s
773"""
774
775
776def get_pypirc_path():
777 """Return path to pypirc config file."""
778 return os.path.join(os.path.expanduser('~'), '.pypirc')
779
780
781def generate_pypirc(username, password):
782 """Create a default .pypirc file."""
783 rc = get_pypirc_path()
784 with open(rc, 'w') as f:
785 f.write(DEFAULT_PYPIRC % (username, password))
786 try:
787 os.chmod(rc, 0o600)
788 except OSError:
789 # should do something better here
790 pass
791
792
793def read_pypirc(repository=DEFAULT_REPOSITORY, realm=DEFAULT_REALM):
794 """Read the .pypirc file."""
795 rc = get_pypirc_path()
796 if os.path.exists(rc):
797 config = RawConfigParser()
798 config.read(rc)
799 sections = config.sections()
800 if 'distutils' in sections:
801 # let's get the list of servers
802 index_servers = config.get('distutils', 'index-servers')
803 _servers = [server.strip() for server in
804 index_servers.split('\n')
805 if server.strip() != '']
806 if _servers == []:
807 # nothing set, let's try to get the default pypi
808 if 'pypi' in sections:
809 _servers = ['pypi']
810 else:
811 # the file is not properly defined, returning
812 # an empty dict
813 return {}
814 for server in _servers:
815 current = {'server': server}
816 current['username'] = config.get(server, 'username')
817
818 # optional params
819 for key, default in (('repository', DEFAULT_REPOSITORY),
820 ('realm', DEFAULT_REALM),
821 ('password', None)):
822 if config.has_option(server, key):
823 current[key] = config.get(server, key)
824 else:
825 current[key] = default
826 if (current['server'] == repository or
827 current['repository'] == repository):
828 return current
829 elif 'server-login' in sections:
830 # old format
831 server = 'server-login'
832 if config.has_option(server, 'repository'):
833 repository = config.get(server, 'repository')
834 else:
835 repository = DEFAULT_REPOSITORY
836
837 return {'username': config.get(server, 'username'),
838 'password': config.get(server, 'password'),
839 'repository': repository,
840 'server': server,
841 'realm': DEFAULT_REALM}
842
843 return {}
844
845
846# utility functions for 2to3 support
847
848def run_2to3(files, doctests_only=False, fixer_names=None,
849 options=None, explicit=None):
850 """ Wrapper function around the refactor() class which
851 performs the conversions on a list of python files.
852 Invoke 2to3 on a list of Python files. The files should all come
853 from the build area, as the modification is done in-place."""
854
855 #if not files:
856 # return
857
858 # Make this class local, to delay import of 2to3
859 from lib2to3.refactor import get_fixers_from_package, RefactoringTool
860 fixers = []
861 fixers = get_fixers_from_package('lib2to3.fixes')
862
863 if fixer_names:
864 for fixername in fixer_names:
865 fixers.extend(fixer for fixer in
866 get_fixers_from_package(fixername))
867 r = RefactoringTool(fixers, options=options)
868 r.refactor(files, write=True, doctests_only=doctests_only)
869
870
871class Mixin2to3:
872 """ Wrapper class for commands that run 2to3.
873 To configure 2to3, setup scripts may either change
874 the class variables, or inherit from this class
875 to override how 2to3 is invoked.
876 """
877 # provide list of fixers to run.
878 # defaults to all from lib2to3.fixers
879 fixer_names = None
880
881 # options dictionary
882 options = None
883
884 # list of fixers to invoke even though they are marked as explicit
885 explicit = None
886
887 def run_2to3(self, files, doctests_only=False):
888 """ Issues a call to util.run_2to3. """
889 return run_2to3(files, doctests_only, self.fixer_names,
890 self.options, self.explicit)
891
892RICH_GLOB = re.compile(r'\{([^}]*)\}')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200893_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200894_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
895
896
897def iglob(path_glob):
898 """Extended globbing function that supports ** and {opt1,opt2,opt3}."""
899 if _CHECK_RECURSIVE_GLOB.search(path_glob):
900 msg = """invalid glob %r: recursive glob "**" must be used alone"""
901 raise ValueError(msg % path_glob)
902 if _CHECK_MISMATCH_SET.search(path_glob):
903 msg = """invalid glob %r: mismatching set marker '{' or '}'"""
904 raise ValueError(msg % path_glob)
905 return _iglob(path_glob)
906
907
908def _iglob(path_glob):
909 rich_path_glob = RICH_GLOB.split(path_glob, 1)
910 if len(rich_path_glob) > 1:
911 assert len(rich_path_glob) == 3, rich_path_glob
912 prefix, set, suffix = rich_path_glob
913 for item in set.split(','):
914 for path in _iglob(''.join((prefix, item, suffix))):
915 yield path
916 else:
917 if '**' not in path_glob:
918 for item in std_iglob(path_glob):
919 yield item
920 else:
921 prefix, radical = path_glob.split('**', 1)
922 if prefix == '':
923 prefix = '.'
924 if radical == '':
925 radical = '*'
926 else:
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200927 # we support both
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200928 radical = radical.lstrip('/')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200929 radical = radical.lstrip('\\')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200930 for path, dir, files in os.walk(prefix):
931 path = os.path.normpath(path)
932 for file in _iglob(os.path.join(path, radical)):
933 yield file
934
935
Éric Araujof89ebdc2011-10-21 06:27:06 +0200936# HOWTO change cfg_to_args
937#
938# This function has two major constraints: It is copied by inspect.getsource
939# in generate_setup_py; it is used in generated setup.py which may be run by
940# any Python version supported by distutils2 (2.4-3.3).
941#
942# * Keep objects like D1_D2_SETUP_ARGS static, i.e. in the function body
943# instead of global.
944# * If you use a function from another module, update the imports in
945# SETUP_TEMPLATE. Use only modules, classes and functions compatible with
946# all versions: codecs.open instead of open, RawConfigParser.readfp instead
947# of read, standard exceptions instead of Packaging*Error, etc.
948# * If you use a function from this module, update the template and
949# generate_setup_py.
950#
951# test_util tests this function and the generated setup.py, but does not test
952# that it's compatible with all Python versions.
953
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200954def cfg_to_args(path='setup.cfg'):
955 """Compatibility helper to use setup.cfg in setup.py.
956
957 This functions uses an existing setup.cfg to generate a dictionnary of
958 keywords that can be used by distutils.core.setup(**kwargs). It is used
959 by generate_setup_py.
960
961 *file* is the path to the setup.cfg file. If it doesn't exist,
962 PackagingFileError is raised.
963 """
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200964
965 # XXX ** == needs testing
966 D1_D2_SETUP_ARGS = {"name": ("metadata",),
967 "version": ("metadata",),
968 "author": ("metadata",),
969 "author_email": ("metadata",),
970 "maintainer": ("metadata",),
971 "maintainer_email": ("metadata",),
972 "url": ("metadata", "home_page"),
973 "description": ("metadata", "summary"),
974 "long_description": ("metadata", "description"),
975 "download-url": ("metadata",),
976 "classifiers": ("metadata", "classifier"),
977 "platforms": ("metadata", "platform"), # **
978 "license": ("metadata",),
979 "requires": ("metadata", "requires_dist"),
980 "provides": ("metadata", "provides_dist"), # **
981 "obsoletes": ("metadata", "obsoletes_dist"), # **
Éric Araujo36050302011-06-10 23:52:26 +0200982 "package_dir": ("files", 'packages_root'),
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200983 "packages": ("files",),
984 "scripts": ("files",),
985 "py_modules": ("files", "modules"), # **
986 }
987
988 MULTI_FIELDS = ("classifiers",
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200989 "platforms",
Éric Araujo36050302011-06-10 23:52:26 +0200990 "requires",
991 "provides",
992 "obsoletes",
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200993 "packages",
Éric Araujo36050302011-06-10 23:52:26 +0200994 "scripts",
995 "py_modules")
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200996
997 def has_get_option(config, section, option):
998 if config.has_option(section, option):
999 return config.get(section, option)
1000 elif config.has_option(section, option.replace('_', '-')):
1001 return config.get(section, option.replace('_', '-'))
1002 else:
1003 return False
1004
1005 # The real code starts here
1006 config = RawConfigParser()
Éric Araujof89ebdc2011-10-21 06:27:06 +02001007 f = codecs.open(path, encoding='utf-8')
1008 try:
1009 config.readfp(f)
1010 finally:
1011 f.close()
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001012
1013 kwargs = {}
1014 for arg in D1_D2_SETUP_ARGS:
1015 if len(D1_D2_SETUP_ARGS[arg]) == 2:
1016 # The distutils field name is different than packaging's
1017 section, option = D1_D2_SETUP_ARGS[arg]
1018
1019 else:
1020 # The distutils field name is the same thant packaging's
1021 section = D1_D2_SETUP_ARGS[arg][0]
1022 option = arg
1023
1024 in_cfg_value = has_get_option(config, section, option)
1025 if not in_cfg_value:
1026 # There is no such option in the setup.cfg
Éric Araujo36050302011-06-10 23:52:26 +02001027 if arg == 'long_description':
1028 filenames = has_get_option(config, section, 'description-file')
1029 if filenames:
1030 filenames = split_multiline(filenames)
1031 in_cfg_value = []
1032 for filename in filenames:
Éric Araujof89ebdc2011-10-21 06:27:06 +02001033 fp = codecs.open(filename, encoding='utf-8')
1034 try:
Éric Araujo36050302011-06-10 23:52:26 +02001035 in_cfg_value.append(fp.read())
Éric Araujof89ebdc2011-10-21 06:27:06 +02001036 finally:
1037 fp.close()
Éric Araujo36050302011-06-10 23:52:26 +02001038 in_cfg_value = '\n\n'.join(in_cfg_value)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001039 else:
1040 continue
1041
Éric Araujo36050302011-06-10 23:52:26 +02001042 if arg == 'package_dir' and in_cfg_value:
1043 in_cfg_value = {'': in_cfg_value}
1044
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001045 if arg in MULTI_FIELDS:
1046 # support multiline options
Éric Araujo36050302011-06-10 23:52:26 +02001047 in_cfg_value = split_multiline(in_cfg_value)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001048
1049 kwargs[arg] = in_cfg_value
1050
1051 return kwargs
1052
1053
Éric Araujof89ebdc2011-10-21 06:27:06 +02001054SETUP_TEMPLATE = """\
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001055# This script was automatically generated by packaging
1056import os
Éric Araujof89ebdc2011-10-21 06:27:06 +02001057import codecs
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001058from distutils.core import setup
Éric Araujof89ebdc2011-10-21 06:27:06 +02001059try:
1060 from ConfigParser import RawConfigParser
1061except ImportError:
1062 from configparser import RawConfigParser
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001063
Éric Araujof89ebdc2011-10-21 06:27:06 +02001064%(split_multiline)s
1065
1066%(cfg_to_args)s
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001067
1068setup(**cfg_to_args())
1069"""
1070
1071
1072def generate_setup_py():
1073 """Generate a distutils compatible setup.py using an existing setup.cfg.
1074
1075 Raises a PackagingFileError when a setup.py already exists.
1076 """
1077 if os.path.exists("setup.py"):
Tarek Ziade721ccd02011-06-02 12:00:44 +02001078 raise PackagingFileError("a setup.py file already exists")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001079
Éric Araujof89ebdc2011-10-21 06:27:06 +02001080 source = SETUP_TEMPLATE % {'split_multiline': getsource(split_multiline),
1081 'cfg_to_args': getsource(cfg_to_args)}
Victor Stinner9cf6d132011-05-19 21:42:47 +02001082 with open("setup.py", "w", encoding='utf-8') as fp:
Éric Araujof89ebdc2011-10-21 06:27:06 +02001083 fp.write(source)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001084
1085
1086# Taken from the pip project
1087# https://github.com/pypa/pip/blob/master/pip/util.py
1088def ask(message, options):
1089 """Prompt the user with *message*; *options* contains allowed responses."""
1090 while True:
1091 response = input(message)
1092 response = response.strip().lower()
1093 if response not in options:
Éric Araujo3cab2f12011-06-08 04:10:57 +02001094 print('invalid response:', repr(response))
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001095 print('choose one of', ', '.join(repr(o) for o in options))
1096 else:
1097 return response
1098
1099
1100def _parse_record_file(record_file):
1101 distinfo, extra_metadata, installed = ({}, [], [])
1102 with open(record_file, 'r') as rfile:
1103 for path in rfile:
1104 path = path.strip()
1105 if path.endswith('egg-info') and os.path.isfile(path):
1106 distinfo_dir = path.replace('egg-info', 'dist-info')
1107 metadata = path
1108 egginfo = path
1109 elif path.endswith('egg-info') and os.path.isdir(path):
1110 distinfo_dir = path.replace('egg-info', 'dist-info')
1111 egginfo = path
1112 for metadata_file in os.listdir(path):
1113 metadata_fpath = os.path.join(path, metadata_file)
1114 if metadata_file == 'PKG-INFO':
1115 metadata = metadata_fpath
1116 else:
1117 extra_metadata.append(metadata_fpath)
1118 elif 'egg-info' in path and os.path.isfile(path):
1119 # skip extra metadata files
1120 continue
1121 else:
1122 installed.append(path)
1123
1124 distinfo['egginfo'] = egginfo
1125 distinfo['metadata'] = metadata
1126 distinfo['distinfo_dir'] = distinfo_dir
1127 distinfo['installer_path'] = os.path.join(distinfo_dir, 'INSTALLER')
1128 distinfo['metadata_path'] = os.path.join(distinfo_dir, 'METADATA')
1129 distinfo['record_path'] = os.path.join(distinfo_dir, 'RECORD')
1130 distinfo['requested_path'] = os.path.join(distinfo_dir, 'REQUESTED')
1131 installed.extend([distinfo['installer_path'], distinfo['metadata_path']])
1132 distinfo['installed'] = installed
1133 distinfo['extra_metadata'] = extra_metadata
1134 return distinfo
1135
1136
1137def _write_record_file(record_path, installed_files):
1138 with open(record_path, 'w', encoding='utf-8') as f:
1139 writer = csv.writer(f, delimiter=',', lineterminator=os.linesep,
1140 quotechar='"')
1141
1142 for fpath in installed_files:
1143 if fpath.endswith('.pyc') or fpath.endswith('.pyo'):
1144 # do not put size and md5 hash, as in PEP-376
1145 writer.writerow((fpath, '', ''))
1146 else:
1147 hash = hashlib.md5()
1148 with open(fpath, 'rb') as fp:
1149 hash.update(fp.read())
1150 md5sum = hash.hexdigest()
1151 size = os.path.getsize(fpath)
1152 writer.writerow((fpath, md5sum, size))
1153
1154 # add the RECORD file itself
1155 writer.writerow((record_path, '', ''))
1156 return record_path
1157
1158
1159def egginfo_to_distinfo(record_file, installer=_DEFAULT_INSTALLER,
1160 requested=False, remove_egginfo=False):
1161 """Create files and directories required for PEP 376
1162
1163 :param record_file: path to RECORD file as produced by setup.py --record
1164 :param installer: installer name
1165 :param requested: True if not installed as a dependency
1166 :param remove_egginfo: delete egginfo dir?
1167 """
1168 distinfo = _parse_record_file(record_file)
1169 distinfo_dir = distinfo['distinfo_dir']
1170 if os.path.isdir(distinfo_dir) and not os.path.islink(distinfo_dir):
1171 shutil.rmtree(distinfo_dir)
1172 elif os.path.exists(distinfo_dir):
1173 os.unlink(distinfo_dir)
1174
1175 os.makedirs(distinfo_dir)
1176
1177 # copy setuptools extra metadata files
1178 if distinfo['extra_metadata']:
1179 for path in distinfo['extra_metadata']:
1180 shutil.copy2(path, distinfo_dir)
1181 new_path = path.replace('egg-info', 'dist-info')
1182 distinfo['installed'].append(new_path)
1183
1184 metadata_path = distinfo['metadata_path']
1185 logger.info('creating %s', metadata_path)
1186 shutil.copy2(distinfo['metadata'], metadata_path)
1187
1188 installer_path = distinfo['installer_path']
1189 logger.info('creating %s', installer_path)
1190 with open(installer_path, 'w') as f:
1191 f.write(installer)
1192
1193 if requested:
1194 requested_path = distinfo['requested_path']
1195 logger.info('creating %s', requested_path)
Victor Stinner4c9706b2011-05-19 15:52:59 +02001196 open(requested_path, 'wb').close()
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001197 distinfo['installed'].append(requested_path)
1198
1199 record_path = distinfo['record_path']
1200 logger.info('creating %s', record_path)
1201 _write_record_file(record_path, distinfo['installed'])
1202
1203 if remove_egginfo:
1204 egginfo = distinfo['egginfo']
1205 logger.info('removing %s', egginfo)
1206 if os.path.isfile(egginfo):
1207 os.remove(egginfo)
1208 else:
1209 shutil.rmtree(egginfo)
1210
1211
1212def _has_egg_info(srcdir):
1213 if os.path.isdir(srcdir):
1214 for item in os.listdir(srcdir):
1215 full_path = os.path.join(srcdir, item)
1216 if item.endswith('.egg-info') and os.path.isdir(full_path):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001217 logger.debug("Found egg-info directory.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001218 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001219 logger.debug("No egg-info directory found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001220 return False
1221
1222
1223def _has_setuptools_text(setup_py):
1224 return _has_text(setup_py, 'setuptools')
1225
1226
1227def _has_distutils_text(setup_py):
1228 return _has_text(setup_py, 'distutils')
1229
1230
1231def _has_text(setup_py, installer):
1232 installer_pattern = re.compile('import {0}|from {0}'.format(installer))
1233 with open(setup_py, 'r', encoding='utf-8') as setup:
1234 for line in setup:
1235 if re.search(installer_pattern, line):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001236 logger.debug("Found %s text in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001237 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001238 logger.debug("No %s text found in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001239 return False
1240
1241
1242def _has_required_metadata(setup_cfg):
1243 config = RawConfigParser()
1244 config.read([setup_cfg], encoding='utf8')
1245 return (config.has_section('metadata') and
1246 'name' in config.options('metadata') and
1247 'version' in config.options('metadata'))
1248
1249
1250def _has_pkg_info(srcdir):
1251 pkg_info = os.path.join(srcdir, 'PKG-INFO')
1252 has_pkg_info = os.path.isfile(pkg_info)
1253 if has_pkg_info:
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001254 logger.debug("PKG-INFO file found.")
1255 else:
1256 logger.debug("No PKG-INFO file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001257 return has_pkg_info
1258
1259
1260def _has_setup_py(srcdir):
1261 setup_py = os.path.join(srcdir, 'setup.py')
1262 if os.path.isfile(setup_py):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001263 logger.debug('setup.py file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001264 return True
1265 return False
1266
1267
1268def _has_setup_cfg(srcdir):
1269 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1270 if os.path.isfile(setup_cfg):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001271 logger.debug('setup.cfg file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001272 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001273 logger.debug("No setup.cfg file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001274 return False
1275
1276
1277def is_setuptools(path):
1278 """Check if the project is based on setuptools.
1279
1280 :param path: path to source directory containing a setup.py script.
1281
1282 Return True if the project requires setuptools to install, else False.
1283 """
1284 srcdir = os.path.abspath(path)
1285 setup_py = os.path.join(srcdir, 'setup.py')
1286
1287 return _has_setup_py(srcdir) and (_has_egg_info(srcdir) or
1288 _has_setuptools_text(setup_py))
1289
1290
1291def is_distutils(path):
1292 """Check if the project is based on distutils.
1293
1294 :param path: path to source directory containing a setup.py script.
1295
1296 Return True if the project requires distutils to install, else False.
1297 """
1298 srcdir = os.path.abspath(path)
1299 setup_py = os.path.join(srcdir, 'setup.py')
1300
1301 return _has_setup_py(srcdir) and (_has_pkg_info(srcdir) or
1302 _has_distutils_text(setup_py))
1303
1304
1305def is_packaging(path):
1306 """Check if the project is based on packaging
1307
1308 :param path: path to source directory containing a setup.cfg file.
1309
1310 Return True if the project has a valid setup.cfg, else False.
1311 """
1312 srcdir = os.path.abspath(path)
1313 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1314
1315 return _has_setup_cfg(srcdir) and _has_required_metadata(setup_cfg)
1316
1317
1318def get_install_method(path):
1319 """Check if the project is based on packaging, setuptools, or distutils
1320
1321 :param path: path to source directory containing a setup.cfg file,
1322 or setup.py.
1323
1324 Returns a string representing the best install method to use.
1325 """
1326 if is_packaging(path):
1327 return "packaging"
1328 elif is_setuptools(path):
1329 return "setuptools"
1330 elif is_distutils(path):
1331 return "distutils"
1332 else:
1333 raise InstallationException('Cannot detect install method')
1334
1335
1336# XXX to be replaced by shutil.copytree
1337def copy_tree(src, dst, preserve_mode=True, preserve_times=True,
1338 preserve_symlinks=False, update=False, verbose=True,
1339 dry_run=False):
Éric Araujof89ebdc2011-10-21 06:27:06 +02001340 # FIXME use of this function is why we get spurious logging message on
1341 # stdout when tests run; kill and replace by shuil!
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001342 from distutils.file_util import copy_file
1343
1344 if not dry_run and not os.path.isdir(src):
1345 raise PackagingFileError(
1346 "cannot copy tree '%s': not a directory" % src)
1347 try:
1348 names = os.listdir(src)
1349 except os.error as e:
1350 errstr = e[1]
1351 if dry_run:
1352 names = []
1353 else:
1354 raise PackagingFileError(
1355 "error listing files in '%s': %s" % (src, errstr))
1356
1357 if not dry_run:
1358 _mkpath(dst, verbose=verbose)
1359
1360 outputs = []
1361
1362 for n in names:
1363 src_name = os.path.join(src, n)
1364 dst_name = os.path.join(dst, n)
1365
1366 if preserve_symlinks and os.path.islink(src_name):
1367 link_dest = os.readlink(src_name)
1368 if verbose >= 1:
1369 logger.info("linking %s -> %s", dst_name, link_dest)
1370 if not dry_run:
1371 os.symlink(link_dest, dst_name)
1372 outputs.append(dst_name)
1373
1374 elif os.path.isdir(src_name):
1375 outputs.extend(
1376 copy_tree(src_name, dst_name, preserve_mode,
1377 preserve_times, preserve_symlinks, update,
1378 verbose=verbose, dry_run=dry_run))
1379 else:
1380 copy_file(src_name, dst_name, preserve_mode,
1381 preserve_times, update, verbose=verbose,
1382 dry_run=dry_run)
1383 outputs.append(dst_name)
1384
1385 return outputs
1386
1387# cache for by mkpath() -- in addition to cheapening redundant calls,
1388# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
1389_path_created = set()
1390
1391
1392# I don't use os.makedirs because a) it's new to Python 1.5.2, and
1393# b) it blows up if the directory already exists (I want to silently
1394# succeed in that case).
1395def _mkpath(name, mode=0o777, verbose=True, dry_run=False):
1396 # Detect a common bug -- name is None
1397 if not isinstance(name, str):
1398 raise PackagingInternalError(
1399 "mkpath: 'name' must be a string (got %r)" % (name,))
1400
1401 # XXX what's the better way to handle verbosity? print as we create
1402 # each directory in the path (the current behaviour), or only announce
1403 # the creation of the whole path? (quite easy to do the latter since
1404 # we're not using a recursive algorithm)
1405
1406 name = os.path.normpath(name)
1407 created_dirs = []
1408 if os.path.isdir(name) or name == '':
1409 return created_dirs
1410 if os.path.abspath(name) in _path_created:
1411 return created_dirs
1412
1413 head, tail = os.path.split(name)
1414 tails = [tail] # stack of lone dirs to create
1415
1416 while head and tail and not os.path.isdir(head):
1417 head, tail = os.path.split(head)
1418 tails.insert(0, tail) # push next higher dir onto stack
1419
1420 # now 'head' contains the deepest directory that already exists
1421 # (that is, the child of 'head' in 'name' is the highest directory
1422 # that does *not* exist)
1423 for d in tails:
1424 head = os.path.join(head, d)
1425 abs_head = os.path.abspath(head)
1426
1427 if abs_head in _path_created:
1428 continue
1429
1430 if verbose >= 1:
1431 logger.info("creating %s", head)
1432
1433 if not dry_run:
1434 try:
1435 os.mkdir(head, mode)
1436 except OSError as exc:
1437 if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
1438 raise PackagingFileError(
1439 "could not create '%s': %s" % (head, exc.args[-1]))
1440 created_dirs.append(head)
1441
1442 _path_created.add(abs_head)
1443 return created_dirs
Éric Araujoce5fe832011-07-08 16:27:12 +02001444
1445
1446def encode_multipart(fields, files, boundary=None):
1447 """Prepare a multipart HTTP request.
1448
1449 *fields* is a sequence of (name: str, value: str) elements for regular
1450 form fields, *files* is a sequence of (name: str, filename: str, value:
1451 bytes) elements for data to be uploaded as files.
1452
1453 Returns (content_type: bytes, body: bytes) ready for http.client.HTTP.
1454 """
1455 # Taken from
1456 # http://code.activestate.com/recipes/146306-http-client-to-post-using-multipartform-data/
1457
1458 if boundary is None:
1459 boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
1460 elif not isinstance(boundary, bytes):
1461 raise TypeError('boundary must be bytes, not %r' % type(boundary))
1462
1463 l = []
1464 for key, values in fields:
1465 # handle multiple entries for the same name
1466 if not isinstance(values, (tuple, list)):
Éric Araujo95fc53f2011-09-01 05:11:29 +02001467 values = [values]
Éric Araujoce5fe832011-07-08 16:27:12 +02001468
1469 for value in values:
1470 l.extend((
1471 b'--' + boundary,
1472 ('Content-Disposition: form-data; name="%s"' %
1473 key).encode('utf-8'),
1474 b'',
1475 value.encode('utf-8')))
1476
1477 for key, filename, value in files:
1478 l.extend((
1479 b'--' + boundary,
1480 ('Content-Disposition: form-data; name="%s"; filename="%s"' %
1481 (key, filename)).encode('utf-8'),
1482 b'',
1483 value))
1484
1485 l.append(b'--' + boundary + b'--')
1486 l.append(b'')
1487
1488 body = b'\r\n'.join(l)
1489 content_type = b'multipart/form-data; boundary=' + boundary
1490 return content_type, body