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