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