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