blob: 89f5389be9056b83d360a501ac66dbdec5d19704 [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
Éric Araujo7724a6c2011-09-17 03:31:51 +0200329 if sys.dont_write_bytecode:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200330 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
Éric Araujo7724a6c2011-09-17 03:31:51 +0200349 # XXX use something better than mkstemp
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200350 script_fd, script_name = mkstemp(".py")
Éric Araujo7724a6c2011-09-17 03:31:51 +0200351 os.close(script_fd)
352 script_fd = None
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200353 logger.info("writing byte-compilation script '%s'", script_name)
354 if not dry_run:
355 if script_fd is not None:
Victor Stinner9cf6d132011-05-19 21:42:47 +0200356 script = os.fdopen(script_fd, "w", encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200357 else:
Victor Stinner9cf6d132011-05-19 21:42:47 +0200358 script = open(script_name, "w", encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200359
Victor Stinner21a9c742011-05-19 15:51:27 +0200360 with script:
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200361 script.write("""\
362from packaging.util import byte_compile
363files = [
364""")
365
366 # XXX would be nice to write absolute filenames, just for
367 # safety's sake (script should be more robust in the face of
368 # chdir'ing before running it). But this requires abspath'ing
369 # 'prefix' as well, and that breaks the hack in build_lib's
370 # 'byte_compile()' method that carefully tacks on a trailing
371 # slash (os.sep really) to make sure the prefix here is "just
372 # right". This whole prefix business is rather delicate -- the
373 # problem is that it's really a directory, but I'm treating it
374 # as a dumb string, so trailing slashes and so forth matter.
375
376 #py_files = map(os.path.abspath, py_files)
377 #if prefix:
378 # prefix = os.path.abspath(prefix)
379
380 script.write(",\n".join(map(repr, py_files)) + "]\n")
381 script.write("""
382byte_compile(files, optimize=%r, force=%r,
383 prefix=%r, base_dir=%r,
384 verbose=%r, dry_run=False,
385 direct=True)
386""" % (optimize, force, prefix, base_dir, verbose))
387
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200388 cmd = [sys.executable, script_name]
389 if optimize == 1:
390 cmd.insert(1, "-O")
391 elif optimize == 2:
392 cmd.insert(1, "-OO")
393
Éric Araujo088025f2011-06-04 18:45:40 +0200394 env = os.environ.copy()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200395 env['PYTHONPATH'] = os.path.pathsep.join(sys.path)
396 try:
397 spawn(cmd, env=env)
398 finally:
399 execute(os.remove, (script_name,), "removing %s" % script_name,
400 dry_run=dry_run)
401
402 # "Direct" byte-compilation: use the py_compile module to compile
403 # right here, right now. Note that the script generated in indirect
404 # mode simply calls 'byte_compile()' in direct mode, a weird sort of
405 # cross-process recursion. Hey, it works!
406 else:
407 from py_compile import compile
408
409 for file in py_files:
410 if file[-3:] != ".py":
411 # This lets us be lazy and not filter filenames in
412 # the "install_lib" command.
413 continue
414
415 # Terminology from the py_compile module:
416 # cfile - byte-compiled file
417 # dfile - purported source filename (same as 'file' by default)
418 cfile = file + (__debug__ and "c" or "o")
419 dfile = file
420 if prefix:
421 if file[:len(prefix)] != prefix:
422 raise ValueError("invalid prefix: filename %r doesn't "
423 "start with %r" % (file, prefix))
424 dfile = dfile[len(prefix):]
425 if base_dir:
426 dfile = os.path.join(base_dir, dfile)
427
428 cfile_base = os.path.basename(cfile)
429 if direct:
430 if force or newer(file, cfile):
431 logger.info("byte-compiling %s to %s", file, cfile_base)
432 if not dry_run:
433 compile(file, cfile, dfile)
434 else:
435 logger.debug("skipping byte-compilation of %s to %s",
436 file, cfile_base)
437
438
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200439_RE_VERSION = re.compile('(\d+\.\d+(\.\d+)*)')
440_MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld '
441 'PROJECT:ld64-((\d+)(\.\d+)*)')
442
443
444def _find_ld_version():
445 """Find the ld version. The version scheme differs under Mac OS X."""
446 if sys.platform == 'darwin':
447 return _find_exe_version('ld -v', _MAC_OS_X_LD_VERSION)
448 else:
449 return _find_exe_version('ld -v')
450
451
452def _find_exe_version(cmd, pattern=_RE_VERSION):
453 """Find the version of an executable by running `cmd` in the shell.
454
455 `pattern` is a compiled regular expression. If not provided, defaults
456 to _RE_VERSION. If the command is not found, or the output does not
457 match the mattern, returns None.
458 """
459 from subprocess import Popen, PIPE
460 executable = cmd.split()[0]
461 if find_executable(executable) is None:
462 return None
463 pipe = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
464 try:
Victor Stinner9904b222011-05-21 02:20:36 +0200465 stdout, stderr = pipe.communicate()
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200466 finally:
467 pipe.stdout.close()
468 pipe.stderr.close()
469 # some commands like ld under MacOS X, will give the
470 # output in the stderr, rather than stdout.
471 if stdout != '':
472 out_string = stdout
473 else:
474 out_string = stderr
475
476 result = pattern.search(out_string)
477 if result is None:
478 return None
479 return result.group(1)
480
481
482def get_compiler_versions():
483 """Return a tuple providing the versions of gcc, ld and dllwrap
484
485 For each command, if a command is not found, None is returned.
486 Otherwise a string with the version is returned.
487 """
488 gcc = _find_exe_version('gcc -dumpversion')
489 ld = _find_ld_version()
490 dllwrap = _find_exe_version('dllwrap --version')
491 return gcc, ld, dllwrap
492
493
494def newer_group(sources, target, missing='error'):
495 """Return true if 'target' is out-of-date with respect to any file
496 listed in 'sources'.
497
498 In other words, if 'target' exists and is newer
499 than every file in 'sources', return false; otherwise return true.
500 'missing' controls what we do when a source file is missing; the
501 default ("error") is to blow up with an OSError from inside 'stat()';
502 if it is "ignore", we silently drop any missing source files; if it is
503 "newer", any missing source files make us assume that 'target' is
504 out-of-date (this is handy in "dry-run" mode: it'll make you pretend to
505 carry out commands that wouldn't work because inputs are missing, but
506 that doesn't matter because you're not actually going to run the
507 commands).
508 """
509 # If the target doesn't even exist, then it's definitely out-of-date.
510 if not os.path.exists(target):
511 return True
512
513 # Otherwise we have to find out the hard way: if *any* source file
514 # is more recent than 'target', then 'target' is out-of-date and
515 # we can immediately return true. If we fall through to the end
516 # of the loop, then 'target' is up-to-date and we return false.
517 target_mtime = os.stat(target).st_mtime
518
519 for source in sources:
520 if not os.path.exists(source):
521 if missing == 'error': # blow up when we stat() the file
522 pass
523 elif missing == 'ignore': # missing source dropped from
524 continue # target's dependency list
525 elif missing == 'newer': # missing source means target is
526 return True # out-of-date
527
528 if os.stat(source).st_mtime > target_mtime:
529 return True
530
531 return False
532
533
534def write_file(filename, contents):
535 """Create *filename* and write *contents* to it.
536
537 *contents* is a sequence of strings without line terminators.
Éric Araujo95fc53f2011-09-01 05:11:29 +0200538
539 This functions is not intended to replace the usual with open + write
540 idiom in all cases, only with Command.execute, which runs depending on
541 the dry_run argument and also logs its arguments).
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200542 """
543 with open(filename, "w") as f:
544 for line in contents:
545 f.write(line + "\n")
546
547
548def _is_package(path):
Éric Araujo1c1d9a52011-06-10 23:26:31 +0200549 return os.path.isdir(path) and os.path.isfile(
550 os.path.join(path, '__init__.py'))
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200551
552
553# Code taken from the pip project
554def _is_archive_file(name):
555 archives = ('.zip', '.tar.gz', '.tar.bz2', '.tgz', '.tar')
556 ext = splitext(name)[1].lower()
Éric Araujo1c1d9a52011-06-10 23:26:31 +0200557 return ext in archives
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200558
559
560def _under(path, root):
Éric Araujo95fc53f2011-09-01 05:11:29 +0200561 # XXX use os.path
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200562 path = path.split(os.sep)
563 root = root.split(os.sep)
564 if len(root) > len(path):
565 return False
566 for pos, part in enumerate(root):
567 if path[pos] != part:
568 return False
569 return True
570
571
572def _package_name(root_path, path):
573 # Return a dotted package name, given a subpath
574 if not _under(path, root_path):
575 raise ValueError('"%s" is not a subpath of "%s"' % (path, root_path))
576 return path[len(root_path) + 1:].replace(os.sep, '.')
577
578
579def find_packages(paths=(os.curdir,), exclude=()):
580 """Return a list all Python packages found recursively within
581 directories 'paths'
582
583 'paths' should be supplied as a sequence of "cross-platform"
584 (i.e. URL-style) path; it will be converted to the appropriate local
585 path syntax.
586
587 'exclude' is a sequence of package names to exclude; '*' can be used as
588 a wildcard in the names, such that 'foo.*' will exclude all subpackages
589 of 'foo' (but not 'foo' itself).
590 """
591 packages = []
592 discarded = []
593
594 def _discarded(path):
595 for discard in discarded:
596 if _under(path, discard):
597 return True
598 return False
599
600 for path in paths:
601 path = convert_path(path)
602 for root, dirs, files in os.walk(path):
603 for dir_ in dirs:
604 fullpath = os.path.join(root, dir_)
605 if _discarded(fullpath):
606 continue
607 # we work only with Python packages
608 if not _is_package(fullpath):
609 discarded.append(fullpath)
610 continue
611 # see if it's excluded
612 excluded = False
613 package_name = _package_name(path, fullpath)
614 for pattern in exclude:
615 if fnmatchcase(package_name, pattern):
616 excluded = True
617 break
618 if excluded:
619 continue
620
621 # adding it to the list
622 packages.append(package_name)
623 return packages
624
625
626def resolve_name(name):
627 """Resolve a name like ``module.object`` to an object and return it.
628
629 Raise ImportError if the module or name is not found.
630 """
631 parts = name.split('.')
632 cursor = len(parts)
633 module_name = parts[:cursor]
634
635 while cursor > 0:
636 try:
637 ret = __import__('.'.join(module_name))
638 break
639 except ImportError:
640 if cursor == 0:
641 raise
642 cursor -= 1
643 module_name = parts[:cursor]
644 ret = ''
645
646 for part in parts[1:]:
647 try:
648 ret = getattr(ret, part)
649 except AttributeError as exc:
650 raise ImportError(exc)
651
652 return ret
653
654
655def splitext(path):
656 """Like os.path.splitext, but take off .tar too"""
657 base, ext = posixpath.splitext(path)
658 if base.lower().endswith('.tar'):
659 ext = base[-4:] + ext
660 base = base[:-4]
661 return base, ext
662
663
Ned Deilyfceb4122011-06-28 20:04:24 -0700664if sys.platform == 'darwin':
665 _cfg_target = None
666 _cfg_target_split = None
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200667
Éric Araujo95fc53f2011-09-01 05:11:29 +0200668
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200669def spawn(cmd, search_path=True, verbose=0, dry_run=False, env=None):
670 """Run another program specified as a command list 'cmd' in a new process.
671
672 'cmd' is just the argument list for the new process, ie.
673 cmd[0] is the program to run and cmd[1:] are the rest of its arguments.
674 There is no way to run a program with a name different from that of its
675 executable.
676
677 If 'search_path' is true (the default), the system's executable
678 search path will be used to find the program; otherwise, cmd[0]
679 must be the exact path to the executable. If 'dry_run' is true,
680 the command will not actually be run.
681
682 If 'env' is given, it's a environment dictionary used for the execution
683 environment.
684
685 Raise PackagingExecError if running the program fails in any way; just
686 return on success.
687 """
Éric Araujo62806062011-06-11 09:46:07 +0200688 logger.debug('spawn: running %r', cmd)
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200689 if dry_run:
Éric Araujo29f62972011-08-04 17:17:07 +0200690 logger.debug('dry run, no process actually spawned')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200691 return
Ned Deilyfceb4122011-06-28 20:04:24 -0700692 if sys.platform == 'darwin':
693 global _cfg_target, _cfg_target_split
694 if _cfg_target is None:
695 _cfg_target = sysconfig.get_config_var(
696 'MACOSX_DEPLOYMENT_TARGET') or ''
697 if _cfg_target:
698 _cfg_target_split = [int(x) for x in _cfg_target.split('.')]
699 if _cfg_target:
700 # ensure that the deployment target of build process is not less
701 # than that used when the interpreter was built. This ensures
702 # extension modules are built with correct compatibility values
703 env = env or os.environ
704 cur_target = env.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target)
705 if _cfg_target_split > [int(x) for x in cur_target.split('.')]:
706 my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: '
707 'now "%s" but "%s" during configure'
708 % (cur_target, _cfg_target))
709 raise PackagingPlatformError(my_msg)
710 env = dict(env, MACOSX_DEPLOYMENT_TARGET=cur_target)
711
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200712 exit_status = subprocess.call(cmd, env=env)
713 if exit_status != 0:
Éric Araujo62806062011-06-11 09:46:07 +0200714 msg = "command %r failed with exit status %d"
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200715 raise PackagingExecError(msg % (cmd, exit_status))
716
717
718def find_executable(executable, path=None):
719 """Try to find 'executable' in the directories listed in 'path'.
720
721 *path* is a string listing directories separated by 'os.pathsep' and
722 defaults to os.environ['PATH']. Returns the complete filename or None
723 if not found.
724 """
725 if path is None:
726 path = os.environ['PATH']
727 paths = path.split(os.pathsep)
728 base, ext = os.path.splitext(executable)
729
730 if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'):
731 executable = executable + '.exe'
732
733 if not os.path.isfile(executable):
734 for p in paths:
735 f = os.path.join(p, executable)
736 if os.path.isfile(f):
737 # the file exists, we have a shot at spawn working
738 return f
739 return None
740 else:
741 return executable
742
743
744DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
745DEFAULT_REALM = 'pypi'
746DEFAULT_PYPIRC = """\
747[distutils]
748index-servers =
749 pypi
750
751[pypi]
752username:%s
753password:%s
754"""
755
756
757def get_pypirc_path():
758 """Return path to pypirc config file."""
759 return os.path.join(os.path.expanduser('~'), '.pypirc')
760
761
762def generate_pypirc(username, password):
763 """Create a default .pypirc file."""
764 rc = get_pypirc_path()
765 with open(rc, 'w') as f:
766 f.write(DEFAULT_PYPIRC % (username, password))
767 try:
768 os.chmod(rc, 0o600)
769 except OSError:
770 # should do something better here
771 pass
772
773
774def read_pypirc(repository=DEFAULT_REPOSITORY, realm=DEFAULT_REALM):
775 """Read the .pypirc file."""
776 rc = get_pypirc_path()
777 if os.path.exists(rc):
778 config = RawConfigParser()
779 config.read(rc)
780 sections = config.sections()
781 if 'distutils' in sections:
782 # let's get the list of servers
783 index_servers = config.get('distutils', 'index-servers')
784 _servers = [server.strip() for server in
785 index_servers.split('\n')
786 if server.strip() != '']
787 if _servers == []:
788 # nothing set, let's try to get the default pypi
789 if 'pypi' in sections:
790 _servers = ['pypi']
791 else:
792 # the file is not properly defined, returning
793 # an empty dict
794 return {}
795 for server in _servers:
796 current = {'server': server}
797 current['username'] = config.get(server, 'username')
798
799 # optional params
800 for key, default in (('repository', DEFAULT_REPOSITORY),
801 ('realm', DEFAULT_REALM),
802 ('password', None)):
803 if config.has_option(server, key):
804 current[key] = config.get(server, key)
805 else:
806 current[key] = default
807 if (current['server'] == repository or
808 current['repository'] == repository):
809 return current
810 elif 'server-login' in sections:
811 # old format
812 server = 'server-login'
813 if config.has_option(server, 'repository'):
814 repository = config.get(server, 'repository')
815 else:
816 repository = DEFAULT_REPOSITORY
817
818 return {'username': config.get(server, 'username'),
819 'password': config.get(server, 'password'),
820 'repository': repository,
821 'server': server,
822 'realm': DEFAULT_REALM}
823
824 return {}
825
826
827# utility functions for 2to3 support
828
829def run_2to3(files, doctests_only=False, fixer_names=None,
830 options=None, explicit=None):
831 """ Wrapper function around the refactor() class which
832 performs the conversions on a list of python files.
833 Invoke 2to3 on a list of Python files. The files should all come
834 from the build area, as the modification is done in-place."""
835
836 #if not files:
837 # return
838
839 # Make this class local, to delay import of 2to3
840 from lib2to3.refactor import get_fixers_from_package, RefactoringTool
841 fixers = []
842 fixers = get_fixers_from_package('lib2to3.fixes')
843
844 if fixer_names:
845 for fixername in fixer_names:
846 fixers.extend(fixer for fixer in
847 get_fixers_from_package(fixername))
848 r = RefactoringTool(fixers, options=options)
849 r.refactor(files, write=True, doctests_only=doctests_only)
850
851
852class Mixin2to3:
853 """ Wrapper class for commands that run 2to3.
854 To configure 2to3, setup scripts may either change
855 the class variables, or inherit from this class
856 to override how 2to3 is invoked.
857 """
858 # provide list of fixers to run.
859 # defaults to all from lib2to3.fixers
860 fixer_names = None
861
862 # options dictionary
863 options = None
864
865 # list of fixers to invoke even though they are marked as explicit
866 explicit = None
867
868 def run_2to3(self, files, doctests_only=False):
869 """ Issues a call to util.run_2to3. """
870 return run_2to3(files, doctests_only, self.fixer_names,
871 self.options, self.explicit)
872
873RICH_GLOB = re.compile(r'\{([^}]*)\}')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200874_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200875_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
876
877
878def iglob(path_glob):
879 """Extended globbing function that supports ** and {opt1,opt2,opt3}."""
880 if _CHECK_RECURSIVE_GLOB.search(path_glob):
881 msg = """invalid glob %r: recursive glob "**" must be used alone"""
882 raise ValueError(msg % path_glob)
883 if _CHECK_MISMATCH_SET.search(path_glob):
884 msg = """invalid glob %r: mismatching set marker '{' or '}'"""
885 raise ValueError(msg % path_glob)
886 return _iglob(path_glob)
887
888
889def _iglob(path_glob):
890 rich_path_glob = RICH_GLOB.split(path_glob, 1)
891 if len(rich_path_glob) > 1:
892 assert len(rich_path_glob) == 3, rich_path_glob
893 prefix, set, suffix = rich_path_glob
894 for item in set.split(','):
895 for path in _iglob(''.join((prefix, item, suffix))):
896 yield path
897 else:
898 if '**' not in path_glob:
899 for item in std_iglob(path_glob):
900 yield item
901 else:
902 prefix, radical = path_glob.split('**', 1)
903 if prefix == '':
904 prefix = '.'
905 if radical == '':
906 radical = '*'
907 else:
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200908 # we support both
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200909 radical = radical.lstrip('/')
Tarek Ziadeec9b76d2011-05-21 11:48:16 +0200910 radical = radical.lstrip('\\')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200911 for path, dir, files in os.walk(prefix):
912 path = os.path.normpath(path)
913 for file in _iglob(os.path.join(path, radical)):
914 yield file
915
916
917def cfg_to_args(path='setup.cfg'):
918 """Compatibility helper to use setup.cfg in setup.py.
919
920 This functions uses an existing setup.cfg to generate a dictionnary of
921 keywords that can be used by distutils.core.setup(**kwargs). It is used
922 by generate_setup_py.
923
924 *file* is the path to the setup.cfg file. If it doesn't exist,
925 PackagingFileError is raised.
926 """
927 # We need to declare the following constants here so that it's easier to
928 # generate the setup.py afterwards, using inspect.getsource.
929
930 # XXX ** == needs testing
931 D1_D2_SETUP_ARGS = {"name": ("metadata",),
932 "version": ("metadata",),
933 "author": ("metadata",),
934 "author_email": ("metadata",),
935 "maintainer": ("metadata",),
936 "maintainer_email": ("metadata",),
937 "url": ("metadata", "home_page"),
938 "description": ("metadata", "summary"),
939 "long_description": ("metadata", "description"),
940 "download-url": ("metadata",),
941 "classifiers": ("metadata", "classifier"),
942 "platforms": ("metadata", "platform"), # **
943 "license": ("metadata",),
944 "requires": ("metadata", "requires_dist"),
945 "provides": ("metadata", "provides_dist"), # **
946 "obsoletes": ("metadata", "obsoletes_dist"), # **
Éric Araujo36050302011-06-10 23:52:26 +0200947 "package_dir": ("files", 'packages_root'),
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200948 "packages": ("files",),
949 "scripts": ("files",),
950 "py_modules": ("files", "modules"), # **
951 }
952
953 MULTI_FIELDS = ("classifiers",
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200954 "platforms",
Éric Araujo36050302011-06-10 23:52:26 +0200955 "requires",
956 "provides",
957 "obsoletes",
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200958 "packages",
Éric Araujo36050302011-06-10 23:52:26 +0200959 "scripts",
960 "py_modules")
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200961
962 def has_get_option(config, section, option):
963 if config.has_option(section, option):
964 return config.get(section, option)
965 elif config.has_option(section, option.replace('_', '-')):
966 return config.get(section, option.replace('_', '-'))
967 else:
968 return False
969
970 # The real code starts here
971 config = RawConfigParser()
Éric Araujo36050302011-06-10 23:52:26 +0200972 if not os.path.exists(path):
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200973 raise PackagingFileError("file '%s' does not exist" %
Éric Araujo36050302011-06-10 23:52:26 +0200974 os.path.abspath(path))
Éric Araujo8d233f22011-06-12 23:02:57 +0200975 config.read(path, encoding='utf-8')
Tarek Ziade1231a4e2011-05-19 13:07:25 +0200976
977 kwargs = {}
978 for arg in D1_D2_SETUP_ARGS:
979 if len(D1_D2_SETUP_ARGS[arg]) == 2:
980 # The distutils field name is different than packaging's
981 section, option = D1_D2_SETUP_ARGS[arg]
982
983 else:
984 # The distutils field name is the same thant packaging's
985 section = D1_D2_SETUP_ARGS[arg][0]
986 option = arg
987
988 in_cfg_value = has_get_option(config, section, option)
989 if not in_cfg_value:
990 # There is no such option in the setup.cfg
Éric Araujo36050302011-06-10 23:52:26 +0200991 if arg == 'long_description':
992 filenames = has_get_option(config, section, 'description-file')
993 if filenames:
994 filenames = split_multiline(filenames)
995 in_cfg_value = []
996 for filename in filenames:
997 with open(filename) as fp:
998 in_cfg_value.append(fp.read())
999 in_cfg_value = '\n\n'.join(in_cfg_value)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001000 else:
1001 continue
1002
Éric Araujo36050302011-06-10 23:52:26 +02001003 if arg == 'package_dir' and in_cfg_value:
1004 in_cfg_value = {'': in_cfg_value}
1005
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001006 if arg in MULTI_FIELDS:
1007 # support multiline options
Éric Araujo36050302011-06-10 23:52:26 +02001008 in_cfg_value = split_multiline(in_cfg_value)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001009
1010 kwargs[arg] = in_cfg_value
1011
1012 return kwargs
1013
1014
1015_SETUP_TMPL = """\
1016# This script was automatically generated by packaging
1017import os
1018from distutils.core import setup
1019from ConfigParser import RawConfigParser
1020
1021%(func)s
1022
1023setup(**cfg_to_args())
1024"""
1025
1026
1027def generate_setup_py():
1028 """Generate a distutils compatible setup.py using an existing setup.cfg.
1029
1030 Raises a PackagingFileError when a setup.py already exists.
1031 """
1032 if os.path.exists("setup.py"):
Tarek Ziade721ccd02011-06-02 12:00:44 +02001033 raise PackagingFileError("a setup.py file already exists")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001034
Victor Stinner9cf6d132011-05-19 21:42:47 +02001035 with open("setup.py", "w", encoding='utf-8') as fp:
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001036 fp.write(_SETUP_TMPL % {'func': getsource(cfg_to_args)})
1037
1038
1039# Taken from the pip project
1040# https://github.com/pypa/pip/blob/master/pip/util.py
1041def ask(message, options):
1042 """Prompt the user with *message*; *options* contains allowed responses."""
1043 while True:
1044 response = input(message)
1045 response = response.strip().lower()
1046 if response not in options:
Éric Araujo3cab2f12011-06-08 04:10:57 +02001047 print('invalid response:', repr(response))
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001048 print('choose one of', ', '.join(repr(o) for o in options))
1049 else:
1050 return response
1051
1052
1053def _parse_record_file(record_file):
1054 distinfo, extra_metadata, installed = ({}, [], [])
1055 with open(record_file, 'r') as rfile:
1056 for path in rfile:
1057 path = path.strip()
1058 if path.endswith('egg-info') and os.path.isfile(path):
1059 distinfo_dir = path.replace('egg-info', 'dist-info')
1060 metadata = path
1061 egginfo = path
1062 elif path.endswith('egg-info') and os.path.isdir(path):
1063 distinfo_dir = path.replace('egg-info', 'dist-info')
1064 egginfo = path
1065 for metadata_file in os.listdir(path):
1066 metadata_fpath = os.path.join(path, metadata_file)
1067 if metadata_file == 'PKG-INFO':
1068 metadata = metadata_fpath
1069 else:
1070 extra_metadata.append(metadata_fpath)
1071 elif 'egg-info' in path and os.path.isfile(path):
1072 # skip extra metadata files
1073 continue
1074 else:
1075 installed.append(path)
1076
1077 distinfo['egginfo'] = egginfo
1078 distinfo['metadata'] = metadata
1079 distinfo['distinfo_dir'] = distinfo_dir
1080 distinfo['installer_path'] = os.path.join(distinfo_dir, 'INSTALLER')
1081 distinfo['metadata_path'] = os.path.join(distinfo_dir, 'METADATA')
1082 distinfo['record_path'] = os.path.join(distinfo_dir, 'RECORD')
1083 distinfo['requested_path'] = os.path.join(distinfo_dir, 'REQUESTED')
1084 installed.extend([distinfo['installer_path'], distinfo['metadata_path']])
1085 distinfo['installed'] = installed
1086 distinfo['extra_metadata'] = extra_metadata
1087 return distinfo
1088
1089
1090def _write_record_file(record_path, installed_files):
1091 with open(record_path, 'w', encoding='utf-8') as f:
1092 writer = csv.writer(f, delimiter=',', lineterminator=os.linesep,
1093 quotechar='"')
1094
1095 for fpath in installed_files:
1096 if fpath.endswith('.pyc') or fpath.endswith('.pyo'):
1097 # do not put size and md5 hash, as in PEP-376
1098 writer.writerow((fpath, '', ''))
1099 else:
1100 hash = hashlib.md5()
1101 with open(fpath, 'rb') as fp:
1102 hash.update(fp.read())
1103 md5sum = hash.hexdigest()
1104 size = os.path.getsize(fpath)
1105 writer.writerow((fpath, md5sum, size))
1106
1107 # add the RECORD file itself
1108 writer.writerow((record_path, '', ''))
1109 return record_path
1110
1111
1112def egginfo_to_distinfo(record_file, installer=_DEFAULT_INSTALLER,
1113 requested=False, remove_egginfo=False):
1114 """Create files and directories required for PEP 376
1115
1116 :param record_file: path to RECORD file as produced by setup.py --record
1117 :param installer: installer name
1118 :param requested: True if not installed as a dependency
1119 :param remove_egginfo: delete egginfo dir?
1120 """
1121 distinfo = _parse_record_file(record_file)
1122 distinfo_dir = distinfo['distinfo_dir']
1123 if os.path.isdir(distinfo_dir) and not os.path.islink(distinfo_dir):
1124 shutil.rmtree(distinfo_dir)
1125 elif os.path.exists(distinfo_dir):
1126 os.unlink(distinfo_dir)
1127
1128 os.makedirs(distinfo_dir)
1129
1130 # copy setuptools extra metadata files
1131 if distinfo['extra_metadata']:
1132 for path in distinfo['extra_metadata']:
1133 shutil.copy2(path, distinfo_dir)
1134 new_path = path.replace('egg-info', 'dist-info')
1135 distinfo['installed'].append(new_path)
1136
1137 metadata_path = distinfo['metadata_path']
1138 logger.info('creating %s', metadata_path)
1139 shutil.copy2(distinfo['metadata'], metadata_path)
1140
1141 installer_path = distinfo['installer_path']
1142 logger.info('creating %s', installer_path)
1143 with open(installer_path, 'w') as f:
1144 f.write(installer)
1145
1146 if requested:
1147 requested_path = distinfo['requested_path']
1148 logger.info('creating %s', requested_path)
Victor Stinner4c9706b2011-05-19 15:52:59 +02001149 open(requested_path, 'wb').close()
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001150 distinfo['installed'].append(requested_path)
1151
1152 record_path = distinfo['record_path']
1153 logger.info('creating %s', record_path)
1154 _write_record_file(record_path, distinfo['installed'])
1155
1156 if remove_egginfo:
1157 egginfo = distinfo['egginfo']
1158 logger.info('removing %s', egginfo)
1159 if os.path.isfile(egginfo):
1160 os.remove(egginfo)
1161 else:
1162 shutil.rmtree(egginfo)
1163
1164
1165def _has_egg_info(srcdir):
1166 if os.path.isdir(srcdir):
1167 for item in os.listdir(srcdir):
1168 full_path = os.path.join(srcdir, item)
1169 if item.endswith('.egg-info') and os.path.isdir(full_path):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001170 logger.debug("Found egg-info directory.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001171 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001172 logger.debug("No egg-info directory found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001173 return False
1174
1175
1176def _has_setuptools_text(setup_py):
1177 return _has_text(setup_py, 'setuptools')
1178
1179
1180def _has_distutils_text(setup_py):
1181 return _has_text(setup_py, 'distutils')
1182
1183
1184def _has_text(setup_py, installer):
1185 installer_pattern = re.compile('import {0}|from {0}'.format(installer))
1186 with open(setup_py, 'r', encoding='utf-8') as setup:
1187 for line in setup:
1188 if re.search(installer_pattern, line):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001189 logger.debug("Found %s text in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001190 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001191 logger.debug("No %s text found in setup.py.", installer)
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001192 return False
1193
1194
1195def _has_required_metadata(setup_cfg):
1196 config = RawConfigParser()
1197 config.read([setup_cfg], encoding='utf8')
1198 return (config.has_section('metadata') and
1199 'name' in config.options('metadata') and
1200 'version' in config.options('metadata'))
1201
1202
1203def _has_pkg_info(srcdir):
1204 pkg_info = os.path.join(srcdir, 'PKG-INFO')
1205 has_pkg_info = os.path.isfile(pkg_info)
1206 if has_pkg_info:
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001207 logger.debug("PKG-INFO file found.")
1208 else:
1209 logger.debug("No PKG-INFO file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001210 return has_pkg_info
1211
1212
1213def _has_setup_py(srcdir):
1214 setup_py = os.path.join(srcdir, 'setup.py')
1215 if os.path.isfile(setup_py):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001216 logger.debug('setup.py file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001217 return True
1218 return False
1219
1220
1221def _has_setup_cfg(srcdir):
1222 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1223 if os.path.isfile(setup_cfg):
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001224 logger.debug('setup.cfg file found.')
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001225 return True
Tarek Ziadeb1b6e132011-05-30 12:07:49 +02001226 logger.debug("No setup.cfg file found.")
Tarek Ziade1231a4e2011-05-19 13:07:25 +02001227 return False
1228
1229
1230def is_setuptools(path):
1231 """Check if the project is based on setuptools.
1232
1233 :param path: path to source directory containing a setup.py script.
1234
1235 Return True if the project requires setuptools to install, else False.
1236 """
1237 srcdir = os.path.abspath(path)
1238 setup_py = os.path.join(srcdir, 'setup.py')
1239
1240 return _has_setup_py(srcdir) and (_has_egg_info(srcdir) or
1241 _has_setuptools_text(setup_py))
1242
1243
1244def is_distutils(path):
1245 """Check if the project is based on distutils.
1246
1247 :param path: path to source directory containing a setup.py script.
1248
1249 Return True if the project requires distutils to install, else False.
1250 """
1251 srcdir = os.path.abspath(path)
1252 setup_py = os.path.join(srcdir, 'setup.py')
1253
1254 return _has_setup_py(srcdir) and (_has_pkg_info(srcdir) or
1255 _has_distutils_text(setup_py))
1256
1257
1258def is_packaging(path):
1259 """Check if the project is based on packaging
1260
1261 :param path: path to source directory containing a setup.cfg file.
1262
1263 Return True if the project has a valid setup.cfg, else False.
1264 """
1265 srcdir = os.path.abspath(path)
1266 setup_cfg = os.path.join(srcdir, 'setup.cfg')
1267
1268 return _has_setup_cfg(srcdir) and _has_required_metadata(setup_cfg)
1269
1270
1271def get_install_method(path):
1272 """Check if the project is based on packaging, setuptools, or distutils
1273
1274 :param path: path to source directory containing a setup.cfg file,
1275 or setup.py.
1276
1277 Returns a string representing the best install method to use.
1278 """
1279 if is_packaging(path):
1280 return "packaging"
1281 elif is_setuptools(path):
1282 return "setuptools"
1283 elif is_distutils(path):
1284 return "distutils"
1285 else:
1286 raise InstallationException('Cannot detect install method')
1287
1288
1289# XXX to be replaced by shutil.copytree
1290def copy_tree(src, dst, preserve_mode=True, preserve_times=True,
1291 preserve_symlinks=False, update=False, verbose=True,
1292 dry_run=False):
1293 from distutils.file_util import copy_file
1294
1295 if not dry_run and not os.path.isdir(src):
1296 raise PackagingFileError(
1297 "cannot copy tree '%s': not a directory" % src)
1298 try:
1299 names = os.listdir(src)
1300 except os.error as e:
1301 errstr = e[1]
1302 if dry_run:
1303 names = []
1304 else:
1305 raise PackagingFileError(
1306 "error listing files in '%s': %s" % (src, errstr))
1307
1308 if not dry_run:
1309 _mkpath(dst, verbose=verbose)
1310
1311 outputs = []
1312
1313 for n in names:
1314 src_name = os.path.join(src, n)
1315 dst_name = os.path.join(dst, n)
1316
1317 if preserve_symlinks and os.path.islink(src_name):
1318 link_dest = os.readlink(src_name)
1319 if verbose >= 1:
1320 logger.info("linking %s -> %s", dst_name, link_dest)
1321 if not dry_run:
1322 os.symlink(link_dest, dst_name)
1323 outputs.append(dst_name)
1324
1325 elif os.path.isdir(src_name):
1326 outputs.extend(
1327 copy_tree(src_name, dst_name, preserve_mode,
1328 preserve_times, preserve_symlinks, update,
1329 verbose=verbose, dry_run=dry_run))
1330 else:
1331 copy_file(src_name, dst_name, preserve_mode,
1332 preserve_times, update, verbose=verbose,
1333 dry_run=dry_run)
1334 outputs.append(dst_name)
1335
1336 return outputs
1337
1338# cache for by mkpath() -- in addition to cheapening redundant calls,
1339# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
1340_path_created = set()
1341
1342
1343# I don't use os.makedirs because a) it's new to Python 1.5.2, and
1344# b) it blows up if the directory already exists (I want to silently
1345# succeed in that case).
1346def _mkpath(name, mode=0o777, verbose=True, dry_run=False):
1347 # Detect a common bug -- name is None
1348 if not isinstance(name, str):
1349 raise PackagingInternalError(
1350 "mkpath: 'name' must be a string (got %r)" % (name,))
1351
1352 # XXX what's the better way to handle verbosity? print as we create
1353 # each directory in the path (the current behaviour), or only announce
1354 # the creation of the whole path? (quite easy to do the latter since
1355 # we're not using a recursive algorithm)
1356
1357 name = os.path.normpath(name)
1358 created_dirs = []
1359 if os.path.isdir(name) or name == '':
1360 return created_dirs
1361 if os.path.abspath(name) in _path_created:
1362 return created_dirs
1363
1364 head, tail = os.path.split(name)
1365 tails = [tail] # stack of lone dirs to create
1366
1367 while head and tail and not os.path.isdir(head):
1368 head, tail = os.path.split(head)
1369 tails.insert(0, tail) # push next higher dir onto stack
1370
1371 # now 'head' contains the deepest directory that already exists
1372 # (that is, the child of 'head' in 'name' is the highest directory
1373 # that does *not* exist)
1374 for d in tails:
1375 head = os.path.join(head, d)
1376 abs_head = os.path.abspath(head)
1377
1378 if abs_head in _path_created:
1379 continue
1380
1381 if verbose >= 1:
1382 logger.info("creating %s", head)
1383
1384 if not dry_run:
1385 try:
1386 os.mkdir(head, mode)
1387 except OSError as exc:
1388 if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
1389 raise PackagingFileError(
1390 "could not create '%s': %s" % (head, exc.args[-1]))
1391 created_dirs.append(head)
1392
1393 _path_created.add(abs_head)
1394 return created_dirs
Éric Araujoce5fe832011-07-08 16:27:12 +02001395
1396
1397def encode_multipart(fields, files, boundary=None):
1398 """Prepare a multipart HTTP request.
1399
1400 *fields* is a sequence of (name: str, value: str) elements for regular
1401 form fields, *files* is a sequence of (name: str, filename: str, value:
1402 bytes) elements for data to be uploaded as files.
1403
1404 Returns (content_type: bytes, body: bytes) ready for http.client.HTTP.
1405 """
1406 # Taken from
1407 # http://code.activestate.com/recipes/146306-http-client-to-post-using-multipartform-data/
1408
1409 if boundary is None:
1410 boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
1411 elif not isinstance(boundary, bytes):
1412 raise TypeError('boundary must be bytes, not %r' % type(boundary))
1413
1414 l = []
1415 for key, values in fields:
1416 # handle multiple entries for the same name
1417 if not isinstance(values, (tuple, list)):
Éric Araujo95fc53f2011-09-01 05:11:29 +02001418 values = [values]
Éric Araujoce5fe832011-07-08 16:27:12 +02001419
1420 for value in values:
1421 l.extend((
1422 b'--' + boundary,
1423 ('Content-Disposition: form-data; name="%s"' %
1424 key).encode('utf-8'),
1425 b'',
1426 value.encode('utf-8')))
1427
1428 for key, filename, value in files:
1429 l.extend((
1430 b'--' + boundary,
1431 ('Content-Disposition: form-data; name="%s"; filename="%s"' %
1432 (key, filename)).encode('utf-8'),
1433 b'',
1434 value))
1435
1436 l.append(b'--' + boundary + b'--')
1437 l.append(b'')
1438
1439 body = b'\r\n'.join(l)
1440 content_type = b'multipart/form-data; boundary=' + boundary
1441 return content_type, body