ยปCore Development>Code coverage>Lib/packaging/util.py

Python code coverage for Lib/packaging/util.py

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