| Éric Araujo | 35a4d01 | 2011-06-04 22:24:59 +0200 | [diff] [blame] | 1 | """Miscellaneous utility functions.""" | 
 | 2 |  | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 3 | import os | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 4 | import re | 
| Éric Araujo | 35a4d01 | 2011-06-04 22:24:59 +0200 | [diff] [blame] | 5 | import csv | 
 | 6 | import sys | 
 | 7 | import errno | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 8 | import shutil | 
 | 9 | import string | 
| Éric Araujo | 35a4d01 | 2011-06-04 22:24:59 +0200 | [diff] [blame] | 10 | import hashlib | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 11 | import posixpath | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 12 | import subprocess | 
| Éric Araujo | 35a4d01 | 2011-06-04 22:24:59 +0200 | [diff] [blame] | 13 | import sysconfig | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 14 | from glob import iglob as std_iglob | 
 | 15 | from fnmatch import fnmatchcase | 
 | 16 | from inspect import getsource | 
 | 17 | from configparser import RawConfigParser | 
 | 18 |  | 
 | 19 | from packaging import logger | 
 | 20 | from packaging.errors import (PackagingPlatformError, PackagingFileError, | 
 | 21 |                               PackagingByteCompileError, PackagingExecError, | 
 | 22 |                               InstallationException, PackagingInternalError) | 
 | 23 |  | 
| Éric Araujo | 95fc53f | 2011-09-01 05:11:29 +0200 | [diff] [blame] | 24 | __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 Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 48 | _PLATFORM = None | 
 | 49 | _DEFAULT_INSTALLER = 'packaging' | 
 | 50 |  | 
 | 51 |  | 
 | 52 | def 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 |  | 
 | 73 | def 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 |  | 
 | 85 | def 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 |  | 
 | 95 | def 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 |  | 
 | 122 | def 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 |  | 
 | 154 | def 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 Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 177 | # Needed by 'split_quoted()' | 
 | 178 | _wordchars_re = _squote_re = _dquote_re = None | 
 | 179 |  | 
 | 180 |  | 
 | 181 | def _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 Araujo | 95fc53f | 2011-09-01 05:11:29 +0200 | [diff] [blame] | 188 | # TODO replace with shlex.split after testing | 
 | 189 |  | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 190 | def 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 Araujo | 1c1d9a5 | 2011-06-10 23:26:31 +0200 | [diff] [blame] | 252 | def 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 Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 260 | def 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 |  | 
 | 280 | def strtobool(val): | 
