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/python{py_version_short}',
31        'platstdlib': '{base}/lib/python{py_version_short}',
32        'purelib': '{base}/lib/python{py_version_short}',
33        'platlib': '{base}/lib/python{py_version_short}',
34        'include': '{base}/include/python{py_version_short}',
35        'platinclude': '{base}/include/python{py_version_short}',
36        'scripts': '{base}/bin',
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}/lib/python{py_version_short}',
60        'platstdlib': '{userbase}/lib/python{py_version_short}',
61        'purelib': '{userbase}/lib/python{py_version_short}',
62        'platlib': '{userbase}/lib/python{py_version_short}',
63        'include': '{userbase}/include/python{py_version_short}',
64        'scripts': '{userbase}/bin',
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/AMD64
116if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
117    _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
118
119# set for cross builds
120if "_PYTHON_PROJECT_BASE" in os.environ:
121    # the build directory for posix builds
122    _PROJECT_BASE = os.path.normpath(os.path.abspath("."))
123def is_python_build():
124    for fn in ("Setup.dist", "Setup.local"):
125        if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
126            return True
127    return False
128
129_PYTHON_BUILD = is_python_build()
130
131if _PYTHON_BUILD:
132    for scheme in ('posix_prefix', 'posix_home'):
133        _INSTALL_SCHEMES[scheme]['include'] = '{srcdir}/Include'
134        _INSTALL_SCHEMES[scheme]['platinclude'] = '{projectbase}'
135
136    # GCC(mingw) use posix build system
137    if os.name == "nt" and sys.version.find('GCC') >= 0:
138        _INSTALL_SCHEMES['nt']['include'] = '{srcdir}/Include'
139        _INSTALL_SCHEMES['nt']['platinclude'] = '{projectbase}'
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    if _PYTHON_BUILD:
283        return os.path.join(_PROJECT_BASE, "Makefile")
284    return os.path.join(get_path('platstdlib'), "config", "Makefile")
285
286def _generate_posix_vars():
287    """Generate the Python module containing build-time variables."""
288    import pprint
289    vars = {}
290    # load the installed Makefile:
291    makefile = _get_makefile_filename()
292    try:
293        _parse_makefile(makefile, vars)
294    except IOError, e:
295        msg = "invalid Python installation: unable to open %s" % makefile
296        if hasattr(e, "strerror"):
297            msg = msg + " (%s)" % e.strerror
298        raise IOError(msg)
299
300    # load the installed pyconfig.h:
301    config_h = get_config_h_filename()
302    try:
303        with open(config_h) as f:
304            parse_config_h(f, vars)
305    except IOError, e:
306        msg = "invalid Python installation: unable to open %s" % config_h
307        if hasattr(e, "strerror"):
308            msg = msg + " (%s)" % e.strerror
309        raise IOError(msg)
310
311    # On AIX, there are wrong paths to the linker scripts in the Makefile
312    # -- these paths are relative to the Python source, but when installed
313    # the scripts are in another directory.
314    if _PYTHON_BUILD:
315        vars['LDSHARED'] = vars['BLDSHARED']
316
317    # There's a chicken-and-egg situation on OS X with regards to the
318    # _sysconfigdata module after the changes introduced by #15298:
319    # get_config_vars() is called by get_platform() as part of the
320    # `make pybuilddir.txt` target -- which is a precursor to the
321    # _sysconfigdata.py module being constructed.  Unfortunately,
322    # get_config_vars() eventually calls _init_posix(), which attempts
323    # to import _sysconfigdata, which we won't have built yet.  In order
324    # for _init_posix() to work, if we're on Darwin, just mock up the
325    # _sysconfigdata module manually and populate it with the build vars.
326    # This is more than sufficient for ensuring the subsequent call to
327    # get_platform() succeeds.
328    name = '_sysconfigdata'
329    if 'darwin' in sys.platform:
330        import imp
331        module = imp.new_module(name)
332        module.build_time_vars = vars
333        sys.modules[name] = module
334
335    pybuilddir = 'build/lib.%s-%s' % (get_platform(), sys.version[:3])
336    if hasattr(sys, "gettotalrefcount"):
337        pybuilddir += '-pydebug'
338    try:
339        os.makedirs(pybuilddir)
340    except OSError:
341        pass
342    destfile = os.path.join(pybuilddir, name + '.py')
343
344    with open(destfile, 'wb') as f:
345        f.write('# system configuration generated and used by'
346                ' the sysconfig module\n')
347        f.write('build_time_vars = ')
348        pprint.pprint(vars, stream=f)
349
350    # Create file used for sys.path fixup -- see Modules/getpath.c
351    with open('pybuilddir.txt', 'w') as f:
352        f.write(pybuilddir)
353
354def _init_posix(vars):
355    """Initialize the module as appropriate for POSIX systems."""
356    # _sysconfigdata is generated at build time, see _generate_posix_vars()
357    from _sysconfigdata import build_time_vars
358    vars.update(build_time_vars)
359
360def _init_non_posix(vars):
361    """Initialize the module as appropriate for NT"""
362    # set basic install directories
363    vars['LIBDEST'] = get_path('stdlib')
364    vars['BINLIBDEST'] = get_path('platstdlib')
365    vars['INCLUDEPY'] = get_path('include')
366    vars['SO'] = '.pyd'
367    vars['EXE'] = '.exe'
368    vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
369    vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
370
371#
372# public APIs
373#
374
375
376def parse_config_h(fp, vars=None):
377    """Parse a config.h-style file.
378
379    A dictionary containing name/value pairs is returned.  If an
380    optional dictionary is passed in as the second argument, it is
381    used instead of a new dictionary.
382    """
383    import re
384    if vars is None:
385        vars = {}
386    define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
387    undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
388
389    while True:
390        line = fp.readline()
391        if not line:
392            break
393        m = define_rx.match(line)
394        if m:
395            n, v = m.group(1, 2)
396            try: v = int(v)
397            except ValueError: pass
398            vars[n] = v
399        else:
400            m = undef_rx.match(line)
401            if m:
402                vars[m.group(1)] = 0
403    return vars
404
405def get_config_h_filename():
406    """Returns the path of pyconfig.h."""
407    if _PYTHON_BUILD:
408        # GCC(mingw): os.name is "nt" but build system is posix
409        if os.name == "nt" and sys.version.find('GCC') < 0:
410            inc_dir = os.path.join(_PROJECT_BASE, "PC")
411        else:
412            inc_dir = _PROJECT_BASE
413    else:
414        inc_dir = get_path('platinclude')
415    return os.path.join(inc_dir, 'pyconfig.h')
416
417def get_scheme_names():
418    """Returns a tuple containing the schemes names."""
419    schemes = _INSTALL_SCHEMES.keys()
420    schemes.sort()
421    return tuple(schemes)
422
423def get_path_names():
424    """Returns a tuple containing the paths names."""
425    return _SCHEME_KEYS
426
427def get_paths(scheme=_get_default_scheme(), vars=None, expand=True):
428    """Returns a mapping containing an install scheme.
429
430    ``scheme`` is the install scheme name. If not provided, it will
431    return the default scheme for the current platform.
432    """
433    if expand:
434        return _expand_vars(scheme, vars)
435    else:
436        return _INSTALL_SCHEMES[scheme]
437
438def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True):
439    """Returns a path corresponding to the scheme.
440
441    ``scheme`` is the install scheme name.
442    """
443    return get_paths(scheme, vars, expand)[name]
444
445def get_config_vars(*args):
446    """With no arguments, return a dictionary of all configuration
447    variables relevant for the current platform.
448
449    On Unix, this means every variable defined in Python's installed Makefile;
450    On Windows and Mac OS it's a much smaller set.
451
452    With arguments, return a list of values that result from looking up
453    each argument in the configuration variable dictionary.
454    """
455    import re
456    global _CONFIG_VARS
457    if _CONFIG_VARS is None:
458        _CONFIG_VARS = {}
459        # Normalized versions of prefix and exec_prefix are handy to have;
460        # in fact, these are the standard versions used most places in the
461        # Distutils.
462        _CONFIG_VARS['prefix'] = _PREFIX
463        _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
464        _CONFIG_VARS['py_version'] = _PY_VERSION
465        _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
466        _CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2]
467        _CONFIG_VARS['base'] = _PREFIX
468        _CONFIG_VARS['platbase'] = _EXEC_PREFIX
469        _CONFIG_VARS['projectbase'] = _PROJECT_BASE
470
471        # GCC(mingw) use posix build system
472        posix_build = None
473        if os.name == 'posix':
474            posix_build = True
475        else:
476            if os.name in ('nt', 'os2'):
477                if sys.version.find('GCC') >= 0:
478                    posix_build = True
479                else:
480                    posix_build = False
481        if posix_build == False:
482            _init_non_posix(_CONFIG_VARS)
483        if posix_build == True:
484            _init_posix(_CONFIG_VARS)
485
486        # Setting 'userbase' is done below the call to the
487        # init function to enable using 'get_config_var' in
488        # the init-function.
489        _CONFIG_VARS['userbase'] = _getuserbase()
490
491        if 'srcdir' not in _CONFIG_VARS:
492            _CONFIG_VARS['srcdir'] = _PROJECT_BASE
493
494        # Convert srcdir into an absolute path if it appears necessary.
495        # Normally it is relative to the build directory.  However, during
496        # testing, for example, we might be running a non-installed python
497        # from a different directory.
498        if _PYTHON_BUILD and posix_build == True:
499            base = _PROJECT_BASE
500            try:
501                cwd = os.getcwd()
502            except OSError:
503                cwd = None
504            if (not os.path.isabs(_CONFIG_VARS['srcdir']) and
505                base != cwd):
506                # srcdir is relative and we are not in the same directory
507                # as the executable. Assume executable is in the build
508                # directory and make srcdir absolute.
509                srcdir = os.path.join(base, _CONFIG_VARS['srcdir'])
510                _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
511
512        # OS X platforms require special customization to handle
513        # multi-architecture, multi-os-version installers
514        if sys.platform == 'darwin':
515            import _osx_support
516            _osx_support.customize_config_vars(_CONFIG_VARS)
517
518    if args:
519        vals = []
520        for name in args:
521            vals.append(_CONFIG_VARS.get(name))
522        return vals
523    else:
524        return _CONFIG_VARS
525
526def get_config_var(name):
527    """Return the value of a single variable using the dictionary returned by
528    'get_config_vars()'.
529
530    Equivalent to get_config_vars().get(name)
531    """
532    return get_config_vars().get(name)
533
534def get_platform():
535    """Return a string that identifies the current platform.
536
537    This is used mainly to distinguish platform-specific build directories and
538    platform-specific built distributions.  Typically includes the OS name
539    and version and the architecture (as supplied by 'os.uname()'),
540    although the exact information included depends on the OS; eg. for IRIX
541    the architecture isn't particularly important (IRIX only runs on SGI
542    hardware), but for Linux the kernel version isn't particularly
543    important.
544
545    Examples of returned values:
546       linux-i586
547       linux-alpha (?)
548       solaris-2.6-sun4u
549       irix-5.3
550       irix64-6.2
551
552    Windows will return one of:
553       win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
554       win-ia64 (64bit Windows on Itanium)
555       win32 (all others - specifically, sys.platform is returned)
556
557    For other non-POSIX platforms, currently just returns 'sys.platform'.
558    """
559    import re
560    if os.name == 'nt':
561        # sniff sys.version for architecture.
562        prefix = " bit ("
563        i = sys.version.find(prefix)
564        if i == -1:
565            return sys.platform
566        j = sys.version.find(")", i)
567        look = sys.version[i+len(prefix):j].lower()
568        if look == 'amd64':
569            return 'win-amd64'
570        if look == 'itanium':
571            return 'win-ia64'
572        return sys.platform
573
574    # Set for cross builds explicitly
575    if "_PYTHON_HOST_PLATFORM" in os.environ:
576        return os.environ["_PYTHON_HOST_PLATFORM"]
577
578    if os.name != "posix" or not hasattr(os, 'uname'):
579        # XXX what about the architecture? NT is Intel or Alpha,
580        # Mac OS is M68k or PPC, etc.
581        return sys.platform
582
583    # Try to distinguish various flavours of Unix
584    osname, host, release, version, machine = os.uname()
585
586    # Convert the OS name to lowercase, remove '/' characters
587    # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
588    osname = osname.lower().replace('/', '')
589    machine = machine.replace(' ', '_')
590    machine = machine.replace('/', '-')
591
592    if osname[:5] == "linux":
593        # At least on Linux/Intel, 'machine' is the processor --
594        # i386, etc.
595        # XXX what about Alpha, SPARC, etc?
596        return  "%s-%s" % (osname, machine)
597    elif osname[:5] == "sunos":
598        if release[0] >= "5":           # SunOS 5 == Solaris 2
599            osname = "solaris"
600            release = "%d.%s" % (int(release[0]) - 3, release[2:])
601            # We can't use "platform.architecture()[0]" because a
602            # bootstrap problem. We use a dict to get an error
603            # if some suspicious happens.
604            bitness = {2147483647:"32bit", 9223372036854775807:"64bit"}
605            machine += ".%s" % bitness[sys.maxint]
606        # fall through to standard osname-release-machine representation
607    elif osname[:4] == "irix":              # could be "irix64"!
608        return "%s-%s" % (osname, release)
609    elif osname[:3] == "aix":
610        return "%s-%s.%s" % (osname, version, release)
611    elif osname[:6] == "cygwin":
612        osname = "cygwin"
613        rel_re = re.compile (r'[\d.]+')
614        m = rel_re.match(release)
615        if m:
616            release = m.group()
617    elif osname[:6] == "darwin":
618        import _osx_support
619        osname, release, machine = _osx_support.get_platform_osx(
620                                            get_config_vars(),
621                                            osname, release, machine)
622
623    return "%s-%s-%s" % (osname, release, machine)
624
625
626def get_python_version():
627    return _PY_VERSION_SHORT
628
629
630def _print_dict(title, data):
631    for index, (key, value) in enumerate(sorted(data.items())):
632        if index == 0:
633            print '%s: ' % (title)
634        print '\t%s = "%s"' % (key, value)
635
636
637def _main():
638    """Display all information sysconfig detains."""
639    if '--generate-posix-vars' in sys.argv:
640        _generate_posix_vars()
641        return
642    print 'Platform: "%s"' % get_platform()
643    print 'Python version: "%s"' % get_python_version()
644    print 'Current installation scheme: "%s"' % _get_default_scheme()
645    print
646    _print_dict('Paths', get_paths())
647    print
648    _print_dict('Variables', get_config_vars())
649
650
651if __name__ == '__main__':
652    _main()
653