Skip to content

Commit 9662139

Browse files
authored
runfiles: Apply repo mapping to Rlocation path (#998)
1 parent 63379a4 commit 9662139

6 files changed

Lines changed: 375 additions & 7 deletions

File tree

examples/bzlmod/MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pip.parse(
2929
)
3030
use_repo(pip, "pip")
3131

32-
bazel_dep(name = "other_module", version = "")
32+
bazel_dep(name = "other_module", version = "", repo_name = "our_other_module")
3333
local_path_override(
3434
module_name = "other_module",
3535
path = "other_module",

examples/bzlmod/other_module/other_module/pkg/lib.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ def GetRunfilePathWithCurrentRepository():
77
# For a non-main repository, the name of the runfiles directory is equal to
88
# the canonical repository name.
99
return r.Rlocation(own_repo + "/other_module/pkg/data/data.txt")
10+
11+
12+
def GetRunfilePathWithRepoMapping():
13+
return runfiles.Create().Rlocation("other_module/other_module/pkg/data/data.txt")

examples/bzlmod/runfiles/BUILD.bazel

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ py_test(
55
srcs = ["runfiles_test.py"],
66
data = [
77
"data/data.txt",
8-
"@other_module//other_module/pkg:data/data.txt",
8+
"@our_other_module//other_module/pkg:data/data.txt",
99
],
1010
env = {
1111
"DATA_RLOCATIONPATH": "$(rlocationpath data/data.txt)",
12-
"OTHER_MODULE_DATA_RLOCATIONPATH": "$(rlocationpath @other_module//other_module/pkg:data/data.txt)",
12+
"OTHER_MODULE_DATA_RLOCATIONPATH": "$(rlocationpath @our_other_module//other_module/pkg:data/data.txt)",
1313
},
1414
deps = [
15-
"@other_module//other_module/pkg:lib",
15+
"@our_other_module//other_module/pkg:lib",
1616
"@rules_python//python/runfiles",
1717
],
1818
)

examples/bzlmod/runfiles/runfiles_test.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,29 @@ class RunfilesTest(unittest.TestCase):
1111
def testCurrentRepository(self):
1212
self.assertEqual(runfiles.Create().CurrentRepository(), "")
1313

14+
def testRunfilesWithRepoMapping(self):
15+
data_path = runfiles.Create().Rlocation("example_bzlmod/runfiles/data/data.txt")
16+
with open(data_path) as f:
17+
self.assertEqual(f.read().strip(), "Hello, example_bzlmod!")
18+
1419
def testRunfileWithRlocationpath(self):
1520
data_rlocationpath = os.getenv("DATA_RLOCATIONPATH")
1621
data_path = runfiles.Create().Rlocation(data_rlocationpath)
1722
with open(data_path) as f:
1823
self.assertEqual(f.read().strip(), "Hello, example_bzlmod!")
1924

25+
def testRunfileInOtherModuleWithOurRepoMapping(self):
26+
data_path = runfiles.Create().Rlocation(
27+
"our_other_module/other_module/pkg/data/data.txt"
28+
)
29+
with open(data_path) as f:
30+
self.assertEqual(f.read().strip(), "Hello, other_module!")
31+
32+
def testRunfileInOtherModuleWithItsRepoMapping(self):
33+
data_path = lib.GetRunfilePathWithRepoMapping()
34+
with open(data_path) as f:
35+
self.assertEqual(f.read().strip(), "Hello, other_module!")
36+
2037
def testRunfileInOtherModuleWithCurrentRepository(self):
2138
data_path = lib.GetRunfilePathWithCurrentRepository()
2239
with open(data_path) as f:

python/runfiles/runfiles.py

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,12 @@ def __init__(self, strategy):
126126
# type: (Union[_ManifestBased, _DirectoryBased]) -> None
127127
self._strategy = strategy
128128
self._python_runfiles_root = _FindPythonRunfilesRoot()
129+
self._repo_mapping = _ParseRepoMapping(
130+
strategy.RlocationChecked("_repo_mapping")
131+
)
129132

130-
def Rlocation(self, path):
131-
# type: (str) -> Optional[str]
133+
def Rlocation(self, path, source_repo=None):
134+
# type: (str, Optional[str]) -> Optional[str]
132135
"""Returns the runtime path of a runfile.
133136
134137
Runfiles are data-dependencies of Bazel-built binaries and tests.
@@ -141,6 +144,13 @@ def Rlocation(self, path):
141144
142145
Args:
143146
path: string; runfiles-root-relative path of the runfile
147+
source_repo: string; optional; the canonical name of the repository
148+
whose repository mapping should be used to resolve apparent to
149+
canonical repository names in `path`. If `None` (default), the
150+
repository mapping of the repository containing the caller of this
151+
method is used. Explicitly setting this parameter should only be
152+
necessary for libraries that want to wrap the runfiles library. Use
153+
`CurrentRepository` to obtain canonical repository names.
144154
Returns:
145155
the path to the runfile, which the caller should check for existence, or
146156
None if the method doesn't know about this runfile
@@ -165,7 +175,31 @@ def Rlocation(self, path):
165175
raise ValueError('path is absolute without a drive letter: "%s"' % path)
166176
if os.path.isabs(path):
167177
return path
168-
return self._strategy.RlocationChecked(path)
178+
179+
if source_repo is None and self._repo_mapping:
180+
# Look up runfiles using the repository mapping of the caller of the
181+
# current method. If the repo mapping is empty, determining this
182+
# name is not necessary.
183+
source_repo = self.CurrentRepository(frame=2)
184+
185+
# Split off the first path component, which contains the repository
186+
# name (apparent or canonical).
187+
target_repo, _, remainder = path.partition("/")
188+
if not remainder or (source_repo, target_repo) not in self._repo_mapping:
189+
# One of the following is the case:
190+
# - not using Bzlmod, so the repository mapping is empty and
191+
# apparent and canonical repository names are the same
192+
# - target_repo is already a canonical repository name and does not
193+
# have to be mapped.
194+
# - path did not contain a slash and referred to a root symlink,
195+
# which also should not be mapped.
196+
return self._strategy.RlocationChecked(path)
197+
198+
# target_repo is an apparent repository name. Look up the corresponding
199+
# canonical repository name with respect to the current repository,
200+
# identified by its canonical name.
201+
target_canonical = self._repo_mapping[(source_repo, target_repo)]
202+
return self._strategy.RlocationChecked(target_canonical + "/" + remainder)
169203

170204
def EnvVars(self):
171205
# type: () -> Dict[str, str]
@@ -254,6 +288,31 @@ def _FindPythonRunfilesRoot():
254288
return root
255289

256290

291+
def _ParseRepoMapping(repo_mapping_path):
292+
# type: (Optional[str]) -> Dict[Tuple[str, str], str]
293+
"""Parses the repository mapping manifest."""
294+
# If the repository mapping file can't be found, that is not an error: We
295+
# might be running without Bzlmod enabled or there may not be any runfiles.
296+
# In this case, just apply an empty repo mapping.
297+
if not repo_mapping_path:
298+
return {}
299+
try:
300+
with open(repo_mapping_path, "r") as f:
301+
content = f.read()
302+
except FileNotFoundError:
303+
return {}
304+
305+
repo_mapping = {}
306+
for line in content.split("\n"):
307+
if not line:
308+
# Empty line following the last line break
309+
break
310+
current_canonical, target_local, target_canonical = line.split(",")
311+
repo_mapping[(current_canonical, target_local)] = target_canonical
312+
313+
return repo_mapping
314+
315+
257316
class _ManifestBased(object):
258317
"""`Runfiles` strategy that parses a runfiles-manifest to look up runfiles."""
259318

0 commit comments

Comments
 (0)