hyb
2026-01-30 44480e71b27aa9d4cb8441f50c873f1b110e9691
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#-----------------------------------------------------------------------------
# Copyright (c) 2013-2023, PyInstaller Development Team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: Apache-2.0
#-----------------------------------------------------------------------------
 
# To make pkg_resources work with frozen modules, we need to set the 'Provider' class for PyiFrozenLoader.
# This class decides where to look for resources and other stuff.
#
# 'pkg_resources.NullProvider' is dedicated to abitrary PEP302 loaders, such as our PyiFrozenLoader. It uses method
# __loader__.get_data() in methods pkg_resources.resource_string() and pkg_resources.resource_stream().
#
# We provide PyiFrozenProvider, which subclasses the NullProvider and implements _has(), _isdir(), and _listdir()
# methods, which are needed for pkg_resources.resource_exists(), resource_isdir(), and resource_listdir() to work. We
# cannot use the DefaultProvider, because it provides filesystem-only implementations (and overrides _get() with a
# filesystem-only one), whereas our provider needs to also support embedded resources.
#
# The PyiFrozenProvider allows querying/listing both PYZ-embedded and on-filesystem resources in a frozen package. The
# results are typically combined for both types of resources (e.g., when listing a directory or checking whether a
# resource exists). When the order of precedence matters, the PYZ-embedded resources take precedence over the
# on-filesystem ones, to keep the behavior consistent with the actual file content retrieval via _get() method (which in
# turn uses PyiFrozenLoader's get_data() method). For example, when checking whether a resource is a directory via
# _isdir(), a PYZ-embedded file will take precedence over a potential on-filesystem directory. Also, in contrast to
# unfrozen packages, the frozen ones do not contain source .py files, which are therefore absent from content listings.
 
 
def _pyi_rthook():
    import os
    import pathlib
    import sys
    import warnings
 
    with warnings.catch_warnings():
        warnings.filterwarnings(
            "ignore",
            category=UserWarning,
            message="pkg_resources is deprecated",
        )
        import pkg_resources
 
    import pyimod02_importers  # PyInstaller's bootstrap module
 
    SYS_PREFIX = pathlib.PurePath(sys._MEIPASS)
 
    class _TocFilesystem:
        """
        A prefix tree implementation for embedded filesystem reconstruction.
 
        NOTE: as of PyInstaller 6.0, the embedded PYZ archive cannot contain data files anymore. Instead, it contains
        only .pyc modules - which are by design not returned by `PyiFrozenProvider`. So this implementation has been
        reduced to supporting only directories implied by collected packages.
        """
        def __init__(self, tree_node):
            self._tree = tree_node
 
        def _get_tree_node(self, path):
            path = pathlib.PurePath(path)
            current = self._tree
            for component in path.parts:
                if component not in current:
                    return None
                current = current[component]
            return current
 
        def path_exists(self, path):
            node = self._get_tree_node(path)
            return isinstance(node, dict)  # Directory only
 
        def path_isdir(self, path):
            node = self._get_tree_node(path)
            return isinstance(node, dict)  # Directory only
 
        def path_listdir(self, path):
            node = self._get_tree_node(path)
            if not isinstance(node, dict):
                return []  # Non-existent or file
            # Return only sub-directories
            return [entry_name for entry_name, entry_data in node.items() if isinstance(entry_data, dict)]
 
    class PyiFrozenProvider(pkg_resources.NullProvider):
        """
        Custom pkg_resources provider for PyiFrozenLoader.
        """
        def __init__(self, module):
            super().__init__(module)
 
            # Get top-level path; if "module" corresponds to a package, we need the path to the package itself.
            # If "module" is a submodule in a package, we need the path to the parent package.
            #
            # This is equivalent to `pkg_resources.NullProvider.module_path`, except we construct a `pathlib.PurePath`
            # for easier manipulation.
            #
            # NOTE: the path is NOT resolved for symbolic links, as neither are paths that are passed by `pkg_resources`
            # to `_has`, `_isdir`, `_listdir` (they are all anchored to `module_path`, which in turn is just
            # `os.path.dirname(module.__file__)`. As `__file__` returned by `PyiFrozenLoader` is always anchored to
            # `sys._MEIPASS`, we do not have to worry about cross-linked directories in macOS .app bundles, where the
            # resolved `__file__` could be either in the `Contents/Frameworks` directory (the "true" `sys._MEIPASS`), or
            # in the `Contents/Resources` directory due to cross-linking.
            self._pkg_path = pathlib.PurePath(module.__file__).parent
 
            # Construct _TocFilesystem on top of pre-computed prefix tree provided by pyimod02_importers.
            self.embedded_tree = _TocFilesystem(pyimod02_importers.get_pyz_toc_tree())
 
        def _normalize_path(self, path):
            # Avoid using `Path.resolve`, because it resolves symlinks. This is undesirable, because the pure path in
            # `self._pkg_path` does not have symlinks resolved, so comparison between the two would be faulty. Instead,
            # use `os.path.normpath` to normalize the path and get rid of any '..' elements (the path itself should
            # already be absolute).
            return pathlib.Path(os.path.normpath(path))
 
        def _is_relative_to_package(self, path):
            return path == self._pkg_path or self._pkg_path in path.parents
 
        def _has(self, path):
            # Prevent access outside the package.
            path = self._normalize_path(path)
            if not self._is_relative_to_package(path):
                return False
 
            # Check the filesystem first to avoid unnecessarily computing the relative path...
            if path.exists():
                return True
            rel_path = path.relative_to(SYS_PREFIX)
            return self.embedded_tree.path_exists(rel_path)
 
        def _isdir(self, path):
            # Prevent access outside the package.
            path = self._normalize_path(path)
            if not self._is_relative_to_package(path):
                return False
 
            # Embedded resources have precedence over filesystem...
            rel_path = path.relative_to(SYS_PREFIX)
            node = self.embedded_tree._get_tree_node(rel_path)
            if node is None:
                return path.is_dir()  # No match found; try the filesystem.
            else:
                # str = file, dict = directory
                return not isinstance(node, str)
 
        def _listdir(self, path):
            # Prevent access outside the package.
            path = self._normalize_path(path)
            if not self._is_relative_to_package(path):
                return []
 
            # Relative path for searching embedded resources.
            rel_path = path.relative_to(SYS_PREFIX)
            # List content from embedded filesystem...
            content = self.embedded_tree.path_listdir(rel_path)
            # ... as well as the actual one.
            if path.is_dir():
                # Use os.listdir() to avoid having to convert Path objects to strings... Also make sure to de-duplicate
                # the results.
                path = str(path)  # not is_py36
                content = list(set(content + os.listdir(path)))
            return content
 
    pkg_resources.register_loader_type(pyimod02_importers.PyiFrozenLoader, PyiFrozenProvider)
 
    # With our PyiFrozenFinder now being a path entry finder, it effectively replaces python's FileFinder. So we need
    # to register it with `pkg_resources.find_on_path` to allow metadata to be found on filesystem.
    pkg_resources.register_finder(pyimod02_importers.PyiFrozenFinder, pkg_resources.find_on_path)
 
    # For the above change to fully take effect, we need to re-initialize pkg_resources's master working set (since the
    # original one was built with assumption that sys.path entries are handled by python's FileFinder).
    # See https://github.com/pypa/setuptools/issues/373
    if hasattr(pkg_resources, '_initialize_master_working_set'):
        pkg_resources._initialize_master_working_set()
 
 
_pyi_rthook()
del _pyi_rthook