1"""Provide access to Python's configuration information.
2
3"""
4import sys
5import os
6from os.path import pardir, realpath
7
8_INSTALL_SCHEMES = {
9    'posix_prefix': {
10        'stdlib': '{base}/lib/python{py_version_short}',
11        'platstdlib': '{platbase}/lib/python{py_version_short}',
12        'purelib': '{base}/lib/python{py_version_short}/site-packages',
13        'platlib': '{platbase}/lib/python{py_version_short}/site-packages',
14        'include': '{base}/include/python{py_version_short}',
15        'platinclude': '{platbase}/include/python{py_version_short}',
16        'scripts': '{base}/bin',
17        'data': '{base}',
18        },
19    'posix_home': {
20        'stdlib': '{base}/lib/python',
21        'platstdlib': '{base}/lib/python',
22        'purelib': '{base}/lib/python',
23        'platlib': '{base}/lib/python',
24        'include': '{base}/include/python',
25        'platinclude': '{base}/include/python',
26        'scripts': '{base}/bin',
27        'data'   : '{base}',
28        },
29    'nt': {
30        'stdlib': '{base}/Lib',
31        'platstdlib': '{base}/Lib',
32        'purelib': '{base}/Lib/site-packages',
33        'platlib': '{base}/Lib/site-packages',
34        'include': '{base}/Include',
35        'platinclude': '{base}/Include',
36        'scripts': '{base}/Scripts',
37        'data'   : '{base}',
38        },
39    'os2': {
40        'stdlib': '{base}/Lib',
41        'platstdlib': '{base}/Lib',
42        'purelib': '{base}/Lib/site-packages',
43        'platlib': '{base}/Lib/site-packages',
44        'include': '{base}/Include',
45        'platinclude': '{base}/Include',
46        'scripts': '{base}/Scripts',
47        'data'   : '{base}',
48        },
49    'os2_home': {
50        'stdlib': '{userbase}/lib/python{py_version_short}',
51        'platstdlib': '{userbase}/lib/python{py_version_short}',
52        'purelib': '{userbase}/lib/python{py_version_short}/site-packages',
53        'platlib': '{userbase}/lib/python{py_version_short}/site-packages',
54        'include': '{userbase}/include/python{py_version_short}',
55        'scripts': '{userbase}/bin',
56        'data'   : '{userbase}',
57        },
58    'nt_user': {
59        'stdlib': '{userbase}/Python{py_version_nodot}',
60        'platstdlib': '{userbase}/Python{py_version_nodot}',
61        'purelib': '{userbase}/Python{py_version_nodot}/site-packages',
62        'platlib': '{userbase}/Python{py_version_nodot}/site-packages',
63        'include': '{userbase}/Python{py_version_nodot}/Include',
64        'scripts': '{userbase}/Scripts',
65        'data'   : '{userbase}',
66        },
67    'posix_user': {
68        'stdlib': '{userbase}/lib/python{py_version_short}',
69        'platstdlib': '{userbase}/lib/python{py_version_short}',
70        'purelib': '{userbase}/lib/python{py_version_short}/site-packages',
71        'platlib': '{userbase}/lib/python{py_version_short}/site-packages',
72        'include': '{userbase}/include/python{py_version_short}',
73        'scripts': '{userbase}/bin',
74        'data'   : '{userbase}',
75        },
76    'osx_framework_user': {
77        'stdlib': '{userbase}/lib/python',
78        'platstdlib': '{userbase}/lib/python',
79        'purelib': '{userbase}/lib/python/site-packages',
80        'platlib': '{userbase}/lib/python/site-packages',
81        'include': '{userbase}/include',
82        'scripts': '{userbase}/bin',
83        'data'   : '{userbase}',
84        },
85    }
86
87_SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include',
88                'scripts', 'data')
89_PY_VERSION = sys.version.split()[0]
90_PY_VERSION_SHORT = sys.version[:3]
91_PY_VERSION_SHORT_NO_DOT = _PY_VERSION[0] + _PY_VERSION[2]
92_PREFIX = os.path.normpath(sys.prefix)
93_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
94_CONFIG_VARS = None
95_USER_BASE = None
96
97def _safe_realpath(path):
98    try:
99        return realpath(path)
100    except OSError:
101        return path
102
103if sys.executable:
104    _PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
105else:
106    # sys.executable can be empty if argv[0] has been changed and Python is
107    # unable to retrieve the real program name
108    _PROJECT_BASE = _safe_realpath(os.getcwd())
109
110if os.name == "nt" and "pcbuild" in _PROJECT_BASE[-8:].lower():
111    _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir))
112# PC/VS7.1
113if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower():
114    _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
115# PC/VS9.0/amd64
116if (os.name == "nt"
117   and os.path.basename(os.path.dirname(os.path.dirname(_PROJECT_BASE))).lower() == "pc"
118   and os.path.basename(os.path.dirname(_PROJECT_BASE)).lower() == "vs9.0"):
119    _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir, pardir))
120# PC/AMD64
121if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
122    _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
123
124# set for cross builds
125if "_PYTHON_PROJECT_BASE" in os.environ:
126    # the build directory for posix builds
127    _PROJECT_BASE = os.path.normpath(os.path.abspath("."))
128def is_python_build():
129    for fn in ("Setup.dist", "Setup.local"):
130        if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
131            return True
132    return False
133
134_PYTHON_BUILD = is_python_build()
135
136if _PYTHON_BUILD:
137    for scheme in ('posix_prefix', 'posix_home'):
138        _INSTALL_SCHEMES[scheme]['include'] = '{projectbase}/Include'
139        _INSTALL_SCHEMES[scheme]['platinclude'] = '{srcdir}'
140
141def _subst_vars(s, local_vars):
142    try:
143        return s.format(**local_vars)
144    except KeyError:
145        try:
146            return s.format(**os.environ)
147        except KeyError, var:
148            raise AttributeError('{%s}' % var)
149
150def _extend_dict(target_dict, other_dict):
151    target_keys = target_dict.keys()
152    for key, value in other_dict.items():
153        if key in target_keys:
154            continue
155        target_dict[key] = value
156
157def _expand_vars(scheme, vars):
158    res = {}
159    if vars is None:
160        vars = {}
161    _extend_dict(vars, get_config_vars())
162
163    for key, value in _INSTALL_SCHEMES[scheme].items():
164        if os.name in ('posix', 'nt'):
165            value = os.path.expanduser(value)
166        res[key] = os.path.normpath(_subst_vars(value, vars))
167    return res
168
169def _get_default_scheme():
170    if os.name == 'posix':
171        # the default scheme for posix is posix_prefix
172        return 'posix_prefix'
173    return os.name
174
175def _getuserbase():
176    env_base = os.environ.get("PYTHONUSERBASE", None)
177    def joinuser(*args):
178        return os.path.expanduser(os.path.join(*args))
179
180    # what about 'os2emx', 'riscos' ?
181    if os.name == "nt":
182        base = os.environ.get("APPDATA") or "~"
183        return env_base if env_base else joinuser(base, "Python")
184
185    if sys.platform == "darwin":
186        framework = get_config_var("PYTHONFRAMEWORK")
187        if framework:
188            return env_base if env_base else \
189                               joinuser("~", "Library", framework, "%d.%d"
190                                            % (sys.version_info[:2]))
191
192    return env_base if env_base else joinuser("~", ".local")
193
194
195def _parse_makefile(filename, vars=None):
196    """Parse a Makefile-style file.
197
198    A dictionary containing name/value pairs is returned.  If an
199    optional dictionary is passed in as the second argument, it is
200    used instead of a new dictionary.
201    """
202    import re
203    # Regexes needed for parsing Makefile (and similar syntaxes,
204    # like old-style Setup files).
205    _variable_rx = re.compile("([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
206    _findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
207    _findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
208
209    if vars is None:
210        vars = {}
211    done = {}
212    notdone = {}
213
214    with open(filename) as f:
215        lines = f.readlines()
216
217    for line in lines:
218        if line.startswith('#') or line.strip() == '':
219            continue
220        m = _variable_rx.match(line)
221        if m:
222            n, v = m.group(1, 2)
223            v = v.strip()
224            # `$$' is a literal `$' in make
225            tmpv = v.replace('$$', '')
226
227            if "$" in tmpv:
228                notdone[n] = v
229            else:
230                try:
231                    v = int(v)
232                except ValueError:
233                    # insert literal `$'
234                    done[n] = v.replace('$$', '$')
235                else:
236                    done[n] = v
237
238    # do variable interpolation here
239    while notdone:
240        for name in notdone.keys():
241            value = notdone[name]
242            m = _findvar1_rx.search(value) or _findvar2_rx.search(value)
243            if m:
244                n = m.group(1)
245                found = True
246                if n in done:
247                    item = str(done[n])
248                elif n in notdone:
249                    # get it on a subsequent round
250                    found = False
251                elif n in os.environ:
252                    # do it like make: fall back to environment
253                    item = os.environ[n]
254                else:
255                    done[n] = item = ""
256                if found:
257                    after = value[m.end():]
258                    value = value[:m.start()] + item + after
259                    if "$" in after:
260                        notdone[name] = value
261                    else:
262                        try: value = int(value)
263                        except ValueError:
264                            done[name] = value.strip()
265                        else:
266                            done[name] = value
267                        del notdone[name]
268            else:
269                # bogus variable reference; just drop it since we can't deal
270                del notdone[name]
271    # strip spurious spaces
272    for k, v in done.items():
273        if isinstance(v, str):
274            done[k] = v.strip()
275
276    # save the results in the global dictionary
277    vars.update(done)
278    return vars
279
280
281def get_makefile_filename():
282    """Return the path of the Makefile."""
283    if _PYTHON_BUILD:
284        return os.path.join(_PROJECT_BASE, "Makefile")
285    return os.path.join(get_path('platstdlib'), "config", "Makefile")
286
287# Issue #22199: retain undocumented private name for compatibility
288_get_makefile_filename = get_makefile_filename
289
290def _generate_posix_vars():
291    """Generate the Python module containing build-time variables."""
292    import pprint
293    vars = {}
294    # load the installed Makefile:
295    makefile = get_makefile_filename()
296    try:
297        _parse_makefile(makefile, vars)
298    except IOError, e:
299        msg = "invalid Python installation: unable to open %s" % makefile
300        if hasattr(e, "strerror"):
301            msg = msg + " (%s)" % e.strerror
302        raise IOError(msg)
303
304    # load the installed pyconfig.h:
305    config_h = get_config_h_filename()
306    try:
307        with open(config_h) as f:
308            parse_config_h(f, vars)
309    except IOError, e:
310        msg = "invalid Python installation: unable to open %s" % config_h
311        if hasattr(e, "strerror"):
312            msg = msg + " (%s)" % e.strerror
313        raise IOError(msg)
314
315    # On AIX, there are wrong paths to the linker scripts in the Makefile
316    # -- these paths are relative to the Python source, but when installed
317    # the scripts are in another directory.
318    if _PYTHON_BUILD:
319        vars['LDSHARED'] = vars['BLDSHARED']
320
321    # There's a chicken-and-egg situation on OS X with regards to the
322    # _sysconfigdata module after the changes introduced by #15298:
323    # get_config_vars() is called by get_platform() as part of the
324    # `make pybuilddir.txt` target -- which is a precursor to the
325    # _sysconfigdata.py module being constructed.  Unfortunately,
326    # get_config_vars() eventually calls _init_posix(), which attempts
327    # to import _sysconfigdata, which we won't have built yet.  In order
328    # for _init_posix() to work, if we're on Darwin, just mock up the
329    # _sysconfigdata module manually and populate it with the build vars.
330    # This is more than sufficient for ensuring the subsequent call to
331    # get_platform() succeeds.
332    name = '_sysconfigdata'
333    if 'darwin' in sys.platform:
334        import imp
335        module = imp.new_module(name)
336        module.build_time_vars = vars
337        sys.modules[name] = module
338
339    pybuilddir = 'build/lib.%s-%s' % (get_platform(), sys.version[:3])
340    if hasattr(sys, "gettotalrefcount"):
341        pybuilddir += '-pydebug'
342    try:
343        os.makedirs(pybuilddir)
344    except OSError:
345        pass
346    destfile = os.path.join(pybuilddir, name + '.py')
347
348    with open(destfile, 'wb') as f:
349        f.write('# system configuration generated and used by'
350                ' the sysconfig module\n')
351        f.write('build_time_vars = ')
352        pprint.pprint(vars, stream=f)
353
354    # Create file used for sys.path fixup -- see Modules/getpath.c
355    with open('pybuilddir.txt', 'w') as f:
356        f.write(pybuilddir)
357
358def _init_posix(vars):
359    """Initialize the module as appropriate for POSIX systems."""
360    # _sysconfigdata is generated at build time, see _generate_posix_vars()
361    from _sysconfigdata import build_time_vars
362    vars.update(build_time_vars)
363
364def _init_non_posix(vars):
365    """Initialize the module as appropriate for NT"""
366    # set basic install directories
367    vars['LIBDEST'] = get_path('stdlib')
368    vars['BINLIBDEST'] = get_path('platstdlib')
369    vars['INCLUDEPY'] = get_path('include')
370    vars['SO'] = '.pyd'
371    vars['EXE'] = '.exe'
372    vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
373    vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
374
375#
376# public APIs
377#
378
379
380def parse_config_h(fp, vars=None):
381    """Parse a config.h-style file.
382
383    A dictionary containing name/value pairs is returned.  If an
384    optional dictionary is passed in as the second argument, it is
385    used instead of a new dictionary.
386    """
387    import re
388    if vars is None:
389        vars = {}
390    define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
391    undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
392
393    while True:
394        line = fp.readline()
395        if not line:
396            break
397        m = define_rx.match(line)
398        if m:
399            n, v = m.group(1, 2)
400            try: v = int(v)
401            except ValueError: pass
402            vars[n] = v
403        else:
404            m = undef_rx.match(line)
405            if m:
406                vars[m.group(1)] = 0
407    return vars
408
409def get_config_h_filename():
410    """Returns the path of pyconfig.h."""
411    if _PYTHON_BUILD:
412        if os.name == "nt":
413            inc_dir = os.path.join(_PROJECT_BASE, "PC")
414        else:
415            inc_dir = _PROJECT_BASE
416    else:
417        inc_dir = get_path('platinclude')
418    return os.path.join(inc_dir, 'pyconfig.h')
419
420def get_scheme_names():
421    """Returns a tuple containing the schemes names."""
422    schemes = _INSTALL_SCHEMES.keys()
423    schemes.sort()
424    return tuple(schemes)
425
426def get_path_names():
427    """Returns a tuple containing the paths names."""
428    return _SCHEME_KEYS
429
430def get_paths(scheme=_get_default_scheme(), vars=None, expand=True):
431    """Returns a mapping containing an install scheme.
432
433    ``scheme`` is the install scheme name. If not provided, it will
434    return the default scheme for the current platform.
435    """
436    if expand:
437        return _expand_vars(scheme, vars)
438    else:
439        return _INSTALL_SCHEMES[scheme]
440
441def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True):
442    """Returns a path corresponding to the scheme.
443
444    ``scheme`` is the install scheme name.
445    """
446    return get_paths(scheme, vars, expand)[name]
447
448def get_config_vars(*args):
449    """With no arguments, return a dictionary of all configuration
450    variables relevant for the current platform.
451
452    On Unix, this means every variable defined in Python's installed Makefile;
453    On Windows and Mac OS it's a much smaller set.
454
455    With arguments, return a list of values that result from looking up
456    each argument in the configuration variable dictionary.
457    """
458    import re
459    global _CONFIG_VARS
460    if _CONFIG_VARS is None:
461        _CONFIG_VARS = {}
462        # Normalized versions of prefix and exec_prefix are handy to have;
463        # in fact, these are the standard versions used most places in the
464        # Distutils.
465        _CONFIG_VARS['prefix'] = _PREFIX
466        _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
467        _CONFIG_VARS['py_version'] = _PY_VERSION
468        _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
469        _CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2]
470        _CONFIG_VARS['base'] = _PREFIX
471        _CONFIG_VARS['platbase'] = _EXEC_PREFIX
472        _CONFIG_VARS['projectbase'] = _PROJECT_BASE
473
474        if os.name in ('nt', 'os2'):
475            _init_non_posix(_CONFIG_VARS)
476        if os.name == 'posix':
477            _init_posix(_CONFIG_VARS)
478
479        # Setting 'userbase' is done below the call to the
480        # init function to enable using 'get_config_var' in
481        # the init-function.
482        _CONFIG_VARS['userbase'] = _getuserbase()
483
484        if 'srcdir' not in _CONFIG_VARS:
485            _CONFIG_VARS['srcdir'] = _PROJECT_BASE
486
487        # Convert srcdir into an absolute path if it appears necessary.
488        # Normally it is relative to the build directory.  However, during
489        # testing, for example, we might be running a non-installed python
490        # from a different directory.
491        if _PYTHON_BUILD and os.name == "posix":
492            base = _PROJECT_BASE
493            try:
494                cwd = os.getcwd()
495            except OSError:
496                cwd = None
497            if (not os.path.isabs(_CONFIG_VARS['srcdir']) and
498                base != cwd):
499                # srcdir is relative and we are not in the same directory
500                # as the executable. Assume executable is in the build
501                # directory and make srcdir absolute.
502                srcdir = os.path.join(base, _CONFIG_VARS['srcdir'])
503                _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
504
505        # OS X platforms require special customization to handle
506        # multi-architecture, multi-os-version installers
507        if sys.platform == 'darwin':
508            import _osx_support
509            _osx_support.customize_config_vars(_CONFIG_VARS)
510
511    if args:
512        vals = []
513        for name in args:
514            vals.append(_CONFIG_VARS.get(name))
515        return vals
516    else:
517        return _CONFIG_VARS
518
519def get_config_var(name):
520    """Return the value of a single variable using the dictionary returned by
521    'get_config_vars()'.
522
523    Equivalent to get_config_vars().get(name)
524    """
525    return get_config_vars().get(name)
526
527def get_platform():
528    """Return a string that identifies the current platform.
529
530    This is used mainly to distinguish platform-specific build directories and
531    platform-specific built distributions.  Typically includes the OS name
532    and version and the architecture (as supplied by 'os.uname()'),
533    although the exact information included depends on the OS; eg. for IRIX
534    the architecture isn't particularly important (IRIX only runs on SGI
535    hardware), but for Linux the kernel version isn't particularly
536    important.
537
538    Examples of returned values:
539       linux-i586
540       linux-alpha (?)
541       solaris-2.6-sun4u
542       irix-5.3
543       irix64-6.2
544
545    Windows will return one of:
546       win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
547       win-ia64 (64bit Windows on Itanium)
548       win32 (all others - specifically, sys.platform is returned)
549
550    For other non-POSIX platforms, currently just returns 'sys.platform'.
551    """
552    import re
553    if os.name == 'nt':
554        # sniff sys.version for architecture.
555        prefix = " bit ("
556        i = sys.version.find(prefix)
557        if i == -1:
558            return sys.platform
559        j = sys.version.find(")", i)
560        look = sys.version[i+len(prefix):j].lower()
561        if look == 'amd64':
562            return 'win-amd64'
563        if look == 'itanium':
564            return 'win-ia64'
565        return sys.platform
566
567    # Set for cross builds explicitly
568    if "_PYTHON_HOST_PLATFORM" in os.environ:
569        return os.environ["_PYTHON_HOST_PLATFORM"]
570
571    if os.name != "posix" or not hasattr(os, 'uname'):
572        # XXX what about the architecture? NT is Intel or Alpha,
573        # Mac OS is M68k or PPC, etc.
574        return sys.platform
575
576    # Try to distinguish various flavours of Unix
577    osname, host, release, version, machine = os.uname()
578
579    # Convert the OS name to lowercase, remove '/' characters
580    # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
581    osname = osname.lower().replace('/', '')
582    machine = machine.replace(' ', '_')
583    machine = machine.replace('/', '-')
584
585    if osname[:5] == "linux":
586        # At least on Linux/Intel, 'machine' is the processor --
587        # i386, etc.
588        # XXX what about Alpha, SPARC, etc?
589        return  "%s-%s" % (osname, machine)
590    elif osname[:5] == "sunos":
591        if release[0] >= "5":           # SunOS 5 == Solaris 2
592            osname = "solaris"
593            release = "%d.%s" % (int(release[0]) - 3, release[2:])
594            # We can't use "platform.architecture()[0]" because a
595            # bootstrap problem. We use a dict to get an error
596            # if some suspicious happens.
597            bitness = {2147483647:"32bit", 9223372036854775807:"64bit"}
598            machine += ".%s" % bitness[sys.maxint]
599        # fall through to standard osname-release-machine representation
600    elif osname[:4] == "irix":              # could be "irix64"!
601        return "%s-%s" % (osname, release)
602    elif osname[:3] == "aix":
603        return "%s-%s.%s" % (osname, version, release)
604    elif osname[:6] == "cygwin":
605        osname = "cygwin"
606        rel_re = re.compile (r'[\d.]+')
607        m = rel_re.match(release)
608        if m:
609            release = m.group()
610    elif osname[:6] == "darwin":
611        import _osx_support
612        osname, release, machine = _osx_support.get_platform_osx(
613                                            get_config_vars(),
614                                            osname, release, machine)
615
616    return "%s-%s-%s" % (osname, release, machine)
617
618
619def get_python_version():
620    return _PY_VERSION_SHORT
621
622
623def _print_dict(title, data):
624    for index, (key, value) in enumerate(sorted(data.items())):
625        if index == 0:
626            print '%s: ' % (title)
627        print '\t%s = "%s"' % (key, value)
628
629
630def _main():
631    """Display all information sysconfig detains."""
632    if '--generate-posix-vars' in sys.argv:
633        _generate_posix_vars()
634        return
635    print 'Platform: "%s"' % get_platform()
636    print 'Python version: "%s"' % get_python_version()
637    print 'Current installation scheme: "%s"' % _get_default_scheme()
638    print
639    _print_dict('Paths', get_paths())
640    print
641    _print_dict('Variables', get_config_vars())
642
643
644if __name__ == '__main__':
645    _main()
646