PageRenderTime 1525ms CodeModel.GetById 233ms app.highlight 1023ms RepoModel.GetById 156ms app.codeStats 1ms

/Lib/distutils/util.py

http://unladen-swallow.googlecode.com/
Python | 572 lines | 513 code | 16 blank | 43 comment | 16 complexity | e8ffde6390ccbe4ece744818f08f7690 MD5 | raw file
  1"""distutils.util
  2
  3Miscellaneous utility functions -- anything that doesn't fit into
  4one of the other *util.py modules.
  5"""
  6
  7__revision__ = "$Id: util.py 74807 2009-09-15 19:14:37Z ronald.oussoren $"
  8
  9import sys, os, re
 10
 11def get_platform ():
 12    """Return a string that identifies the current platform.  This is used
 13    mainly to distinguish platform-specific build directories and
 14    platform-specific built distributions.  Typically includes the OS name
 15    and version and the architecture (as supplied by 'os.uname()'),
 16    although the exact information included depends on the OS; eg. for IRIX
 17    the architecture isn't particularly important (IRIX only runs on SGI
 18    hardware), but for Linux the kernel version isn't particularly
 19    important.
 20
 21    Examples of returned values:
 22       linux-i586
 23       linux-alpha (?)
 24       solaris-2.6-sun4u
 25       irix-5.3
 26       irix64-6.2
 27
 28    Windows will return one of:
 29       win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
 30       win-ia64 (64bit Windows on Itanium)
 31       win32 (all others - specifically, sys.platform is returned)
 32
 33    For other non-POSIX platforms, currently just returns 'sys.platform'.
 34    """
 35    if os.name == 'nt':
 36        # sniff sys.version for architecture.
 37        prefix = " bit ("
 38        i = sys.version.find(prefix)
 39        if i == -1:
 40            return sys.platform
 41        j = sys.version.find(")", i)
 42        look = sys.version[i+len(prefix):j].lower()
 43        if look=='amd64':
 44            return 'win-amd64'
 45        if look=='itanium':
 46            return 'win-ia64'
 47        return sys.platform
 48
 49    if os.name != "posix" or not hasattr(os, 'uname'):
 50        # XXX what about the architecture? NT is Intel or Alpha,
 51        # Mac OS is M68k or PPC, etc.
 52        return sys.platform
 53
 54    # Try to distinguish various flavours of Unix
 55
 56    (osname, host, release, version, machine) = os.uname()
 57
 58    # Convert the OS name to lowercase, remove '/' characters
 59    # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
 60    osname = osname.lower()
 61    osname = osname.replace('/', '')
 62    machine = machine.replace(' ', '_')
 63    machine = machine.replace('/', '-')
 64
 65    if osname[:5] == "linux":
 66        # At least on Linux/Intel, 'machine' is the processor --
 67        # i386, etc.
 68        # XXX what about Alpha, SPARC, etc?
 69        return  "%s-%s" % (osname, machine)
 70    elif osname[:5] == "sunos":
 71        if release[0] >= "5":           # SunOS 5 == Solaris 2
 72            osname = "solaris"
 73            release = "%d.%s" % (int(release[0]) - 3, release[2:])
 74        # fall through to standard osname-release-machine representation
 75    elif osname[:4] == "irix":              # could be "irix64"!
 76        return "%s-%s" % (osname, release)
 77    elif osname[:3] == "aix":
 78        return "%s-%s.%s" % (osname, version, release)
 79    elif osname[:6] == "cygwin":
 80        osname = "cygwin"
 81        rel_re = re.compile (r'[\d.]+')
 82        m = rel_re.match(release)
 83        if m:
 84            release = m.group()
 85    elif osname[:6] == "darwin":
 86        #
 87        # For our purposes, we'll assume that the system version from
 88        # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
 89        # to. This makes the compatibility story a bit more sane because the
 90        # machine is going to compile and link as if it were
 91        # MACOSX_DEPLOYMENT_TARGET.
 92        from distutils.sysconfig import get_config_vars
 93        cfgvars = get_config_vars()
 94
 95        macver = os.environ.get('MACOSX_DEPLOYMENT_TARGET')
 96        if not macver:
 97            macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
 98
 99        if 1:
100            # Always calculate the release of the running machine,
101            # needed to determine if we can build fat binaries or not.
102
103            macrelease = macver
104            # Get the system version. Reading this plist is a documented
105            # way to get the system version (see the documentation for
106            # the Gestalt Manager)
107            try:
108                f = open('/System/Library/CoreServices/SystemVersion.plist')
109            except IOError:
110                # We're on a plain darwin box, fall back to the default
111                # behaviour.
112                pass
113            else:
114                m = re.search(
115                        r'<key>ProductUserVisibleVersion</key>\s*' +
116                        r'<string>(.*?)</string>', f.read())
117                f.close()
118                if m is not None:
119                    macrelease = '.'.join(m.group(1).split('.')[:2])
120                # else: fall back to the default behaviour
121
122        if not macver:
123            macver = macrelease
124
125        if macver:
126            from distutils.sysconfig import get_config_vars
127            release = macver
128            osname = "macosx"
129
130            if (macrelease + '.') >= '10.4.' and \
131                    '-arch' in get_config_vars().get('CFLAGS', '').strip():
132                # The universal build will build fat binaries, but not on
133                # systems before 10.4
134                #
135                # Try to detect 4-way universal builds, those have machine-type
136                # 'universal' instead of 'fat'.
137
138                machine = 'fat'
139                cflags = get_config_vars().get('CFLAGS')
140
141                archs = re.findall('-arch\s+(\S+)', cflags)
142                archs.sort()
143                archs = tuple(archs)
144
145                if len(archs) == 1:
146                    machine = archs[0]
147                elif archs == ('i386', 'ppc'):
148                    machine = 'fat'
149                elif archs == ('i386', 'x86_64'):
150                    machine = 'intel'
151                elif archs == ('i386', 'ppc', 'x86_64'):
152                    machine = 'fat3'
153                elif archs == ('ppc64', 'x86_64'):
154                    machine = 'fat64'
155                elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
156                    machine = 'universal'
157                else:
158                    raise ValueError(
159                       "Don't know machine value for archs=%r"%(archs,))
160
161
162            elif machine in ('PowerPC', 'Power_Macintosh'):
163                # Pick a sane name for the PPC architecture.
164                machine = 'ppc'
165
166    return "%s-%s-%s" % (osname, release, machine)
167
168# get_platform ()
169
170
171def convert_path (pathname):
172    """Return 'pathname' as a name that will work on the native filesystem,
173    i.e. split it on '/' and put it back together again using the current
174    directory separator.  Needed because filenames in the setup script are
175    always supplied in Unix style, and have to be converted to the local
176    convention before we can actually use them in the filesystem.  Raises
177    ValueError on non-Unix-ish systems if 'pathname' either starts or
178    ends with a slash.
179    """
180    if os.sep == '/':
181        return pathname
182    if not pathname:
183        return pathname
184    if pathname[0] == '/':
185        raise ValueError, "path '%s' cannot be absolute" % pathname
186    if pathname[-1] == '/':
187        raise ValueError, "path '%s' cannot end with '/'" % pathname
188
189    paths = pathname.split('/')
190    while '.' in paths:
191        paths.remove('.')
192    if not paths:
193        return os.curdir
194    return apply(os.path.join, paths)
195
196# convert_path ()
197
198
199def change_root (new_root, pathname):
200    """Return 'pathname' with 'new_root' prepended.  If 'pathname' is
201    relative, this is equivalent to "os.path.join(new_root,pathname)".
202    Otherwise, it requires making 'pathname' relative and then joining the
203    two, which is tricky on DOS/Windows and Mac OS.
204    """
205    if os.name == 'posix':
206        if not os.path.isabs(pathname):
207            return os.path.join(new_root, pathname)
208        else:
209            return os.path.join(new_root, pathname[1:])
210
211    elif os.name == 'nt':
212        (drive, path) = os.path.splitdrive(pathname)
213        if path[0] == '\\':
214            path = path[1:]
215        return os.path.join(new_root, path)
216
217    elif os.name == 'os2':
218        (drive, path) = os.path.splitdrive(pathname)
219        if path[0] == os.sep:
220            path = path[1:]
221        return os.path.join(new_root, path)
222
223    elif os.name == 'mac':
224        if not os.path.isabs(pathname):
225            return os.path.join(new_root, pathname)
226        else:
227            # Chop off volume name from start of path
228            elements = pathname.split(":", 1)
229            pathname = ":" + elements[1]
230            return os.path.join(new_root, pathname)
231
232    else:
233        # Delay import to improve startup time.
234        from distutils.errors import DistutilsPlatformError
235        raise DistutilsPlatformError, \
236              "nothing known about platform '%s'" % os.name
237
238
239_environ_checked = 0
240def check_environ ():
241    """Ensure that 'os.environ' has all the environment variables we
242    guarantee that users can use in config files, command-line options,
243    etc.  Currently this includes:
244      HOME - user's home directory (Unix only)
245      PLAT - description of the current platform, including hardware
246             and OS (see 'get_platform()')
247    """
248    global _environ_checked
249    if _environ_checked:
250        return
251
252    if os.name == 'posix' and 'HOME' not in os.environ:
253        import pwd
254        os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]
255
256    if 'PLAT' not in os.environ:
257        os.environ['PLAT'] = get_platform()
258
259    _environ_checked = 1
260
261
262def subst_vars (s, local_vars):
263    """Perform shell/Perl-style variable substitution on 'string'.  Every
264    occurrence of '$' followed by a name is considered a variable, and
265    variable is substituted by the value found in the 'local_vars'
266    dictionary, or in 'os.environ' if it's not in 'local_vars'.
267    'os.environ' is first checked/augmented to guarantee that it contains
268    certain values: see 'check_environ()'.  Raise ValueError for any
269    variables not found in either 'local_vars' or 'os.environ'.
270    """
271    check_environ()
272    def _subst (match, local_vars=local_vars):
273        var_name = match.group(1)
274        if var_name in local_vars:
275            return str(local_vars[var_name])
276        else:
277            return os.environ[var_name]
278
279    try:
280        return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s)
281    except KeyError, var:
282        raise ValueError, "invalid variable '$%s'" % var
283
284# subst_vars ()
285
286
287def grok_environment_error (exc, prefix="error: "):
288    """Generate a useful error message from an EnvironmentError (IOError or
289    OSError) exception object.  Handles Python 1.5.1 and 1.5.2 styles, and
290    does what it can to deal with exception objects that don't have a
291    filename (which happens when the error is due to a two-file operation,
292    such as 'rename()' or 'link()'.  Returns the error message as a string
293    prefixed with 'prefix'.
294    """
295    # check for Python 1.5.2-style {IO,OS}Error exception objects
296    if hasattr(exc, 'filename') and hasattr(exc, 'strerror'):
297        if exc.filename:
298            error = prefix + "%s: %s" % (exc.filename, exc.strerror)
299        else:
300            # two-argument functions in posix module don't
301            # include the filename in the exception object!
302            error = prefix + "%s" % exc.strerror
303    else:
304        error = prefix + str(exc[-1])
305
306    return error
307
308
309# Needed by 'split_quoted()'
310_wordchars_re = _squote_re = _dquote_re = None
311def _init_regex():
312    global _wordchars_re, _squote_re, _dquote_re
313    import string
314    _wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)
315    _squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")
316    _dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
317
318def split_quoted (s):
319    """Split a string up according to Unix shell-like rules for quotes and
320    backslashes.  In short: words are delimited by spaces, as long as those
321    spaces are not escaped by a backslash, or inside a quoted string.
322    Single and double quotes are equivalent, and the quote characters can
323    be backslash-escaped.  The backslash is stripped from any two-character
324    escape sequence, leaving only the escaped character.  The quote
325    characters are stripped from any quoted string.  Returns a list of
326    words.
327    """
328    import string
329
330    # This is a nice algorithm for splitting up a single string, since it
331    # doesn't require character-by-character examination.  It was a little
332    # bit of a brain-bender to get it working right, though...
333    if _wordchars_re is None: _init_regex()
334
335    s = s.strip()
336    words = []
337    pos = 0
338
339    while s:
340        m = _wordchars_re.match(s, pos)
341        end = m.end()
342        if end == len(s):
343            words.append(s[:end])
344            break
345
346        if s[end] in string.whitespace: # unescaped, unquoted whitespace: now
347            words.append(s[:end])       # we definitely have a word delimiter
348            s = string.lstrip(s[end:])
349            pos = 0
350
351        elif s[end] == '\\':            # preserve whatever is being escaped;
352                                        # will become part of the current word
353            s = s[:end] + s[end+1:]
354            pos = end+1
355
356        else:
357            if s[end] == "'":           # slurp singly-quoted string
358                m = _squote_re.match(s, end)
359            elif s[end] == '"':         # slurp doubly-quoted string
360                m = _dquote_re.match(s, end)
361            else:
362                raise RuntimeError, \
363                      "this can't happen (bad char '%c')" % s[end]
364
365            if m is None:
366                raise ValueError, \
367                      "bad string (mismatched %s quotes?)" % s[end]
368
369            (beg, end) = m.span()
370            s = s[:beg] + s[beg+1:end-1] + s[end:]
371            pos = m.end() - 2
372
373        if pos >= len(s):
374            words.append(s)
375            break
376
377    return words
378
379# split_quoted ()
380
381
382def execute (func, args, msg=None, verbose=0, dry_run=0):
383    """Perform some action that affects the outside world (eg.  by
384    writing to the filesystem).  Such actions are special because they
385    are disabled by the 'dry_run' flag.  This method takes care of all
386    that bureaucracy for you; all you have to do is supply the
387    function to call and an argument tuple for it (to embody the
388    "external action" being performed), and an optional message to
389    print.
390    """
391    # Delay import to improve startup time.
392    from distutils import log
393    if msg is None:
394        msg = "%s%r" % (func.__name__, args)
395        if msg[-2:] == ',)':        # correct for singleton tuple
396            msg = msg[0:-2] + ')'
397
398    log.info(msg)
399    if not dry_run:
400        apply(func, args)
401
402
403def strtobool (val):
404    """Convert a string representation of truth to true (1) or false (0).
405
406    True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
407    are 'n', 'no', 'f', 'false', 'off', and '0'.  Raises ValueError if
408    'val' is anything else.
409    """
410    val = val.lower()
411    if val in ('y', 'yes', 't', 'true', 'on', '1'):
412        return 1
413    elif val in ('n', 'no', 'f', 'false', 'off', '0'):
414        return 0
415    else:
416        raise ValueError, "invalid truth value %r" % (val,)
417
418
419def byte_compile (py_files,
420                  optimize=0, force=0,
421                  prefix=None, base_dir=None,
422                  verbose=1, dry_run=0,
423                  direct=None):
424    """Byte-compile a collection of Python source files to either .pyc
425    or .pyo files in the same directory.  'py_files' is a list of files
426    to compile; any files that don't end in ".py" are silently skipped.
427    'optimize' must be one of the following:
428      0 - don't optimize (generate .pyc)
429      1 - normal optimization (like "python -O")
430      2 - extra optimization (like "python -OO")
431    If 'force' is true, all files are recompiled regardless of
432    timestamps.
433
434    The source filename encoded in each bytecode file defaults to the
435    filenames listed in 'py_files'; you can modify these with 'prefix' and
436    'basedir'.  'prefix' is a string that will be stripped off of each
437    source filename, and 'base_dir' is a directory name that will be
438    prepended (after 'prefix' is stripped).  You can supply either or both
439    (or neither) of 'prefix' and 'base_dir', as you wish.
440
441    If 'dry_run' is true, doesn't actually do anything that would
442    affect the filesystem.
443
444    Byte-compilation is either done directly in this interpreter process
445    with the standard py_compile module, or indirectly by writing a
446    temporary script and executing it.  Normally, you should let
447    'byte_compile()' figure out to use direct compilation or not (see
448    the source for details).  The 'direct' flag is used by the script
449    generated in indirect mode; unless you know what you're doing, leave
450    it set to None.
451    """
452
453    # Delay import to improve startup time.
454    from distutils import log
455    from distutils.dep_util import newer
456    # First, if the caller didn't force us into direct or indirect mode,
457    # figure out which mode we should be in.  We take a conservative
458    # approach: choose direct mode *only* if the current interpreter is
459    # in debug mode and optimize is 0.  If we're not in debug mode (-O
460    # or -OO), we don't know which level of optimization this
461    # interpreter is running with, so we can't do direct
462    # byte-compilation and be certain that it's the right thing.  Thus,
463    # always compile indirectly if the current interpreter is in either
464    # optimize mode, or if either optimization level was requested by
465    # the caller.
466    if direct is None:
467        direct = (__debug__ and optimize == 0)
468
469    # "Indirect" byte-compilation: write a temporary script and then
470    # run it with the appropriate flags.
471    if not direct:
472        try:
473            from tempfile import mkstemp
474            (script_fd, script_name) = mkstemp(".py")
475        except ImportError:
476            from tempfile import mktemp
477            (script_fd, script_name) = None, mktemp(".py")
478        log.info("writing byte-compilation script '%s'", script_name)
479        if not dry_run:
480            if script_fd is not None:
481                script = os.fdopen(script_fd, "w")
482            else:
483                script = open(script_name, "w")
484
485            script.write("""\
486from distutils.util import byte_compile
487files = [
488""")
489
490            # XXX would be nice to write absolute filenames, just for
491            # safety's sake (script should be more robust in the face of
492            # chdir'ing before running it).  But this requires abspath'ing
493            # 'prefix' as well, and that breaks the hack in build_lib's
494            # 'byte_compile()' method that carefully tacks on a trailing
495            # slash (os.sep really) to make sure the prefix here is "just
496            # right".  This whole prefix business is rather delicate -- the
497            # problem is that it's really a directory, but I'm treating it
498            # as a dumb string, so trailing slashes and so forth matter.
499
500            #py_files = map(os.path.abspath, py_files)
501            #if prefix:
502            #    prefix = os.path.abspath(prefix)
503
504            script.write(",\n".join(map(repr, py_files)) + "]\n")
505            script.write("""
506byte_compile(files, optimize=%r, force=%r,
507             prefix=%r, base_dir=%r,
508             verbose=%r, dry_run=0,
509             direct=1)
510""" % (optimize, force, prefix, base_dir, verbose))
511
512            script.close()
513
514        cmd = [sys.executable, script_name]
515        if optimize == 1:
516            cmd.insert(1, "-O")
517        elif optimize == 2:
518            cmd.insert(1, "-OO")
519
520        # Delay import to improve startup time.
521        from distutils.spawn import spawn
522        spawn(cmd, dry_run=dry_run)
523        execute(os.remove, (script_name,), "removing %s" % script_name,
524                dry_run=dry_run)
525
526    # "Direct" byte-compilation: use the py_compile module to compile
527    # right here, right now.  Note that the script generated in indirect
528    # mode simply calls 'byte_compile()' in direct mode, a weird sort of
529    # cross-process recursion.  Hey, it works!
530    else:
531        from py_compile import compile
532
533        for file in py_files:
534            if file[-3:] != ".py":
535                # This lets us be lazy and not filter filenames in
536                # the "install_lib" command.
537                continue
538
539            # Terminology from the py_compile module:
540            #   cfile - byte-compiled file
541            #   dfile - purported source filename (same as 'file' by default)
542            cfile = file + (__debug__ and "c" or "o")
543            dfile = file
544            if prefix:
545                if file[:len(prefix)] != prefix:
546                    raise ValueError, \
547                          ("invalid prefix: filename %r doesn't start with %r"
548                           % (file, prefix))
549                dfile = dfile[len(prefix):]
550            if base_dir:
551                dfile = os.path.join(base_dir, dfile)
552
553            cfile_base = os.path.basename(cfile)
554            if direct:
555                if force or newer(file, cfile):
556                    log.info("byte-compiling %s to %s", file, cfile_base)
557                    if not dry_run:
558                        compile(file, cfile, dfile)
559                else:
560                    log.debug("skipping byte-compilation of %s to %s",
561                              file, cfile_base)
562
563# byte_compile ()
564
565def rfc822_escape (header):
566    """Return a version of the string escaped for inclusion in an
567    RFC-822 header, by ensuring there are 8 spaces space after each newline.
568    """
569    lines = header.split('\n')
570    lines = [line.strip() for line in lines]
571    header = ('\n' + 8*' ').join(lines)
572    return header