| Éric Araujo | d5d831b | 2011-06-06 01:13:48 +0200 | [diff] [blame] | 281 |     """Convert a string representation of truth to a boolean. | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 282 |  | 
 | 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 |  | 
 | 296 | def 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 Araujo | 7724a6c | 2011-09-17 03:31:51 +0200 | [diff] [blame] | 329 |     if sys.dont_write_bytecode: | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 330 |         raise PackagingByteCompileError('byte-compiling is disabled.') | 
 | 331 |  | 
 | 332 |     # First, if the caller didn't force us into direct or indirect mode, | 
 | 333 |     # figure out which mode we should be in.  We take a conservative | 
 | 334 |     # approach: choose direct mode *only* if the current interpreter is | 
 | 335 |     # in debug mode and optimize is 0.  If we're not in debug mode (-O | 
 | 336 |     # or -OO), we don't know which level of optimization this | 
 | 337 |     # interpreter is running with, so we can't do direct | 
 | 338 |     # byte-compilation and be certain that it's the right thing.  Thus, | 
 | 339 |     # always compile indirectly if the current interpreter is in either | 
 | 340 |     # optimize mode, or if either optimization level was requested by | 
 | 341 |     # the caller. | 
 | 342 |     if direct is None: | 
 | 343 |         direct = (__debug__ and optimize == 0) | 
 | 344 |  | 
 | 345 |     # "Indirect" byte-compilation: write a temporary script and then | 
 | 346 |     # run it with the appropriate flags. | 
 | 347 |     if not direct: | 
 | 348 |         from tempfile import mkstemp | 
| Éric Araujo | 7724a6c | 2011-09-17 03:31:51 +0200 | [diff] [blame] | 349 |         # XXX use something better than mkstemp | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 350 |         script_fd, script_name = mkstemp(".py") | 
| Éric Araujo | 7724a6c | 2011-09-17 03:31:51 +0200 | [diff] [blame] | 351 |         os.close(script_fd) | 
 | 352 |         script_fd = None | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 353 |         logger.info("writing byte-compilation script '%s'", script_name) | 
 | 354 |         if not dry_run: | 
 | 355 |             if script_fd is not None: | 
| Victor Stinner | 9cf6d13 | 2011-05-19 21:42:47 +0200 | [diff] [blame] | 356 |                 script = os.fdopen(script_fd, "w", encoding='utf-8') | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 357 |             else: | 
| Victor Stinner | 9cf6d13 | 2011-05-19 21:42:47 +0200 | [diff] [blame] | 358 |                 script = open(script_name, "w", encoding='utf-8') | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 359 |  | 
| Victor Stinner | 21a9c74 | 2011-05-19 15:51:27 +0200 | [diff] [blame] | 360 |             with script: | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 361 |                 script.write("""\ | 
 | 362 | from packaging.util import byte_compile | 
 | 363 | files = [ | 
 | 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(""" | 
 | 382 | byte_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 Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 388 |         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 Araujo | 088025f | 2011-06-04 18:45:40 +0200 | [diff] [blame] | 394 |         env = os.environ.copy() | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 395 |         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 Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 439 | _RE_VERSION = re.compile('(\d+\.\d+(\.\d+)*)') | 
 | 440 | _MAC_OS_X_LD_VERSION = re.compile('^@\(#\)PROGRAM:ld  ' | 
 | 441 |                                   'PROJECT:ld64-((\d+)(\.\d+)*)') | 
 | 442 |  | 
 | 443 |  | 
 | 444 | def _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 |  | 
 | 452 | def _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 Stinner | 9904b22 | 2011-05-21 02:20:36 +0200 | [diff] [blame] | 465 |         stdout, stderr = pipe.communicate() | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 466 |     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 |  | 
 | 482 | def 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 |  | 
 | 494 | def 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 |  | 
 | 534 | def write_file(filename, contents): | 
 | 535 |     """Create *filename* and write *contents* to it. | 
 | 536 |  | 
 | 537 |     *contents* is a sequence of strings without line terminators. | 
| Éric Araujo | 95fc53f | 2011-09-01 05:11:29 +0200 | [diff] [blame] | 538 |  | 
 | 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 Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 542 |     """ | 
 | 543 |     with open(filename, "w") as f: | 
 | 544 |         for line in contents: | 
 | 545 |             f.write(line + "\n") | 
 | 546 |  | 
 | 547 |  | 
 | 548 | def _is_package(path): | 
| Éric Araujo | 1c1d9a5 | 2011-06-10 23:26:31 +0200 | [diff] [blame] | 549 |     return os.path.isdir(path) and os.path.isfile( | 
 | 550 |         os.path.join(path, '__init__.py')) | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 551 |  | 
 | 552 |  | 
 | 553 | # Code taken from the pip project | 
 | 554 | def _is_archive_file(name): | 
 | 555 |     archives = ('.zip', '.tar.gz', '.tar.bz2', '.tgz', '.tar') | 
 | 556 |     ext = splitext(name)[1].lower() | 
| Éric Araujo | 1c1d9a5 | 2011-06-10 23:26:31 +0200 | [diff] [blame] | 557 |     return ext in archives | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 558 |  | 
 | 559 |  | 
 | 560 | def _under(path, root): | 
| Éric Araujo | 95fc53f | 2011-09-01 05:11:29 +0200 | [diff] [blame] | 561 |     # XXX use os.path | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 562 |     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 |  | 
 | 572 | def _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 |  | 
 | 579 | def 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 |  | 
 | 626 | def 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 |  | 
 | 655 | def 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 Deily | fceb412 | 2011-06-28 20:04:24 -0700 | [diff] [blame] | 664 | if sys.platform == 'darwin': | 
 | 665 |     _cfg_target = None | 
 | 666 |     _cfg_target_split = None | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 667 |  | 
| Éric Araujo | 95fc53f | 2011-09-01 05:11:29 +0200 | [diff] [blame] | 668 |  | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 669 | def 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 Araujo | 6280606 | 2011-06-11 09:46:07 +0200 | [diff] [blame] | 688 |     logger.debug('spawn: running %r', cmd) | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 689 |     if dry_run: | 
| Éric Araujo | 29f6297 | 2011-08-04 17:17:07 +0200 | [diff] [blame] | 690 |         logger.debug('dry run, no process actually spawned') | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 691 |         return | 
| Ned Deily | fceb412 | 2011-06-28 20:04:24 -0700 | [diff] [blame] | 692 |     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 Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 712 |     exit_status = subprocess.call(cmd, env=env) | 
 | 713 |     if exit_status != 0: | 
| Éric Araujo | 6280606 | 2011-06-11 09:46:07 +0200 | [diff] [blame] | 714 |         msg = "command %r failed with exit status %d" | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 715 |         raise PackagingExecError(msg % (cmd, exit_status)) | 
 | 716 |  | 
 | 717 |  | 
 | 718 | def 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 |  | 
 | 744 | DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi' | 
 | 745 | DEFAULT_REALM = 'pypi' | 
 | 746 | DEFAULT_PYPIRC = """\ | 
 | 747 | [distutils] | 
 | 748 | index-servers = | 
 | 749 |     pypi | 
 | 750 |  | 
 | 751 | [pypi] | 
 | 752 | username:%s | 
 | 753 | password:%s | 
 | 754 | """ | 
 | 755 |  | 
 | 756 |  | 
 | 757 | def get_pypirc_path(): | 
 | 758 |     """Return path to pypirc config file.""" | 
 | 759 |     return os.path.join(os.path.expanduser('~'), '.pypirc') | 
 | 760 |  | 
 | 761 |  | 
 | 762 | def 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 |  | 
 | 774 | def 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 |  | 
 | 829 | def 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 |  | 
 | 852 | class 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 |  | 
 | 873 | RICH_GLOB = re.compile(r'\{([^}]*)\}') | 
| Tarek Ziade | ec9b76d | 2011-05-21 11:48:16 +0200 | [diff] [blame] | 874 | _CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]') | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 875 | _CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$') | 
 | 876 |  | 
 | 877 |  | 
 | 878 | def 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 |  | 
 | 889 | def _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 Ziade | ec9b76d | 2011-05-21 11:48:16 +0200 | [diff] [blame] | 908 |                 # we support both | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 909 |                 radical = radical.lstrip('/') | 
| Tarek Ziade | ec9b76d | 2011-05-21 11:48:16 +0200 | [diff] [blame] | 910 |                 radical = radical.lstrip('\\') | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 911 |             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 |  | 
 | 917 | def 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 Araujo | 3605030 | 2011-06-10 23:52:26 +0200 | [diff] [blame] | 947 |                         "package_dir": ("files", 'packages_root'), | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 948 |                         "packages": ("files",), | 
 | 949 |                         "scripts": ("files",), | 
 | 950 |                         "py_modules": ("files", "modules"),  # ** | 
 | 951 |                         } | 
 | 952 |  | 
 | 953 |     MULTI_FIELDS = ("classifiers", | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 954 |                     "platforms", | 
| Éric Araujo | 3605030 | 2011-06-10 23:52:26 +0200 | [diff] [blame] | 955 |                     "requires", | 
 | 956 |                     "provides", | 
 | 957 |                     "obsoletes", | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 958 |                     "packages", | 
| Éric Araujo | 3605030 | 2011-06-10 23:52:26 +0200 | [diff] [blame] | 959 |                     "scripts", | 
 | 960 |                     "py_modules") | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 961 |  | 
 | 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 Araujo | 3605030 | 2011-06-10 23:52:26 +0200 | [diff] [blame] | 972 |     if not os.path.exists(path): | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 973 |         raise PackagingFileError("file '%s' does not exist" % | 
| Éric Araujo | 3605030 | 2011-06-10 23:52:26 +0200 | [diff] [blame] | 974 |                                  os.path.abspath(path)) | 
| Éric Araujo | 8d233f2 | 2011-06-12 23:02:57 +0200 | [diff] [blame] | 975 |     config.read(path, encoding='utf-8') | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 976 |  | 
 | 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 Araujo | 3605030 | 2011-06-10 23:52:26 +0200 | [diff] [blame] | 991 |             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 Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1000 |             else: | 
 | 1001 |                 continue | 
 | 1002 |  | 
| Éric Araujo | 3605030 | 2011-06-10 23:52:26 +0200 | [diff] [blame] | 1003 |         if arg == 'package_dir' and in_cfg_value: | 
 | 1004 |             in_cfg_value = {'': in_cfg_value} | 
 | 1005 |  | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1006 |         if arg in MULTI_FIELDS: | 
 | 1007 |             # support multiline options | 
| Éric Araujo | 3605030 | 2011-06-10 23:52:26 +0200 | [diff] [blame] | 1008 |             in_cfg_value = split_multiline(in_cfg_value) | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1009 |  | 
 | 1010 |         kwargs[arg] = in_cfg_value | 
 | 1011 |  | 
 | 1012 |     return kwargs | 
 | 1013 |  | 
 | 1014 |  | 
 | 1015 | _SETUP_TMPL = """\ | 
 | 1016 | # This script was automatically generated by packaging | 
 | 1017 | import os | 
 | 1018 | from distutils.core import setup | 
 | 1019 | from ConfigParser import RawConfigParser | 
 | 1020 |  | 
 | 1021 | %(func)s | 
 | 1022 |  | 
 | 1023 | setup(**cfg_to_args()) | 
 | 1024 | """ | 
 | 1025 |  | 
 | 1026 |  | 
 | 1027 | def 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 Ziade | 721ccd0 | 2011-06-02 12:00:44 +0200 | [diff] [blame] | 1033 |         raise PackagingFileError("a setup.py file already exists") | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1034 |  | 
| Victor Stinner | 9cf6d13 | 2011-05-19 21:42:47 +0200 | [diff] [blame] | 1035 |     with open("setup.py", "w", encoding='utf-8') as fp: | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1036 |         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 | 
 | 1041 | def 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 Araujo | 3cab2f1 | 2011-06-08 04:10:57 +0200 | [diff] [blame] | 1047 |             print('invalid response:', repr(response)) | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1048 |             print('choose one of', ', '.join(repr(o) for o in options)) | 
 | 1049 |         else: | 
 | 1050 |             return response | 
 | 1051 |  | 
 | 1052 |  | 
 | 1053 | def _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 |  | 
 | 1090 | def _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 |  | 
 | 1112 | def 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 Stinner | 4c9706b | 2011-05-19 15:52:59 +0200 | [diff] [blame] | 1149 |         open(requested_path, 'wb').close() | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1150 |         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 |  | 
 | 1165 | def _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 Ziade | b1b6e13 | 2011-05-30 12:07:49 +0200 | [diff] [blame] | 1170 |                 logger.debug("Found egg-info directory.") | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1171 |                 return True | 
| Tarek Ziade | b1b6e13 | 2011-05-30 12:07:49 +0200 | [diff] [blame] | 1172 |     logger.debug("No egg-info directory found.") | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1173 |     return False | 
 | 1174 |  | 
 | 1175 |  | 
 | 1176 | def _has_setuptools_text(setup_py): | 
 | 1177 |     return _has_text(setup_py, 'setuptools') | 
 | 1178 |  | 
 | 1179 |  | 
 | 1180 | def _has_distutils_text(setup_py): | 
 | 1181 |     return _has_text(setup_py, 'distutils') | 
 | 1182 |  | 
 | 1183 |  | 
 | 1184 | def _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 Ziade | b1b6e13 | 2011-05-30 12:07:49 +0200 | [diff] [blame] | 1189 |                 logger.debug("Found %s text in setup.py.", installer) | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1190 |                 return True | 
| Tarek Ziade | b1b6e13 | 2011-05-30 12:07:49 +0200 | [diff] [blame] | 1191 |     logger.debug("No %s text found in setup.py.", installer) | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1192 |     return False | 
 | 1193 |  | 
 | 1194 |  | 
 | 1195 | def _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 |  | 
 | 1203 | def _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 Ziade | b1b6e13 | 2011-05-30 12:07:49 +0200 | [diff] [blame] | 1207 |         logger.debug("PKG-INFO file found.") | 
 | 1208 |     else: | 
 | 1209 |         logger.debug("No PKG-INFO file found.") | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1210 |     return has_pkg_info | 
 | 1211 |  | 
 | 1212 |  | 
 | 1213 | def _has_setup_py(srcdir): | 
 | 1214 |     setup_py = os.path.join(srcdir, 'setup.py') | 
 | 1215 |     if os.path.isfile(setup_py): | 
| Tarek Ziade | b1b6e13 | 2011-05-30 12:07:49 +0200 | [diff] [blame] | 1216 |         logger.debug('setup.py file found.') | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1217 |         return True | 
 | 1218 |     return False | 
 | 1219 |  | 
 | 1220 |  | 
 | 1221 | def _has_setup_cfg(srcdir): | 
 | 1222 |     setup_cfg = os.path.join(srcdir, 'setup.cfg') | 
 | 1223 |     if os.path.isfile(setup_cfg): | 
| Tarek Ziade | b1b6e13 | 2011-05-30 12:07:49 +0200 | [diff] [blame] | 1224 |         logger.debug('setup.cfg file found.') | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1225 |         return True | 
| Tarek Ziade | b1b6e13 | 2011-05-30 12:07:49 +0200 | [diff] [blame] | 1226 |     logger.debug("No setup.cfg file found.") | 
| Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1227 |     return False | 
 | 1228 |  | 
 | 1229 |  | 
 | 1230 | def 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 |  | 
 | 1244 | def 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 |  | 
 | 1258 | def 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 |  | 
 | 1271 | def 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 | 
 | 1290 | def 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). | 
 | 1346 | def _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 Araujo | ce5fe83 | 2011-07-08 16:27:12 +0200 | [diff] [blame] | 1395 |  | 
 | 1396 |  | 
 | 1397 | def 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 Araujo | 95fc53f | 2011-09-01 05:11:29 +0200 | [diff] [blame] | 1418 |             values = [values] | 
| Éric Araujo | ce5fe83 | 2011-07-08 16:27:12 +0200 | [diff] [blame] | 1419 |  | 
 | 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 |