xref: /aosp_15_r20/external/bazelbuild-rules_python/python/runfiles/runfiles.py (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1*60517a1eSAndroid Build Coastguard Worker# Copyright 2018 The Bazel Authors. All rights reserved.
2*60517a1eSAndroid Build Coastguard Worker#
3*60517a1eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*60517a1eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*60517a1eSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*60517a1eSAndroid Build Coastguard Worker#
7*60517a1eSAndroid Build Coastguard Worker#    http://www.apache.org/licenses/LICENSE-2.0
8*60517a1eSAndroid Build Coastguard Worker#
9*60517a1eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*60517a1eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*60517a1eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*60517a1eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*60517a1eSAndroid Build Coastguard Worker# limitations under the License.
14*60517a1eSAndroid Build Coastguard Worker
15*60517a1eSAndroid Build Coastguard Worker"""Runfiles lookup library for Bazel-built Python binaries and tests.
16*60517a1eSAndroid Build Coastguard Worker
17*60517a1eSAndroid Build Coastguard WorkerSee @rules_python//python/runfiles/README.md for usage instructions.
18*60517a1eSAndroid Build Coastguard Worker"""
19*60517a1eSAndroid Build Coastguard Workerimport inspect
20*60517a1eSAndroid Build Coastguard Workerimport os
21*60517a1eSAndroid Build Coastguard Workerimport posixpath
22*60517a1eSAndroid Build Coastguard Workerimport sys
23*60517a1eSAndroid Build Coastguard Workerfrom typing import Dict, Optional, Tuple, Union
24*60517a1eSAndroid Build Coastguard Worker
25*60517a1eSAndroid Build Coastguard Worker
26*60517a1eSAndroid Build Coastguard Workerclass _ManifestBased:
27*60517a1eSAndroid Build Coastguard Worker    """`Runfiles` strategy that parses a runfiles-manifest to look up runfiles."""
28*60517a1eSAndroid Build Coastguard Worker
29*60517a1eSAndroid Build Coastguard Worker    def __init__(self, path: str) -> None:
30*60517a1eSAndroid Build Coastguard Worker        if not path:
31*60517a1eSAndroid Build Coastguard Worker            raise ValueError()
32*60517a1eSAndroid Build Coastguard Worker        if not isinstance(path, str):
33*60517a1eSAndroid Build Coastguard Worker            raise TypeError()
34*60517a1eSAndroid Build Coastguard Worker        self._path = path
35*60517a1eSAndroid Build Coastguard Worker        self._runfiles = _ManifestBased._LoadRunfiles(path)
36*60517a1eSAndroid Build Coastguard Worker
37*60517a1eSAndroid Build Coastguard Worker    def RlocationChecked(self, path: str) -> Optional[str]:
38*60517a1eSAndroid Build Coastguard Worker        """Returns the runtime path of a runfile."""
39*60517a1eSAndroid Build Coastguard Worker        exact_match = self._runfiles.get(path)
40*60517a1eSAndroid Build Coastguard Worker        if exact_match:
41*60517a1eSAndroid Build Coastguard Worker            return exact_match
42*60517a1eSAndroid Build Coastguard Worker        # If path references a runfile that lies under a directory that
43*60517a1eSAndroid Build Coastguard Worker        # itself is a runfile, then only the directory is listed in the
44*60517a1eSAndroid Build Coastguard Worker        # manifest. Look up all prefixes of path in the manifest and append
45*60517a1eSAndroid Build Coastguard Worker        # the relative path from the prefix to the looked up path.
46*60517a1eSAndroid Build Coastguard Worker        prefix_end = len(path)
47*60517a1eSAndroid Build Coastguard Worker        while True:
48*60517a1eSAndroid Build Coastguard Worker            prefix_end = path.rfind("/", 0, prefix_end - 1)
49*60517a1eSAndroid Build Coastguard Worker            if prefix_end == -1:
50*60517a1eSAndroid Build Coastguard Worker                return None
51*60517a1eSAndroid Build Coastguard Worker            prefix_match = self._runfiles.get(path[0:prefix_end])
52*60517a1eSAndroid Build Coastguard Worker            if prefix_match:
53*60517a1eSAndroid Build Coastguard Worker                return prefix_match + "/" + path[prefix_end + 1 :]
54*60517a1eSAndroid Build Coastguard Worker
55*60517a1eSAndroid Build Coastguard Worker    @staticmethod
56*60517a1eSAndroid Build Coastguard Worker    def _LoadRunfiles(path: str) -> Dict[str, str]:
57*60517a1eSAndroid Build Coastguard Worker        """Loads the runfiles manifest."""
58*60517a1eSAndroid Build Coastguard Worker        result = {}
59*60517a1eSAndroid Build Coastguard Worker        with open(path, "r") as f:
60*60517a1eSAndroid Build Coastguard Worker            for line in f:
61*60517a1eSAndroid Build Coastguard Worker                line = line.strip()
62*60517a1eSAndroid Build Coastguard Worker                if line:
63*60517a1eSAndroid Build Coastguard Worker                    tokens = line.split(" ", 1)
64*60517a1eSAndroid Build Coastguard Worker                    if len(tokens) == 1:
65*60517a1eSAndroid Build Coastguard Worker                        result[line] = line
66*60517a1eSAndroid Build Coastguard Worker                    else:
67*60517a1eSAndroid Build Coastguard Worker                        result[tokens[0]] = tokens[1]
68*60517a1eSAndroid Build Coastguard Worker        return result
69*60517a1eSAndroid Build Coastguard Worker
70*60517a1eSAndroid Build Coastguard Worker    def _GetRunfilesDir(self) -> str:
71*60517a1eSAndroid Build Coastguard Worker        if self._path.endswith("/MANIFEST") or self._path.endswith("\\MANIFEST"):
72*60517a1eSAndroid Build Coastguard Worker            return self._path[: -len("/MANIFEST")]
73*60517a1eSAndroid Build Coastguard Worker        if self._path.endswith(".runfiles_manifest"):
74*60517a1eSAndroid Build Coastguard Worker            return self._path[: -len("_manifest")]
75*60517a1eSAndroid Build Coastguard Worker        return ""
76*60517a1eSAndroid Build Coastguard Worker
77*60517a1eSAndroid Build Coastguard Worker    def EnvVars(self) -> Dict[str, str]:
78*60517a1eSAndroid Build Coastguard Worker        directory = self._GetRunfilesDir()
79*60517a1eSAndroid Build Coastguard Worker        return {
80*60517a1eSAndroid Build Coastguard Worker            "RUNFILES_MANIFEST_FILE": self._path,
81*60517a1eSAndroid Build Coastguard Worker            "RUNFILES_DIR": directory,
82*60517a1eSAndroid Build Coastguard Worker            # TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can
83*60517a1eSAndroid Build Coastguard Worker            # pick up RUNFILES_DIR.
84*60517a1eSAndroid Build Coastguard Worker            "JAVA_RUNFILES": directory,
85*60517a1eSAndroid Build Coastguard Worker        }
86*60517a1eSAndroid Build Coastguard Worker
87*60517a1eSAndroid Build Coastguard Worker
88*60517a1eSAndroid Build Coastguard Workerclass _DirectoryBased:
89*60517a1eSAndroid Build Coastguard Worker    """`Runfiles` strategy that appends runfiles paths to the runfiles root."""
90*60517a1eSAndroid Build Coastguard Worker
91*60517a1eSAndroid Build Coastguard Worker    def __init__(self, path: str) -> None:
92*60517a1eSAndroid Build Coastguard Worker        if not path:
93*60517a1eSAndroid Build Coastguard Worker            raise ValueError()
94*60517a1eSAndroid Build Coastguard Worker        if not isinstance(path, str):
95*60517a1eSAndroid Build Coastguard Worker            raise TypeError()
96*60517a1eSAndroid Build Coastguard Worker        self._runfiles_root = path
97*60517a1eSAndroid Build Coastguard Worker
98*60517a1eSAndroid Build Coastguard Worker    def RlocationChecked(self, path: str) -> str:
99*60517a1eSAndroid Build Coastguard Worker        # Use posixpath instead of os.path, because Bazel only creates a runfiles
100*60517a1eSAndroid Build Coastguard Worker        # tree on Unix platforms, so `Create()` will only create a directory-based
101*60517a1eSAndroid Build Coastguard Worker        # runfiles strategy on those platforms.
102*60517a1eSAndroid Build Coastguard Worker        return posixpath.join(self._runfiles_root, path)
103*60517a1eSAndroid Build Coastguard Worker
104*60517a1eSAndroid Build Coastguard Worker    def EnvVars(self) -> Dict[str, str]:
105*60517a1eSAndroid Build Coastguard Worker        return {
106*60517a1eSAndroid Build Coastguard Worker            "RUNFILES_DIR": self._runfiles_root,
107*60517a1eSAndroid Build Coastguard Worker            # TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can
108*60517a1eSAndroid Build Coastguard Worker            # pick up RUNFILES_DIR.
109*60517a1eSAndroid Build Coastguard Worker            "JAVA_RUNFILES": self._runfiles_root,
110*60517a1eSAndroid Build Coastguard Worker        }
111*60517a1eSAndroid Build Coastguard Worker
112*60517a1eSAndroid Build Coastguard Worker
113*60517a1eSAndroid Build Coastguard Workerclass Runfiles:
114*60517a1eSAndroid Build Coastguard Worker    """Returns the runtime location of runfiles.
115*60517a1eSAndroid Build Coastguard Worker
116*60517a1eSAndroid Build Coastguard Worker    Runfiles are data-dependencies of Bazel-built binaries and tests.
117*60517a1eSAndroid Build Coastguard Worker    """
118*60517a1eSAndroid Build Coastguard Worker
119*60517a1eSAndroid Build Coastguard Worker    def __init__(self, strategy: Union[_ManifestBased, _DirectoryBased]) -> None:
120*60517a1eSAndroid Build Coastguard Worker        self._strategy = strategy
121*60517a1eSAndroid Build Coastguard Worker        self._python_runfiles_root = _FindPythonRunfilesRoot()
122*60517a1eSAndroid Build Coastguard Worker        self._repo_mapping = _ParseRepoMapping(
123*60517a1eSAndroid Build Coastguard Worker            strategy.RlocationChecked("_repo_mapping")
124*60517a1eSAndroid Build Coastguard Worker        )
125*60517a1eSAndroid Build Coastguard Worker
126*60517a1eSAndroid Build Coastguard Worker    def Rlocation(self, path: str, source_repo: Optional[str] = None) -> Optional[str]:
127*60517a1eSAndroid Build Coastguard Worker        """Returns the runtime path of a runfile.
128*60517a1eSAndroid Build Coastguard Worker
129*60517a1eSAndroid Build Coastguard Worker        Runfiles are data-dependencies of Bazel-built binaries and tests.
130*60517a1eSAndroid Build Coastguard Worker
131*60517a1eSAndroid Build Coastguard Worker        The returned path may not be valid. The caller should check the path's
132*60517a1eSAndroid Build Coastguard Worker        validity and that the path exists.
133*60517a1eSAndroid Build Coastguard Worker
134*60517a1eSAndroid Build Coastguard Worker        The function may return None. In that case the caller can be sure that the
135*60517a1eSAndroid Build Coastguard Worker        rule does not know about this data-dependency.
136*60517a1eSAndroid Build Coastguard Worker
137*60517a1eSAndroid Build Coastguard Worker        Args:
138*60517a1eSAndroid Build Coastguard Worker          path: string; runfiles-root-relative path of the runfile
139*60517a1eSAndroid Build Coastguard Worker          source_repo: string; optional; the canonical name of the repository
140*60517a1eSAndroid Build Coastguard Worker            whose repository mapping should be used to resolve apparent to
141*60517a1eSAndroid Build Coastguard Worker            canonical repository names in `path`. If `None` (default), the
142*60517a1eSAndroid Build Coastguard Worker            repository mapping of the repository containing the caller of this
143*60517a1eSAndroid Build Coastguard Worker            method is used. Explicitly setting this parameter should only be
144*60517a1eSAndroid Build Coastguard Worker            necessary for libraries that want to wrap the runfiles library. Use
145*60517a1eSAndroid Build Coastguard Worker            `CurrentRepository` to obtain canonical repository names.
146*60517a1eSAndroid Build Coastguard Worker        Returns:
147*60517a1eSAndroid Build Coastguard Worker          the path to the runfile, which the caller should check for existence, or
148*60517a1eSAndroid Build Coastguard Worker          None if the method doesn't know about this runfile
149*60517a1eSAndroid Build Coastguard Worker        Raises:
150*60517a1eSAndroid Build Coastguard Worker          TypeError: if `path` is not a string
151*60517a1eSAndroid Build Coastguard Worker          ValueError: if `path` is None or empty, or it's absolute or not normalized
152*60517a1eSAndroid Build Coastguard Worker        """
153*60517a1eSAndroid Build Coastguard Worker        if not path:
154*60517a1eSAndroid Build Coastguard Worker            raise ValueError()
155*60517a1eSAndroid Build Coastguard Worker        if not isinstance(path, str):
156*60517a1eSAndroid Build Coastguard Worker            raise TypeError()
157*60517a1eSAndroid Build Coastguard Worker        if (
158*60517a1eSAndroid Build Coastguard Worker            path.startswith("../")
159*60517a1eSAndroid Build Coastguard Worker            or "/.." in path
160*60517a1eSAndroid Build Coastguard Worker            or path.startswith("./")
161*60517a1eSAndroid Build Coastguard Worker            or "/./" in path
162*60517a1eSAndroid Build Coastguard Worker            or path.endswith("/.")
163*60517a1eSAndroid Build Coastguard Worker            or "//" in path
164*60517a1eSAndroid Build Coastguard Worker        ):
165*60517a1eSAndroid Build Coastguard Worker            raise ValueError('path is not normalized: "%s"' % path)
166*60517a1eSAndroid Build Coastguard Worker        if path[0] == "\\":
167*60517a1eSAndroid Build Coastguard Worker            raise ValueError('path is absolute without a drive letter: "%s"' % path)
168*60517a1eSAndroid Build Coastguard Worker        if os.path.isabs(path):
169*60517a1eSAndroid Build Coastguard Worker            return path
170*60517a1eSAndroid Build Coastguard Worker
171*60517a1eSAndroid Build Coastguard Worker        if source_repo is None and self._repo_mapping:
172*60517a1eSAndroid Build Coastguard Worker            # Look up runfiles using the repository mapping of the caller of the
173*60517a1eSAndroid Build Coastguard Worker            # current method. If the repo mapping is empty, determining this
174*60517a1eSAndroid Build Coastguard Worker            # name is not necessary.
175*60517a1eSAndroid Build Coastguard Worker            source_repo = self.CurrentRepository(frame=2)
176*60517a1eSAndroid Build Coastguard Worker
177*60517a1eSAndroid Build Coastguard Worker        # Split off the first path component, which contains the repository
178*60517a1eSAndroid Build Coastguard Worker        # name (apparent or canonical).
179*60517a1eSAndroid Build Coastguard Worker        target_repo, _, remainder = path.partition("/")
180*60517a1eSAndroid Build Coastguard Worker        if not remainder or (source_repo, target_repo) not in self._repo_mapping:
181*60517a1eSAndroid Build Coastguard Worker            # One of the following is the case:
182*60517a1eSAndroid Build Coastguard Worker            # - not using Bzlmod, so the repository mapping is empty and
183*60517a1eSAndroid Build Coastguard Worker            #   apparent and canonical repository names are the same
184*60517a1eSAndroid Build Coastguard Worker            # - target_repo is already a canonical repository name and does not
185*60517a1eSAndroid Build Coastguard Worker            #   have to be mapped.
186*60517a1eSAndroid Build Coastguard Worker            # - path did not contain a slash and referred to a root symlink,
187*60517a1eSAndroid Build Coastguard Worker            #   which also should not be mapped.
188*60517a1eSAndroid Build Coastguard Worker            return self._strategy.RlocationChecked(path)
189*60517a1eSAndroid Build Coastguard Worker
190*60517a1eSAndroid Build Coastguard Worker        assert (
191*60517a1eSAndroid Build Coastguard Worker            source_repo is not None
192*60517a1eSAndroid Build Coastguard Worker        ), "BUG: if the `source_repo` is None, we should never go past the `if` statement above"
193*60517a1eSAndroid Build Coastguard Worker
194*60517a1eSAndroid Build Coastguard Worker        # target_repo is an apparent repository name. Look up the corresponding
195*60517a1eSAndroid Build Coastguard Worker        # canonical repository name with respect to the current repository,
196*60517a1eSAndroid Build Coastguard Worker        # identified by its canonical name.
197*60517a1eSAndroid Build Coastguard Worker        target_canonical = self._repo_mapping[(source_repo, target_repo)]
198*60517a1eSAndroid Build Coastguard Worker        return self._strategy.RlocationChecked(target_canonical + "/" + remainder)
199*60517a1eSAndroid Build Coastguard Worker
200*60517a1eSAndroid Build Coastguard Worker    def EnvVars(self) -> Dict[str, str]:
201*60517a1eSAndroid Build Coastguard Worker        """Returns environment variables for subprocesses.
202*60517a1eSAndroid Build Coastguard Worker
203*60517a1eSAndroid Build Coastguard Worker        The caller should set the returned key-value pairs in the environment of
204*60517a1eSAndroid Build Coastguard Worker        subprocesses in case those subprocesses are also Bazel-built binaries that
205*60517a1eSAndroid Build Coastguard Worker        need to use runfiles.
206*60517a1eSAndroid Build Coastguard Worker
207*60517a1eSAndroid Build Coastguard Worker        Returns:
208*60517a1eSAndroid Build Coastguard Worker          {string: string}; a dict; keys are environment variable names, values are
209*60517a1eSAndroid Build Coastguard Worker          the values for these environment variables
210*60517a1eSAndroid Build Coastguard Worker        """
211*60517a1eSAndroid Build Coastguard Worker        return self._strategy.EnvVars()
212*60517a1eSAndroid Build Coastguard Worker
213*60517a1eSAndroid Build Coastguard Worker    def CurrentRepository(self, frame: int = 1) -> str:
214*60517a1eSAndroid Build Coastguard Worker        """Returns the canonical name of the caller's Bazel repository.
215*60517a1eSAndroid Build Coastguard Worker
216*60517a1eSAndroid Build Coastguard Worker        For example, this function returns '' (the empty string) when called
217*60517a1eSAndroid Build Coastguard Worker        from the main repository and a string of the form
218*60517a1eSAndroid Build Coastguard Worker        'rules_python~0.13.0` when called from code in the repository
219*60517a1eSAndroid Build Coastguard Worker        corresponding to the rules_python Bazel module.
220*60517a1eSAndroid Build Coastguard Worker
221*60517a1eSAndroid Build Coastguard Worker        More information about the difference between canonical repository
222*60517a1eSAndroid Build Coastguard Worker        names and the `@repo` part of labels is available at:
223*60517a1eSAndroid Build Coastguard Worker        https://bazel.build/build/bzlmod#repository-names
224*60517a1eSAndroid Build Coastguard Worker
225*60517a1eSAndroid Build Coastguard Worker        NOTE: This function inspects the callstack to determine where in the
226*60517a1eSAndroid Build Coastguard Worker        runfiles the caller is located to determine which repository it came
227*60517a1eSAndroid Build Coastguard Worker        from. This may fail or produce incorrect results depending on who the
228*60517a1eSAndroid Build Coastguard Worker        caller is, for example if it is not represented by a Python source
229*60517a1eSAndroid Build Coastguard Worker        file. Use the `frame` argument to control the stack lookup.
230*60517a1eSAndroid Build Coastguard Worker
231*60517a1eSAndroid Build Coastguard Worker        Args:
232*60517a1eSAndroid Build Coastguard Worker            frame: int; the stack frame to return the repository name for.
233*60517a1eSAndroid Build Coastguard Worker            Defaults to 1, the caller of the CurrentRepository function.
234*60517a1eSAndroid Build Coastguard Worker
235*60517a1eSAndroid Build Coastguard Worker        Returns:
236*60517a1eSAndroid Build Coastguard Worker            The canonical name of the Bazel repository containing the file
237*60517a1eSAndroid Build Coastguard Worker            containing the frame-th caller of this function
238*60517a1eSAndroid Build Coastguard Worker
239*60517a1eSAndroid Build Coastguard Worker        Raises:
240*60517a1eSAndroid Build Coastguard Worker            ValueError: if the caller cannot be determined or the caller's file
241*60517a1eSAndroid Build Coastguard Worker            path is not contained in the Python runfiles tree
242*60517a1eSAndroid Build Coastguard Worker        """
243*60517a1eSAndroid Build Coastguard Worker        try:
244*60517a1eSAndroid Build Coastguard Worker            # pylint: disable-next=protected-access
245*60517a1eSAndroid Build Coastguard Worker            caller_path = inspect.getfile(sys._getframe(frame))
246*60517a1eSAndroid Build Coastguard Worker        except (TypeError, ValueError) as exc:
247*60517a1eSAndroid Build Coastguard Worker            raise ValueError("failed to determine caller's file path") from exc
248*60517a1eSAndroid Build Coastguard Worker        caller_runfiles_path = os.path.relpath(caller_path, self._python_runfiles_root)
249*60517a1eSAndroid Build Coastguard Worker        if caller_runfiles_path.startswith(".." + os.path.sep):
250*60517a1eSAndroid Build Coastguard Worker            # With Python 3.10 and earlier, sys.path contains the directory
251*60517a1eSAndroid Build Coastguard Worker            # of the script, which can result in a module being loaded from
252*60517a1eSAndroid Build Coastguard Worker            # outside the runfiles tree. In this case, assume that the module is
253*60517a1eSAndroid Build Coastguard Worker            # located in the main repository.
254*60517a1eSAndroid Build Coastguard Worker            # With Python 3.11 and higher, the Python launcher sets
255*60517a1eSAndroid Build Coastguard Worker            # PYTHONSAFEPATH, which prevents this behavior.
256*60517a1eSAndroid Build Coastguard Worker            # TODO: This doesn't cover the case of a script being run from an
257*60517a1eSAndroid Build Coastguard Worker            #       external repository, which could be heuristically detected
258*60517a1eSAndroid Build Coastguard Worker            #       by parsing the script's path.
259*60517a1eSAndroid Build Coastguard Worker            if (
260*60517a1eSAndroid Build Coastguard Worker                sys.version_info.minor <= 10
261*60517a1eSAndroid Build Coastguard Worker                and sys.path[0] != self._python_runfiles_root
262*60517a1eSAndroid Build Coastguard Worker            ):
263*60517a1eSAndroid Build Coastguard Worker                return ""
264*60517a1eSAndroid Build Coastguard Worker            raise ValueError(
265*60517a1eSAndroid Build Coastguard Worker                "{} does not lie under the runfiles root {}".format(
266*60517a1eSAndroid Build Coastguard Worker                    caller_path, self._python_runfiles_root
267*60517a1eSAndroid Build Coastguard Worker                )
268*60517a1eSAndroid Build Coastguard Worker            )
269*60517a1eSAndroid Build Coastguard Worker
270*60517a1eSAndroid Build Coastguard Worker        caller_runfiles_directory = caller_runfiles_path[
271*60517a1eSAndroid Build Coastguard Worker            : caller_runfiles_path.find(os.path.sep)
272*60517a1eSAndroid Build Coastguard Worker        ]
273*60517a1eSAndroid Build Coastguard Worker        # With Bzlmod, the runfiles directory of the main repository is always
274*60517a1eSAndroid Build Coastguard Worker        # named "_main". Without Bzlmod, the value returned by this function is
275*60517a1eSAndroid Build Coastguard Worker        # never used, so we just assume Bzlmod is enabled.
276*60517a1eSAndroid Build Coastguard Worker        if caller_runfiles_directory == "_main":
277*60517a1eSAndroid Build Coastguard Worker            # The canonical name of the main repository (also known as the
278*60517a1eSAndroid Build Coastguard Worker            # workspace) is the empty string.
279*60517a1eSAndroid Build Coastguard Worker            return ""
280*60517a1eSAndroid Build Coastguard Worker        # For all other repositories, the name of the runfiles directory is the
281*60517a1eSAndroid Build Coastguard Worker        # canonical name.
282*60517a1eSAndroid Build Coastguard Worker        return caller_runfiles_directory
283*60517a1eSAndroid Build Coastguard Worker
284*60517a1eSAndroid Build Coastguard Worker    # TODO: Update return type to Self when 3.11 is the min version
285*60517a1eSAndroid Build Coastguard Worker    # https://peps.python.org/pep-0673/
286*60517a1eSAndroid Build Coastguard Worker    @staticmethod
287*60517a1eSAndroid Build Coastguard Worker    def CreateManifestBased(manifest_path: str) -> "Runfiles":
288*60517a1eSAndroid Build Coastguard Worker        return Runfiles(_ManifestBased(manifest_path))
289*60517a1eSAndroid Build Coastguard Worker
290*60517a1eSAndroid Build Coastguard Worker    # TODO: Update return type to Self when 3.11 is the min version
291*60517a1eSAndroid Build Coastguard Worker    # https://peps.python.org/pep-0673/
292*60517a1eSAndroid Build Coastguard Worker    @staticmethod
293*60517a1eSAndroid Build Coastguard Worker    def CreateDirectoryBased(runfiles_dir_path: str) -> "Runfiles":
294*60517a1eSAndroid Build Coastguard Worker        return Runfiles(_DirectoryBased(runfiles_dir_path))
295*60517a1eSAndroid Build Coastguard Worker
296*60517a1eSAndroid Build Coastguard Worker    # TODO: Update return type to Self when 3.11 is the min version
297*60517a1eSAndroid Build Coastguard Worker    # https://peps.python.org/pep-0673/
298*60517a1eSAndroid Build Coastguard Worker    @staticmethod
299*60517a1eSAndroid Build Coastguard Worker    def Create(env: Optional[Dict[str, str]] = None) -> Optional["Runfiles"]:
300*60517a1eSAndroid Build Coastguard Worker        """Returns a new `Runfiles` instance.
301*60517a1eSAndroid Build Coastguard Worker
302*60517a1eSAndroid Build Coastguard Worker        The returned object is either:
303*60517a1eSAndroid Build Coastguard Worker        - manifest-based, meaning it looks up runfile paths from a manifest file, or
304*60517a1eSAndroid Build Coastguard Worker        - directory-based, meaning it looks up runfile paths under a given directory
305*60517a1eSAndroid Build Coastguard Worker        path
306*60517a1eSAndroid Build Coastguard Worker
307*60517a1eSAndroid Build Coastguard Worker        If `env` contains "RUNFILES_MANIFEST_FILE" with non-empty value, this method
308*60517a1eSAndroid Build Coastguard Worker        returns a manifest-based implementation. The object eagerly reads and caches
309*60517a1eSAndroid Build Coastguard Worker        the whole manifest file upon instantiation; this may be relevant for
310*60517a1eSAndroid Build Coastguard Worker        performance consideration.
311*60517a1eSAndroid Build Coastguard Worker
312*60517a1eSAndroid Build Coastguard Worker        Otherwise, if `env` contains "RUNFILES_DIR" with non-empty value (checked in
313*60517a1eSAndroid Build Coastguard Worker        this priority order), this method returns a directory-based implementation.
314*60517a1eSAndroid Build Coastguard Worker
315*60517a1eSAndroid Build Coastguard Worker        If neither cases apply, this method returns null.
316*60517a1eSAndroid Build Coastguard Worker
317*60517a1eSAndroid Build Coastguard Worker        Args:
318*60517a1eSAndroid Build Coastguard Worker        env: {string: string}; optional; the map of environment variables. If None,
319*60517a1eSAndroid Build Coastguard Worker            this function uses the environment variable map of this process.
320*60517a1eSAndroid Build Coastguard Worker        Raises:
321*60517a1eSAndroid Build Coastguard Worker        IOError: if some IO error occurs.
322*60517a1eSAndroid Build Coastguard Worker        """
323*60517a1eSAndroid Build Coastguard Worker        env_map = os.environ if env is None else env
324*60517a1eSAndroid Build Coastguard Worker        manifest = env_map.get("RUNFILES_MANIFEST_FILE")
325*60517a1eSAndroid Build Coastguard Worker        if manifest:
326*60517a1eSAndroid Build Coastguard Worker            return CreateManifestBased(manifest)
327*60517a1eSAndroid Build Coastguard Worker
328*60517a1eSAndroid Build Coastguard Worker        directory = env_map.get("RUNFILES_DIR")
329*60517a1eSAndroid Build Coastguard Worker        if directory:
330*60517a1eSAndroid Build Coastguard Worker            return CreateDirectoryBased(directory)
331*60517a1eSAndroid Build Coastguard Worker
332*60517a1eSAndroid Build Coastguard Worker        return None
333*60517a1eSAndroid Build Coastguard Worker
334*60517a1eSAndroid Build Coastguard Worker
335*60517a1eSAndroid Build Coastguard Worker# Support legacy imports by defining a private symbol.
336*60517a1eSAndroid Build Coastguard Worker_Runfiles = Runfiles
337*60517a1eSAndroid Build Coastguard Worker
338*60517a1eSAndroid Build Coastguard Worker
339*60517a1eSAndroid Build Coastguard Workerdef _FindPythonRunfilesRoot() -> str:
340*60517a1eSAndroid Build Coastguard Worker    """Finds the root of the Python runfiles tree."""
341*60517a1eSAndroid Build Coastguard Worker    root = __file__
342*60517a1eSAndroid Build Coastguard Worker    # Walk up our own runfiles path to the root of the runfiles tree from which
343*60517a1eSAndroid Build Coastguard Worker    # the current file is being run. This path coincides with what the Bazel
344*60517a1eSAndroid Build Coastguard Worker    # Python stub sets up as sys.path[0]. Since that entry can be changed at
345*60517a1eSAndroid Build Coastguard Worker    # runtime, we rederive it here.
346*60517a1eSAndroid Build Coastguard Worker    for _ in range("rules_python/python/runfiles/runfiles.py".count("/") + 1):
347*60517a1eSAndroid Build Coastguard Worker        root = os.path.dirname(root)
348*60517a1eSAndroid Build Coastguard Worker    return root
349*60517a1eSAndroid Build Coastguard Worker
350*60517a1eSAndroid Build Coastguard Worker
351*60517a1eSAndroid Build Coastguard Workerdef _ParseRepoMapping(repo_mapping_path: Optional[str]) -> Dict[Tuple[str, str], str]:
352*60517a1eSAndroid Build Coastguard Worker    """Parses the repository mapping manifest."""
353*60517a1eSAndroid Build Coastguard Worker    # If the repository mapping file can't be found, that is not an error: We
354*60517a1eSAndroid Build Coastguard Worker    # might be running without Bzlmod enabled or there may not be any runfiles.
355*60517a1eSAndroid Build Coastguard Worker    # In this case, just apply an empty repo mapping.
356*60517a1eSAndroid Build Coastguard Worker    if not repo_mapping_path:
357*60517a1eSAndroid Build Coastguard Worker        return {}
358*60517a1eSAndroid Build Coastguard Worker    try:
359*60517a1eSAndroid Build Coastguard Worker        with open(repo_mapping_path, "r") as f:
360*60517a1eSAndroid Build Coastguard Worker            content = f.read()
361*60517a1eSAndroid Build Coastguard Worker    except FileNotFoundError:
362*60517a1eSAndroid Build Coastguard Worker        return {}
363*60517a1eSAndroid Build Coastguard Worker
364*60517a1eSAndroid Build Coastguard Worker    repo_mapping = {}
365*60517a1eSAndroid Build Coastguard Worker    for line in content.split("\n"):
366*60517a1eSAndroid Build Coastguard Worker        if not line:
367*60517a1eSAndroid Build Coastguard Worker            # Empty line following the last line break
368*60517a1eSAndroid Build Coastguard Worker            break
369*60517a1eSAndroid Build Coastguard Worker        current_canonical, target_local, target_canonical = line.split(",")
370*60517a1eSAndroid Build Coastguard Worker        repo_mapping[(current_canonical, target_local)] = target_canonical
371*60517a1eSAndroid Build Coastguard Worker
372*60517a1eSAndroid Build Coastguard Worker    return repo_mapping
373*60517a1eSAndroid Build Coastguard Worker
374*60517a1eSAndroid Build Coastguard Worker
375*60517a1eSAndroid Build Coastguard Workerdef CreateManifestBased(manifest_path: str) -> Runfiles:
376*60517a1eSAndroid Build Coastguard Worker    return Runfiles.CreateManifestBased(manifest_path)
377*60517a1eSAndroid Build Coastguard Worker
378*60517a1eSAndroid Build Coastguard Worker
379*60517a1eSAndroid Build Coastguard Workerdef CreateDirectoryBased(runfiles_dir_path: str) -> Runfiles:
380*60517a1eSAndroid Build Coastguard Worker    return Runfiles.CreateDirectoryBased(runfiles_dir_path)
381*60517a1eSAndroid Build Coastguard Worker
382*60517a1eSAndroid Build Coastguard Worker
383*60517a1eSAndroid Build Coastguard Workerdef Create(env: Optional[Dict[str, str]] = None) -> Optional[Runfiles]:
384*60517a1eSAndroid Build Coastguard Worker    return Runfiles.Create(env)
385