blob: a1f6782c7bc6cec036d8a513eeabaad624936945 [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
Éric Araujo4d155462011-11-15 11:43:20 +0100262def execute(func, args, msg=None, dry_run=False):
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200263 """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,
Éric Araujof8361622011-11-14 18:10:19 +0100299 base_dir=None, dry_run=False, direct=None):
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200300 """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")
Éric Araujof8361622011-11-14 18:10:19 +0100308 This function is independent from the running Python's -O or -B options;
309 it is fully controlled by the parameters passed in.
310
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200311 If 'force' is true, all files are recompiled regardless of
312 timestamps.
313
314 The source filename encoded in each bytecode file defaults to the
315 filenames listed in 'py_files'; you can modify these with 'prefix' and
316 'basedir'. 'prefix' is a string that will be stripped off of each
317 source filename, and 'base_dir' is a directory name that will be
318 prepended (after 'prefix' is stripped). You can supply either or both
319 (or neither) of 'prefix' and 'base_dir', as you wish.
320
321 If 'dry_run' is true, doesn't actually do anything that would
322 affect the filesystem.
323
324 Byte-compilation is either done directly in this interpreter process
325 with the standard py_compile module, or indirectly by writing a
326 temporary script and executing it. Normally, you should let
327 'byte_compile()' figure out to use direct compilation or not (see
328 the source for details). The 'direct' flag is used by the script
329 generated in indirect mode; unless you know what you're doing, leave
330 it set to None.
Éric Araujo88080152011-11-03 05:08:28 +0100331 """
Éric Araujof8361622011-11-14 18:10:19 +0100332 # FIXME use compileall + remove direct/indirect shenanigans
333
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200334 # 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,
Éric Araujof8361622011-11-14 18:10:19 +0100386 dry_run=False,
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200387 direct=True)
Éric Araujof8361622011-11-14 18:10:19 +0100388""" % (optimize, force, prefix, base_dir))
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200389
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200390 cmd = [sys.executable, script_name]
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200391
Éric Araujo088025f2011-06-04 18:45:40 +0200392 env = os.environ.copy()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200393 env['PYTHONPATH'] = os.path.pathsep.join(sys.path)
394 try:
395 spawn(cmd, env=env)
396 finally:
397 execute(os.remove, (script_name,), "removing %s" % script_name,
398 dry_run=dry_run)
399
400 # "Direct" byte-compilation: use the py_compile module to compile
401 # right here, right now. Note that the script generated in indirect
402 # mode simply calls 'byte_compile()' in direct mode, a weird sort of
403 # cross-process recursion. Hey, it works!
404 else:
405 from py_compile import compile
406
407 for file in py_files:
408 if file[-3:] != ".py":
409 # This lets us be lazy and not filter filenames in
410 # the "install_lib" command.
411 continue
412
413 # Terminology from the py_compile module:
414 # cfile - byte-compiled file
415 # dfile - purported source filename (same as 'file' by default)
Éric Araujof8361622011-11-14 18:10:19 +0100416 # The second argument to cache_from_source forces the extension to
417 # be .pyc (if true) or .pyo (if false); without it, the extension
418 # would depend on the calling Python's -O option
419 cfile = imp.cache_from_source(file, not optimize)
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
Éric Araujo4d155462011-11-15 11:43:20 +0100684def spawn(cmd, search_path=True, dry_run=False, env=None):
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200685 """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
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200856 fixers = get_fixers_from_package('lib2to3.fixes')
857
858 if fixer_names:
859 for fixername in fixer_names:
Éric Araujo692a4932012-02-09 21:37:14 +0100860 fixers.extend(get_fixers_from_package(fixername))
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200861 r = RefactoringTool(fixers, options=options)
862 r.refactor(files, write=True, doctests_only=doctests_only)
863
864
865class Mixin2to3:
866 """ Wrapper class for commands that run 2to3.
867 To configure 2to3, setup scripts may either change
868 the class variables, or inherit from this class
869 to override how 2to3 is invoked.
870 """
Éric Araujo692a4932012-02-09 21:37:14 +0100871 # list of fixers to run; defaults to all implicit from lib2to3.fixers
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200872 fixer_names = None
Éric Araujo692a4932012-02-09 21:37:14 +0100873 # dict of options
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200874 options = None
Éric Araujo692a4932012-02-09 21:37:14 +0100875 # list of extra fixers to invoke
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200876 explicit = None
Éric Araujo692a4932012-02-09 21:37:14 +0100877 # TODO need a better way to add just one fixer from a package
878 # TODO need a way to exclude individual fixers
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200879
880 def run_2to3(self, files, doctests_only=False):
881 """ Issues a call to util.run_2to3. """
882 return run_2to3(files, doctests_only, self.fixer_names,
883 self.options, self.explicit)
884
Éric Araujo692a4932012-02-09 21:37:14 +0100885 # TODO provide initialize/finalize_options
886
887
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200888RICH_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
Éric Araujof89ebdc2011-10-21 06:27:06 +02001052import codecs
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001053from distutils.core import setup
Éric Araujof89ebdc2011-10-21 06:27:06 +02001054try:
1055 from ConfigParser import RawConfigParser
1056except ImportError:
1057 from configparser import RawConfigParser
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001058
Éric Araujoac03a2b2012-02-09 21:17:46 +01001059
É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,
Éric Araujo4d155462011-11-15 11:43:20 +01001334 preserve_symlinks=False, update=False, dry_run=False):
Éric Araujof89ebdc2011-10-21 06:27:06 +02001335 # FIXME use of this function is why we get spurious logging message on
Éric Araujof8361622011-11-14 18:10:19 +01001336 # stdout when tests run; kill and replace by shutil!
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001337 from distutils.file_util import copy_file
1338
1339 if not dry_run and not os.path.isdir(src):
1340 raise PackagingFileError(
1341 "cannot copy tree '%s': not a directory" % src)
1342 try:
1343 names = os.listdir(src)
1344 except os.error as e:
1345 errstr = e[1]
1346 if dry_run:
1347 names = []
1348 else:
1349 raise PackagingFileError(
1350 "error listing files in '%s': %s" % (src, errstr))
1351
1352 if not dry_run:
Éric Araujo4d155462011-11-15 11:43:20 +01001353 _mkpath(dst)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001354
1355 outputs = []
1356
1357 for n in names:
1358 src_name = os.path.join(src, n)
1359 dst_name = os.path.join(dst, n)
1360
1361 if preserve_symlinks and os.path.islink(src_name):
1362 link_dest = os.readlink(src_name)
Éric Araujo4d155462011-11-15 11:43:20 +01001363 logger.info("linking %s -> %s", dst_name, link_dest)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001364 if not dry_run:
1365 os.symlink(link_dest, dst_name)
1366 outputs.append(dst_name)
1367
1368 elif os.path.isdir(src_name):
1369 outputs.extend(
1370 copy_tree(src_name, dst_name, preserve_mode,
1371 preserve_times, preserve_symlinks, update,
Éric Araujo4d155462011-11-15 11:43:20 +01001372 dry_run=dry_run))
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001373 else:
1374 copy_file(src_name, dst_name, preserve_mode,
Éric Araujo4d155462011-11-15 11:43:20 +01001375 preserve_times, update, dry_run=dry_run)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001376 outputs.append(dst_name)
1377
1378 return outputs
1379
1380# cache for by mkpath() -- in addition to cheapening redundant calls,
1381# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
1382_path_created = set()
1383
1384
1385# I don't use os.makedirs because a) it's new to Python 1.5.2, and
1386# b) it blows up if the directory already exists (I want to silently
1387# succeed in that case).
Éric Araujo4d155462011-11-15 11:43:20 +01001388def _mkpath(name, mode=0o777, dry_run=False):
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001389 # Detect a common bug -- name is None
1390 if not isinstance(name, str):
1391 raise PackagingInternalError(
1392 "mkpath: 'name' must be a string (got %r)" % (name,))
1393
1394 # XXX what's the better way to handle verbosity? print as we create
1395 # each directory in the path (the current behaviour), or only announce
1396 # the creation of the whole path? (quite easy to do the latter since
1397 # we're not using a recursive algorithm)
1398
1399 name = os.path.normpath(name)
1400 created_dirs = []
1401 if os.path.isdir(name) or name == '':
1402 return created_dirs
1403 if os.path.abspath(name) in _path_created:
1404 return created_dirs
1405
1406 head, tail = os.path.split(name)
1407 tails = [tail] # stack of lone dirs to create
1408
1409 while head and tail and not os.path.isdir(head):
1410 head, tail = os.path.split(head)
1411 tails.insert(0, tail) # push next higher dir onto stack
1412
1413 # now 'head' contains the deepest directory that already exists
1414 # (that is, the child of 'head' in 'name' is the highest directory
1415 # that does *not* exist)
1416 for d in tails:
1417 head = os.path.join(head, d)
1418 abs_head = os.path.abspath(head)
1419
1420 if abs_head in _path_created:
1421 continue
1422
Éric Araujo4d155462011-11-15 11:43:20 +01001423 logger.info("creating %s", head)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001424 if not dry_run:
1425 try:
1426 os.mkdir(head, mode)
1427 except OSError as exc:
1428 if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
1429 raise PackagingFileError(
1430 "could not create '%s': %s" % (head, exc.args[-1]))
1431 created_dirs.append(head)
1432
1433 _path_created.add(abs_head)
1434 return created_dirs
Éric Araujoce5fe832011-07-08 16:27:12 +02001435
1436
1437def encode_multipart(fields, files, boundary=None):
1438 """Prepare a multipart HTTP request.
1439
1440 *fields* is a sequence of (name: str, value: str) elements for regular
1441 form fields, *files* is a sequence of (name: str, filename: str, value:
1442 bytes) elements for data to be uploaded as files.
1443
1444 Returns (content_type: bytes, body: bytes) ready for http.client.HTTP.
1445 """
Éric Araujof8361622011-11-14 18:10:19 +01001446 # Taken from http://code.activestate.com/recipes/146306
Éric Araujoce5fe832011-07-08 16:27:12 +02001447
1448 if boundary is None:
1449 boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
1450 elif not isinstance(boundary, bytes):
1451 raise TypeError('boundary must be bytes, not %r' % type(boundary))
1452
1453 l = []
1454 for key, values in fields:
1455 # handle multiple entries for the same name
1456 if not isinstance(values, (tuple, list)):
Éric Araujo95fc53f2011-09-01 05:11:29 +02001457 values = [values]
Éric Araujoce5fe832011-07-08 16:27:12 +02001458
1459 for value in values:
1460 l.extend((
1461 b'--' + boundary,
1462 ('Content-Disposition: form-data; name="%s"' %
1463 key).encode('utf-8'),
1464 b'',
1465 value.encode('utf-8')))
1466
1467 for key, filename, value in files:
1468 l.extend((
1469 b'--' + boundary,
1470 ('Content-Disposition: form-data; name="%s"; filename="%s"' %
1471 (key, filename)).encode('utf-8'),
1472 b'',
1473 value))
1474
1475 l.append(b'--' + boundary + b'--')
1476 l.append(b'')
1477
1478 body = b'\r\n'.join(l)
1479 content_type = b'multipart/form-data; boundary=' + boundary
1480 return content_type, body