1"""Append module search paths for third-party packages to sys.path.
2
3****************************************************************
4* This module is automatically imported during initialization. *
5****************************************************************
6
7This will append site-specific paths to the module search path.  On
8Unix (including Mac OSX), it starts with sys.prefix and
9sys.exec_prefix (if different) and appends
10lib/python<version>/site-packages.
11On other platforms (such as Windows), it tries each of the
12prefixes directly, as well as with lib/site-packages appended.  The
13resulting directories, if they exist, are appended to sys.path, and
14also inspected for path configuration files.
15
16If a file named "pyvenv.cfg" exists one directory above sys.executable,
17sys.prefix and sys.exec_prefix are set to that directory and
18it is also checked for site-packages (sys.base_prefix and
19sys.base_exec_prefix will always be the "real" prefixes of the Python
20installation). If "pyvenv.cfg" (a bootstrap configuration file) contains
21the key "include-system-site-packages" set to anything other than "false"
22(case-insensitive), the system-level prefixes will still also be
23searched for site-packages; otherwise they won't.
24
25All of the resulting site-specific directories, if they exist, are
26appended to sys.path, and also inspected for path configuration
27files.
28
29A path configuration file is a file whose name has the form
30<package>.pth; its contents are additional directories (one per line)
31to be added to sys.path.  Non-existing directories (or
32non-directories) are never added to sys.path; no directory is added to
33sys.path more than once.  Blank lines and lines beginning with
34'#' are skipped. Lines starting with 'import' are executed.
35
36For example, suppose sys.prefix and sys.exec_prefix are set to
37/usr/local and there is a directory /usr/local/lib/python2.5/site-packages
38with three subdirectories, foo, bar and spam, and two path
39configuration files, foo.pth and bar.pth.  Assume foo.pth contains the
40following:
41
42  # foo package configuration
43  foo
44  bar
45  bletch
46
47and bar.pth contains:
48
49  # bar package configuration
50  bar
51
52Then the following directories are added to sys.path, in this order:
53
54  /usr/local/lib/python2.5/site-packages/bar
55  /usr/local/lib/python2.5/site-packages/foo
56
57Note that bletch is omitted because it doesn't exist; bar precedes foo
58because bar.pth comes alphabetically before foo.pth; and spam is
59omitted because it is not mentioned in either path configuration file.
60
61The readline module is also automatically configured to enable
62completion for systems that support it.  This can be overridden in
63sitecustomize, usercustomize or PYTHONSTARTUP.  Starting Python in
64isolated mode (-I) disables automatic readline configuration.
65
66After these operations, an attempt is made to import a module
67named sitecustomize, which can perform arbitrary additional
68site-specific customizations.  If this import fails with an
69ImportError exception, it is silently ignored.
70"""
71
72import sys
73import os
74import builtins
75import _sitebuiltins
76import io
77
78# Prefixes for site-packages; add additional prefixes like /usr/local here
79PREFIXES = [sys.prefix, sys.exec_prefix]
80# Enable per user site-packages directory
81# set it to False to disable the feature or True to force the feature
82ENABLE_USER_SITE = None
83
84# for distutils.commands.install
85# These values are initialized by the getuserbase() and getusersitepackages()
86# functions, through the main() function when Python starts.
87USER_SITE = None
88USER_BASE = None
89
90
91def _trace(message):
92    if sys.flags.verbose:
93        print(message, file=sys.stderr)
94
95
96def makepath(*paths):
97    dir = os.path.join(*paths)
98    try:
99        dir = os.path.abspath(dir)
100    except OSError:
101        pass
102    return dir, os.path.normcase(dir)
103
104
105def abs_paths():
106    """Set all module __file__ and __cached__ attributes to an absolute path"""
107    for m in set(sys.modules.values()):
108        loader_module = None
109        try:
110            loader_module = m.__loader__.__module__
111        except AttributeError:
112            try:
113                loader_module = m.__spec__.loader.__module__
114            except AttributeError:
115                pass
116        if loader_module not in {'_frozen_importlib', '_frozen_importlib_external'}:
117            continue   # don't mess with a PEP 302-supplied __file__
118        try:
119            m.__file__ = os.path.abspath(m.__file__)
120        except (AttributeError, OSError, TypeError):
121            pass
122        try:
123            m.__cached__ = os.path.abspath(m.__cached__)
124        except (AttributeError, OSError, TypeError):
125            pass
126
127
128def removeduppaths():
129    """ Remove duplicate entries from sys.path along with making them
130    absolute"""
131    # This ensures that the initial path provided by the interpreter contains
132    # only absolute pathnames, even if we're running from the build directory.
133    L = []
134    known_paths = set()
135    for dir in sys.path:
136        # Filter out duplicate paths (on case-insensitive file systems also
137        # if they only differ in case); turn relative paths into absolute
138        # paths.
139        dir, dircase = makepath(dir)
140        if dircase not in known_paths:
141            L.append(dir)
142            known_paths.add(dircase)
143    sys.path[:] = L
144    return known_paths
145
146
147def _init_pathinfo():
148    """Return a set containing all existing file system items from sys.path."""
149    d = set()
150    for item in sys.path:
151        try:
152            if os.path.exists(item):
153                _, itemcase = makepath(item)
154                d.add(itemcase)
155        except TypeError:
156            continue
157    return d
158
159
160def addpackage(sitedir, name, known_paths):
161    """Process a .pth file within the site-packages directory:
162       For each line in the file, either combine it with sitedir to a path
163       and add that to known_paths, or execute it if it starts with 'import '.
164    """
165    if known_paths is None:
166        known_paths = _init_pathinfo()
167        reset = True
168    else:
169        reset = False
170    fullname = os.path.join(sitedir, name)
171    _trace(f"Processing .pth file: {fullname!r}")
172    try:
173        # locale encoding is not ideal especially on Windows. But we have used
174        # it for a long time. setuptools uses the locale encoding too.
175        f = io.TextIOWrapper(io.open_code(fullname), encoding="locale")
176    except OSError:
177        return
178    with f:
179        for n, line in enumerate(f):
180            if line.startswith("#"):
181                continue
182            if line.strip() == "":
183                continue
184            try:
185                if line.startswith(("import ", "import\t")):
186                    exec(line)
187                    continue
188                line = line.rstrip()
189                dir, dircase = makepath(sitedir, line)
190                if not dircase in known_paths and os.path.exists(dir):
191                    sys.path.append(dir)
192                    known_paths.add(dircase)
193            except Exception:
194                print("Error processing line {:d} of {}:\n".format(n+1, fullname),
195                      file=sys.stderr)
196                import traceback
197                for record in traceback.format_exception(*sys.exc_info()):
198                    for line in record.splitlines():
199                        print('  '+line, file=sys.stderr)
200                print("\nRemainder of file ignored", file=sys.stderr)
201                break
202    if reset:
203        known_paths = None
204    return known_paths
205
206
207def addsitedir(sitedir, known_paths=None):
208    """Add 'sitedir' argument to sys.path if missing and handle .pth files in
209    'sitedir'"""
210    _trace(f"Adding directory: {sitedir!r}")
211    if known_paths is None:
212        known_paths = _init_pathinfo()
213        reset = True
214    else:
215        reset = False
216    sitedir, sitedircase = makepath(sitedir)
217    if not sitedircase in known_paths:
218        sys.path.append(sitedir)        # Add path component
219        known_paths.add(sitedircase)
220    try:
221        names = os.listdir(sitedir)
222    except OSError:
223        return
224    names = [name for name in names if name.endswith(".pth")]
225    for name in sorted(names):
226        addpackage(sitedir, name, known_paths)
227    if reset:
228        known_paths = None
229    return known_paths
230
231
232def check_enableusersite():
233    """Check if user site directory is safe for inclusion
234
235    The function tests for the command line flag (including environment var),
236    process uid/gid equal to effective uid/gid.
237
238    None: Disabled for security reasons
239    False: Disabled by user (command line option)
240    True: Safe and enabled
241    """
242    if sys.flags.no_user_site:
243        return False
244
245    if hasattr(os, "getuid") and hasattr(os, "geteuid"):
246        # check process uid == effective uid
247        if os.geteuid() != os.getuid():
248            return None
249    if hasattr(os, "getgid") and hasattr(os, "getegid"):
250        # check process gid == effective gid
251        if os.getegid() != os.getgid():
252            return None
253
254    return True
255
256
257# NOTE: sysconfig and it's dependencies are relatively large but site module
258# needs very limited part of them.
259# To speedup startup time, we have copy of them.
260#
261# See https://bugs.python.org/issue29585
262
263# Copy of sysconfig._getuserbase()
264def _getuserbase():
265    env_base = os.environ.get("PYTHONUSERBASE", None)
266    if env_base:
267        return env_base
268
269    # Emscripten, VxWorks, and WASI have no home directories
270    if sys.platform in {"emscripten", "vxworks", "wasi"}:
271        return None
272
273    def joinuser(*args):
274        return os.path.expanduser(os.path.join(*args))
275
276    if os.name == "nt":
277        base = os.environ.get("APPDATA") or "~"
278        return joinuser(base, "Python")
279
280    if sys.platform == "darwin" and sys._framework:
281        return joinuser("~", "Library", sys._framework,
282                        "%d.%d" % sys.version_info[:2])
283
284    return joinuser("~", ".local")
285
286
287# Same to sysconfig.get_path('purelib', os.name+'_user')
288def _get_path(userbase):
289    version = sys.version_info
290
291    if os.name == 'nt':
292        ver_nodot = sys.winver.replace('.', '')
293        return f'{userbase}\\Python{ver_nodot}\\site-packages'
294
295    if sys.platform == 'darwin' and sys._framework:
296        return f'{userbase}/lib/python/site-packages'
297
298    return f'{userbase}/lib/python{version[0]}.{version[1]}/site-packages'
299
300
301def getuserbase():
302    """Returns the `user base` directory path.
303
304    The `user base` directory can be used to store data. If the global
305    variable ``USER_BASE`` is not initialized yet, this function will also set
306    it.
307    """
308    global USER_BASE
309    if USER_BASE is None:
310        USER_BASE = _getuserbase()
311    return USER_BASE
312
313
314def getusersitepackages():
315    """Returns the user-specific site-packages directory path.
316
317    If the global variable ``USER_SITE`` is not initialized yet, this
318    function will also set it.
319    """
320    global USER_SITE, ENABLE_USER_SITE
321    userbase = getuserbase() # this will also set USER_BASE
322
323    if USER_SITE is None:
324        if userbase is None:
325            ENABLE_USER_SITE = False # disable user site and return None
326        else:
327            USER_SITE = _get_path(userbase)
328
329    return USER_SITE
330
331def addusersitepackages(known_paths):
332    """Add a per user site-package to sys.path
333
334    Each user has its own python directory with site-packages in the
335    home directory.
336    """
337    # get the per user site-package path
338    # this call will also make sure USER_BASE and USER_SITE are set
339    _trace("Processing user site-packages")
340    user_site = getusersitepackages()
341
342    if ENABLE_USER_SITE and os.path.isdir(user_site):
343        addsitedir(user_site, known_paths)
344    return known_paths
345
346def getsitepackages(prefixes=None):
347    """Returns a list containing all global site-packages directories.
348
349    For each directory present in ``prefixes`` (or the global ``PREFIXES``),
350    this function will find its `site-packages` subdirectory depending on the
351    system environment, and will return a list of full paths.
352    """
353    sitepackages = []
354    seen = set()
355
356    if prefixes is None:
357        prefixes = PREFIXES
358
359    for prefix in prefixes:
360        if not prefix or prefix in seen:
361            continue
362        seen.add(prefix)
363
364        if os.sep == '/':
365            libdirs = [sys.platlibdir]
366            if sys.platlibdir != "lib":
367                libdirs.append("lib")
368
369            for libdir in libdirs:
370                path = os.path.join(prefix, libdir,
371                                    "python%d.%d" % sys.version_info[:2],
372                                    "site-packages")
373                sitepackages.append(path)
374        else:
375            sitepackages.append(prefix)
376            sitepackages.append(os.path.join(prefix, "Lib", "site-packages"))
377    return sitepackages
378
379def addsitepackages(known_paths, prefixes=None):
380    """Add site-packages to sys.path"""
381    _trace("Processing global site-packages")
382    for sitedir in getsitepackages(prefixes):
383        if os.path.isdir(sitedir):
384            addsitedir(sitedir, known_paths)
385
386    return known_paths
387
388def setquit():
389    """Define new builtins 'quit' and 'exit'.
390
391    These are objects which make the interpreter exit when called.
392    The repr of each object contains a hint at how it works.
393
394    """
395    if os.sep == '\\':
396        eof = 'Ctrl-Z plus Return'
397    else:
398        eof = 'Ctrl-D (i.e. EOF)'
399
400    builtins.quit = _sitebuiltins.Quitter('quit', eof)
401    builtins.exit = _sitebuiltins.Quitter('exit', eof)
402
403
404def setcopyright():
405    """Set 'copyright' and 'credits' in builtins"""
406    builtins.copyright = _sitebuiltins._Printer("copyright", sys.copyright)
407    if sys.platform[:4] == 'java':
408        builtins.credits = _sitebuiltins._Printer(
409            "credits",
410            "Jython is maintained by the Jython developers (www.jython.org).")
411    else:
412        builtins.credits = _sitebuiltins._Printer("credits", """\
413    Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
414    for supporting Python development.  See www.python.org for more information.""")
415    files, dirs = [], []
416    # Not all modules are required to have a __file__ attribute.  See
417    # PEP 420 for more details.
418    here = getattr(sys, '_stdlib_dir', None)
419    if not here and hasattr(os, '__file__'):
420        here = os.path.dirname(os.__file__)
421    if here:
422        files.extend(["LICENSE.txt", "LICENSE"])
423        dirs.extend([os.path.join(here, os.pardir), here, os.curdir])
424    builtins.license = _sitebuiltins._Printer(
425        "license",
426        "See https://www.python.org/psf/license/",
427        files, dirs)
428
429
430def sethelper():
431    builtins.help = _sitebuiltins._Helper()
432
433def enablerlcompleter():
434    """Enable default readline configuration on interactive prompts, by
435    registering a sys.__interactivehook__.
436
437    If the readline module can be imported, the hook will set the Tab key
438    as completion key and register ~/.python_history as history file.
439    This can be overridden in the sitecustomize or usercustomize module,
440    or in a PYTHONSTARTUP file.
441    """
442    def register_readline():
443        import atexit
444        try:
445            import readline
446            import rlcompleter
447        except ImportError:
448            return
449
450        # Reading the initialization (config) file may not be enough to set a
451        # completion key, so we set one first and then read the file.
452        readline_doc = getattr(readline, '__doc__', '')
453        if readline_doc is not None and 'libedit' in readline_doc:
454            readline.parse_and_bind('bind ^I rl_complete')
455        else:
456            readline.parse_and_bind('tab: complete')
457
458        try:
459            readline.read_init_file()
460        except OSError:
461            # An OSError here could have many causes, but the most likely one
462            # is that there's no .inputrc file (or .editrc file in the case of
463            # Mac OS X + libedit) in the expected location.  In that case, we
464            # want to ignore the exception.
465            pass
466
467        if readline.get_current_history_length() == 0:
468            # If no history was loaded, default to .python_history.
469            # The guard is necessary to avoid doubling history size at
470            # each interpreter exit when readline was already configured
471            # through a PYTHONSTARTUP hook, see:
472            # http://bugs.python.org/issue5845#msg198636
473            history = os.path.join(os.path.expanduser('~'),
474                                   '.python_history')
475            try:
476                readline.read_history_file(history)
477            except OSError:
478                pass
479
480            def write_history():
481                try:
482                    readline.write_history_file(history)
483                except OSError:
484                    # bpo-19891, bpo-41193: Home directory does not exist
485                    # or is not writable, or the filesystem is read-only.
486                    pass
487
488            atexit.register(write_history)
489
490    sys.__interactivehook__ = register_readline
491
492def venv(known_paths):
493    global PREFIXES, ENABLE_USER_SITE
494
495    env = os.environ
496    if sys.platform == 'darwin' and '__PYVENV_LAUNCHER__' in env:
497        executable = sys._base_executable = os.environ['__PYVENV_LAUNCHER__']
498    else:
499        executable = sys.executable
500    exe_dir, _ = os.path.split(os.path.abspath(executable))
501    site_prefix = os.path.dirname(exe_dir)
502    sys._home = None
503    conf_basename = 'pyvenv.cfg'
504    candidate_confs = [
505        conffile for conffile in (
506            os.path.join(exe_dir, conf_basename),
507            os.path.join(site_prefix, conf_basename)
508            )
509        if os.path.isfile(conffile)
510        ]
511
512    if candidate_confs:
513        virtual_conf = candidate_confs[0]
514        system_site = "true"
515        # Issue 25185: Use UTF-8, as that's what the venv module uses when
516        # writing the file.
517        with open(virtual_conf, encoding='utf-8') as f:
518            for line in f:
519                if '=' in line:
520                    key, _, value = line.partition('=')
521                    key = key.strip().lower()
522                    value = value.strip()
523                    if key == 'include-system-site-packages':
524                        system_site = value.lower()
525                    elif key == 'home':
526                        sys._home = value
527
528        sys.prefix = sys.exec_prefix = site_prefix
529
530        # Doing this here ensures venv takes precedence over user-site
531        addsitepackages(known_paths, [sys.prefix])
532
533        # addsitepackages will process site_prefix again if its in PREFIXES,
534        # but that's ok; known_paths will prevent anything being added twice
535        if system_site == "true":
536            PREFIXES.insert(0, sys.prefix)
537        else:
538            PREFIXES = [sys.prefix]
539            ENABLE_USER_SITE = False
540
541    return known_paths
542
543
544def execsitecustomize():
545    """Run custom site specific code, if available."""
546    try:
547        try:
548            import sitecustomize
549        except ImportError as exc:
550            if exc.name == 'sitecustomize':
551                pass
552            else:
553                raise
554    except Exception as err:
555        if sys.flags.verbose:
556            sys.excepthook(*sys.exc_info())
557        else:
558            sys.stderr.write(
559                "Error in sitecustomize; set PYTHONVERBOSE for traceback:\n"
560                "%s: %s\n" %
561                (err.__class__.__name__, err))
562
563
564def execusercustomize():
565    """Run custom user specific code, if available."""
566    try:
567        try:
568            import usercustomize
569        except ImportError as exc:
570            if exc.name == 'usercustomize':
571                pass
572            else:
573                raise
574    except Exception as err:
575        if sys.flags.verbose:
576            sys.excepthook(*sys.exc_info())
577        else:
578            sys.stderr.write(
579                "Error in usercustomize; set PYTHONVERBOSE for traceback:\n"
580                "%s: %s\n" %
581                (err.__class__.__name__, err))
582
583
584def main():
585    """Add standard site-specific directories to the module search path.
586
587    This function is called automatically when this module is imported,
588    unless the python interpreter was started with the -S flag.
589    """
590    global ENABLE_USER_SITE
591
592    orig_path = sys.path[:]
593    known_paths = removeduppaths()
594    if orig_path != sys.path:
595        # removeduppaths() might make sys.path absolute.
596        # fix __file__ and __cached__ of already imported modules too.
597        abs_paths()
598
599    known_paths = venv(known_paths)
600    if ENABLE_USER_SITE is None:
601        ENABLE_USER_SITE = check_enableusersite()
602    known_paths = addusersitepackages(known_paths)
603    known_paths = addsitepackages(known_paths)
604    setquit()
605    setcopyright()
606    sethelper()
607    if not sys.flags.isolated:
608        enablerlcompleter()
609    execsitecustomize()
610    if ENABLE_USER_SITE:
611        execusercustomize()
612
613# Prevent extending of sys.path when python was started with -S and
614# site is imported later.
615if not sys.flags.no_site:
616    main()
617
618def _script():
619    help = """\
620    %s [--user-base] [--user-site]
621
622    Without arguments print some useful information
623    With arguments print the value of USER_BASE and/or USER_SITE separated
624    by '%s'.
625
626    Exit codes with --user-base or --user-site:
627      0 - user site directory is enabled
628      1 - user site directory is disabled by user
629      2 - user site directory is disabled by super user
630          or for security reasons
631     >2 - unknown error
632    """
633    args = sys.argv[1:]
634    if not args:
635        user_base = getuserbase()
636        user_site = getusersitepackages()
637        print("sys.path = [")
638        for dir in sys.path:
639            print("    %r," % (dir,))
640        print("]")
641        def exists(path):
642            if path is not None and os.path.isdir(path):
643                return "exists"
644            else:
645                return "doesn't exist"
646        print(f"USER_BASE: {user_base!r} ({exists(user_base)})")
647        print(f"USER_SITE: {user_site!r} ({exists(user_site)})")
648        print(f"ENABLE_USER_SITE: {ENABLE_USER_SITE!r}")
649        sys.exit(0)
650
651    buffer = []
652    if '--user-base' in args:
653        buffer.append(USER_BASE)
654    if '--user-site' in args:
655        buffer.append(USER_SITE)
656
657    if buffer:
658        print(os.pathsep.join(buffer))
659        if ENABLE_USER_SITE:
660            sys.exit(0)
661        elif ENABLE_USER_SITE is False:
662            sys.exit(1)
663        elif ENABLE_USER_SITE is None:
664            sys.exit(2)
665        else:
666            sys.exit(3)
667    else:
668        import textwrap
669        print(textwrap.dedent(help % (sys.argv[0], os.pathsep)))
670        sys.exit(10)
671
672if __name__ == '__main__':
673    _script()
674