From 04be94018cabf5ed48095d3500e33069af4cbcea Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 11 Jun 2025 13:06:46 +0200 Subject: [PATCH 1/2] Add impl --- .../devtools/build/runfiles/Runfiles.java | 113 +++++++++++------- 1 file changed, 72 insertions(+), 41 deletions(-) diff --git a/java/runfiles/src/main/java/com/google/devtools/build/runfiles/Runfiles.java b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/Runfiles.java index bec2091..681f247 100644 --- a/java/runfiles/src/main/java/com/google/devtools/build/runfiles/Runfiles.java +++ b/java/runfiles/src/main/java/com/google/devtools/build/runfiles/Runfiles.java @@ -24,8 +24,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.NavigableMap; import java.util.Objects; -import java.util.stream.Collectors; +import java.util.TreeMap; /** * Runfiles lookup library for Bazel-built Java binaries and tests. @@ -45,7 +46,7 @@ *

2. Import the runfiles library. * *

- *   import com.google.devtools.build.runfiles.Runfiles;
+ *   import Runfiles;
  * 
* *

3. Create a {@link Runfiles.Preloaded} object: @@ -59,11 +60,11 @@ *

4. To look up a runfile, use either of the following approaches: * *

4a. Annotate the class from which runfiles should be looked up with {@link - * AutoBazelRepository} and obtain the name of the Bazel repository containing the class from a - * constant generated by this annotation: + * AutoBazelRepository} and obtain the name of the Bazel + * repository containing the class from a constant generated by this annotation: * *

- *   import com.google.devtools.build.runfiles.AutoBazelRepository;
+ *   import AutoBazelRepository;
  *   @AutoBazelRepository
  *   public class MyClass {
  *     public void myFunction() {
@@ -95,7 +96,7 @@
  *
  * 
* - * For more details on why it is required to pass in the current repository name, see {@see + *

For more details on why it is required to pass in the current repository name, see {@see * https://bazel.build/build/bzlmod#repository-names}. * *

Subprocesses

@@ -204,7 +205,7 @@ public final Runfiles unmapped() { protected abstract String rlocationChecked(String path); - protected abstract Map getRepoMapping(); + protected abstract RepositoryMapping getRepoMapping(); // Private constructor, so only nested classes may extend it. private Preloaded() {} @@ -406,47 +407,19 @@ private static String getRunfilesDir(Map env) throws IOException return value; } - private static Map loadRepositoryMapping(String path) - throws IOException { - if (path == null || !new File(path).exists()) { - return Collections.emptyMap(); - } - - try (BufferedReader r = - new BufferedReader( - new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) { - return Collections.unmodifiableMap( - r.lines() - .filter(line -> !line.isEmpty()) - .map( - line -> { - String[] split = line.split(","); - if (split.length != 3) { - throw new IllegalArgumentException( - "Invalid line in repository mapping: '" + line + "'"); - } - return split; - }) - .collect( - Collectors.toMap( - split -> new Preloaded.RepoMappingKey(split[0], split[1]), - split -> split[2]))); - } - } - /** {@link Runfiles} implementation that parses a runfiles-manifest file to look up runfiles. */ private static final class ManifestBased extends Preloaded { private final Map runfiles; private final String manifestPath; - private final Map repoMapping; + private final RepositoryMapping repoMapping; ManifestBased(String manifestPath) throws IOException { Util.checkArgument(manifestPath != null); Util.checkArgument(!manifestPath.isEmpty()); this.manifestPath = manifestPath; this.runfiles = loadRunfiles(manifestPath); - this.repoMapping = loadRepositoryMapping(rlocationChecked("_repo_mapping")); + this.repoMapping = RepositoryMapping.readFromFile(rlocationChecked("_repo_mapping")); } @Override @@ -481,7 +454,7 @@ protected Map getEnvVars() { } @Override - protected Map getRepoMapping() { + protected RepositoryMapping getRepoMapping() { return repoMapping; } @@ -543,13 +516,13 @@ private static String findRunfilesDir(String manifest) { private static final class DirectoryBased extends Preloaded { private final String runfilesRoot; - private final Map repoMapping; + private final RepositoryMapping repoMapping; DirectoryBased(String runfilesDir) throws IOException { Util.checkArgument(!Util.isNullOrEmpty(runfilesDir)); Util.checkArgument(new File(runfilesDir).isDirectory()); this.runfilesRoot = runfilesDir; - this.repoMapping = loadRepositoryMapping(rlocationChecked("_repo_mapping")); + this.repoMapping = RepositoryMapping.readFromFile(rlocationChecked("_repo_mapping")); } @Override @@ -558,7 +531,7 @@ protected String rlocationChecked(String path) { } @Override - protected Map getRepoMapping() { + protected RepositoryMapping getRepoMapping() { return repoMapping; } @@ -579,4 +552,62 @@ static Preloaded createManifestBasedForTesting(String manifestPath) throws IOExc static Preloaded createDirectoryBasedForTesting(String runfilesDir) throws IOException { return new DirectoryBased(runfilesDir); } + + static final class RepositoryMapping { + private final Map exactMappings; + private final NavigableMap> wildcardMappings; + + private RepositoryMapping( + Map exactMappings, + NavigableMap> wildcardMappings) { + this.exactMappings = exactMappings; + this.wildcardMappings = wildcardMappings; + } + + static RepositoryMapping readFromFile(String path) throws IOException { + if (path == null || !new File(path).exists()) { + return new RepositoryMapping(Collections.emptyMap(), Collections.emptyNavigableMap()); + } + + try (BufferedReader r = + new BufferedReader( + new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) { + Map exactMappings = new HashMap<>(); + NavigableMap> wildcardMappings = new TreeMap<>(); + String line; + while ((line = r.readLine()) != null) { + if (line.isEmpty()) { + continue; + } + String[] split = line.split(","); + if (split.length != 3) { + throw new IllegalArgumentException( + "Invalid line in repository mapping: '" + line + "'"); + } + if (split[0].endsWith("*")) { + Map targetMap = + wildcardMappings.computeIfAbsent( + split[0].substring(0, split[0].length() - 1), k -> new HashMap<>()); + targetMap.put(split[1], split[2]); + } else { + exactMappings.put(new Preloaded.RepoMappingKey(split[0], split[1]), split[2]); + } + } + return new RepositoryMapping(exactMappings, wildcardMappings); + } + } + + String getOrDefault(Preloaded.RepoMappingKey key, String defaultValue) { + String exactMatch = exactMappings.get(key); + if (exactMatch != null) { + return exactMatch; + } + Map.Entry> floorEntry = + wildcardMappings.floorEntry(key.sourceRepo); + if (floorEntry == null || !key.sourceRepo.startsWith(floorEntry.getKey())) { + return defaultValue; + } + return floorEntry.getValue().getOrDefault(key.targetRepoApparentName, defaultValue); + } + } } From 678ea6da9257e7db1e1f5d4523b59f47e702a32a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 11 Jun 2025 13:18:27 +0200 Subject: [PATCH 2/2] Add test --- .../devtools/build/runfiles/RunfilesTest.java | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/RunfilesTest.java b/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/RunfilesTest.java index 035a0b5..406ebe8 100644 --- a/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/RunfilesTest.java +++ b/test/java/runfiles/src/test/java/com/google/devtools/build/runfiles/RunfilesTest.java @@ -427,6 +427,40 @@ public void testManifestBasedRlocationWithRepoMapping_fromOtherRepo() throws Exc assertThat(r.rlocation("protobuf")).isNull(); } + @Test + public void testManifestBasedRlocationWithRepoMapping_fromExtensionRepo() throws Exception { + Path rm = + tempFile( + "foo.repo_mapping", + ImmutableList.of( + ",config.json,config.json+1.2.3", + ",my_module,_main", + ",my_protobuf,protobuf+3.19.2", + ",my_workspace,_main", + "my_module++ext+*,my_module,my_module+", + "my_module++ext+*,repo1,my_module++ext+repo1")); + Path mf = + tempFile( + "foo.runfiles/MANIFEST", + ImmutableList.of( + "_repo_mapping " + rm, + "config.json /etc/config.json", + "protobuf+3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile", + "_main/bar/runfile /the/path/./to/other//other runfile.txt", + "protobuf+3.19.2/bar/dir E:\\Actual Path\\Directory", + "my_module+/foo/runfile /the/path/to/my_module+/runfile", + "my_module++ext+repo1/foo/runfile /the/path/to/my_module++ext+repo1/runfile", + "repo2+/foo/runfile /the/path/to/repo2+/runfile")); + Runfiles r = + Runfiles.createManifestBasedForTesting(mf.toString()) + .withSourceRepository("my_module++ext+repo1"); + + assertThat(r.rlocation("my_module/foo/runfile")).isEqualTo("/the/path/to/my_module+/runfile"); + assertThat(r.rlocation("repo1/foo/runfile")) + .isEqualTo("/the/path/to/my_module++ext+repo1/runfile"); + assertThat(r.rlocation("repo2+/foo/runfile")).isEqualTo("/the/path/to/repo2+/runfile"); + } + @Test public void testDirectoryBasedRlocationWithRepoMapping_fromMain() throws Exception { Path dir = tempDir.newFolder("foo.runfiles").toPath(); @@ -549,6 +583,28 @@ public void testDirectoryBasedRlocationWithRepoMapping_fromOtherRepo() throws Ex assertThat(r.rlocation("config.json")).isEqualTo(dir + "/config.json"); } + @Test + public void testDirectoryBasedRlocationWithRepoMapping_fromExtensionRepo() throws Exception { + Path dir = tempDir.newFolder("foo.runfiles").toPath(); + Path unused = + tempFile( + dir.resolve("_repo_mapping").toString(), + ImmutableList.of( + ",config.json,config.json+1.2.3", + ",my_module,_main", + ",my_protobuf,protobuf+3.19.2", + ",my_workspace,_main", + "my_module++ext+*,my_module,my_module+", + "my_module++ext+*,repo1,my_module++ext+repo1")); + Runfiles r = + Runfiles.createDirectoryBasedForTesting(dir.toString()) + .withSourceRepository("my_module++ext+repo1"); + + assertThat(r.rlocation("my_module/foo")).isEqualTo(dir + "/my_module+/foo"); + assertThat(r.rlocation("repo1/foo")).isEqualTo(dir + "/my_module++ext+repo1/foo"); + assertThat(r.rlocation("repo2+/foo")).isEqualTo(dir + "/repo2+/foo"); + } + @Test public void testDirectoryBasedCtorArgumentValidation() throws IOException { assertThrows( @@ -581,7 +637,8 @@ public void testManifestBasedCtorArgumentValidation() throws Exception { () -> Runfiles.createManifestBasedForTesting("").withSourceRepository("")); Path mf = tempFile("foobar", ImmutableList.of("a b")); - Runfiles unused = Runfiles.createManifestBasedForTesting(mf.toString()).withSourceRepository(""); + Runfiles unused = + Runfiles.createManifestBasedForTesting(mf.toString()).withSourceRepository(""); } @Test