From c019df06cf9307a63e44d51c31823ed4e5dad146 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 7 Oct 2022 23:16:20 +0200 Subject: [PATCH] Add `$(rlocationpath(s) ...)` expansion The new location expansion pattern `rlocationpath` and its plural version `rlocationpaths` resolve to the path(s) suitable for the Rlocation function offered by runfiles libraries. Compared to the `rootpath` pattern, which can returns '../repo_name/pkg/file` for a file in an external repository and `pkg/file` for a file in the main repository, the path returned by `rlocationpath` is always of the form `repo_name/pkg/file`. Work towards #16124 Fixes #10923 --- .../docgen/templates/be/make-variables.vm | 69 ++++++++---- .../build/lib/analysis/LocationExpander.java | 102 ++++++++++++------ .../lib/analysis/LocationTemplateContext.java | 10 +- .../LocationExpanderIntegrationTest.java | 33 ++++++ .../lib/analysis/LocationExpanderTest.java | 12 ++- .../lib/analysis/LocationFunctionTest.java | 48 +++++---- src/test/py/bazel/runfiles_test.py | 39 +++++++ 7 files changed, 233 insertions(+), 80 deletions(-) diff --git a/src/main/java/com/google/devtools/build/docgen/templates/be/make-variables.vm b/src/main/java/com/google/devtools/build/docgen/templates/be/make-variables.vm index 096a6eac0aea0d..7f0a003ec4fd43 100644 --- a/src/main/java/com/google/devtools/build/docgen/templates/be/make-variables.vm +++ b/src/main/java/com/google/devtools/build/docgen/templates/be/make-variables.vm @@ -262,15 +262,15 @@

Output files are staged similarly, but are also prefixed with the subpath - bazel-out/cpu-compilation_mode/bin (or for certain outputs: - bazel-out/cpu-compilation_mode/genfiles, or for the outputs - of host tools: bazel-out/host/bin). In the above example, - //testapp:app is a host tool because it appears in + bazel-out/cpu-compilation_mode/bin (or for the outputs of + tools: bazel-out/cpu-opt-exec-hash/bin). In the above example, + //testapp:app is a tool because it appears in show_app_output's tools attribute. So its output file app is written to - bazel-myproject/bazel-out/host/bin/testapp/app. The exec path - is thus bazel-out/host/bin/testapp/app. This extra prefix + bazel-myproject/bazel-out/cpu-opt-exec-hash/bin/testapp/app. + The exec path is thus + bazel-out/cpu-opt-exec-hash/bin/testapp/app. This extra prefix makes it possible to build the same target for, say, two different CPUs in the same build without the results clobbering each other.

@@ -284,12 +284,17 @@
  • - rootpath: Denotes the runfiles path that a built binary can - use to find its dependencies at runtime. -

    + rootpath: Denotes the path that a built binary can use to + find a dependency at runtime relative to the subdirectory of its runfiles + directory corresponding to the main repository. + Note: This only works if + --enable_runfiles is enabled, which is not the case on + Windows by default. Use rlocationpath instead for + cross-platform support.

    - This is the same as execpath but strips the output prefixes - described above. In the above example this means both + This is similar to execpath but strips the configuration + prefixes described above. In the example from above this means both empty.source and app use pure workspace-relative paths: testapp/empty.source and testapp/app.

    @@ -298,29 +303,57 @@

  • +
  • +

    + rlocationpath: The path a built binary can pass to the + Rlocation function of a runfiles library to find a dependency at + runtime, either in the runfiles directory (if available) or using the + runfiles manifest. +

    +

    + This is similar to rootpath in that it does not contain + configuration prefixes, but differs in that it always starts with the + name of the repository. In the example from above this means that + empty.source and app result in the following + paths: myproject/testapp/empty.source and + myproject/testapp/app. +

    +

    + Passing this path to a binary and resolving it to a file system path using + the runfiles libraries is the preferred approach to find dependencies at + runtime. Compared to rootpath, it has the advantage that it + works on all platforms and even if the runfiles directory should not be + available. +

    +

    + This has the same "one output only" requirements as execpath. +

    +
  • +
  • location: A synonym for either execpath or rootpath, depending on the attribute being expanded. This is legacy pre-Starlark behavior and not recommended unless you really know what it does for a particular rule. See #2475 + href="https://github.com/bazelbuild/bazel/issues/2475#issuecomment-339318016">#2475 for details.
  • - execpaths, rootpaths, and locations are - the plural variations of execpath, rootpath, and - location, respectively. They support labels producing multiple - outputs, in which case each output is listed separated by a space. Zero-output - rules and malformed labels produce build errors. + execpaths, rootpaths, rlocationpaths, + and locations are the plural variations of execpath, + rootpath, rlocationpaths, andlocation, + respectively. They support labels producing multiple outputs, in which case + each output is listed separated by a space. Zero-output rules and malformed + labels produce build errors.

    All referenced labels must appear in the consuming target's srcs, output files, or deps. Otherwise the build fails. C++ targets can also reference labels in data. + href="$expander.expandRef("cc_binary.data")">data.

    diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java b/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java index d82d3de89a0f7d..bee7944c86835c 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java @@ -17,6 +17,7 @@ import static java.util.stream.Collectors.joining; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableCollection; @@ -26,7 +27,9 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.LocationExpander.LocationFunction.PathType; import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.cmdline.LabelConstants; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.RepositoryMapping; import com.google.devtools.build.lib.packages.BuildType; @@ -63,34 +66,35 @@ public final class LocationExpander { private static final boolean EXACTLY_ONE = false; private static final boolean ALLOW_MULTIPLE = true; - private static final boolean USE_LOCATION_PATHS = false; - private static final boolean USE_EXEC_PATHS = true; - private final RuleErrorConsumer ruleErrorConsumer; private final ImmutableMap functions; private final RepositoryMapping repositoryMapping; + private final String workspaceRunfilesDirectory; @VisibleForTesting LocationExpander( RuleErrorConsumer ruleErrorConsumer, Map functions, - RepositoryMapping repositoryMapping) { + RepositoryMapping repositoryMapping, + String workspaceRunfilesDirectory) { this.ruleErrorConsumer = ruleErrorConsumer; this.functions = ImmutableMap.copyOf(functions); this.repositoryMapping = repositoryMapping; + this.workspaceRunfilesDirectory = workspaceRunfilesDirectory; } private LocationExpander( - RuleErrorConsumer ruleErrorConsumer, + RuleContext ruleContext, Label root, Supplier>> locationMap, boolean execPaths, boolean legacyExternalRunfiles, RepositoryMapping repositoryMapping) { this( - ruleErrorConsumer, + ruleContext, allLocationFunctions(root, locationMap, execPaths, legacyExternalRunfiles), - repositoryMapping); + repositoryMapping, + ruleContext.getWorkspaceName()); } /** @@ -204,7 +208,7 @@ private String expand(String value, ErrorReporter reporter) { // (2) Call appropriate function to obtain string replacement. String functionValue = value.substring(nextWhitespace + 1, end).trim(); try { - String replacement = functions.get(fname).apply(functionValue, repositoryMapping); + String replacement = functions.get(fname).apply(functionValue, repositoryMapping, workspaceRunfilesDirectory); result.append(replacement); } catch (IllegalStateException ise) { reporter.report(ise.getMessage()); @@ -232,23 +236,29 @@ public String expandAttribute(String attrName, String attrValue) { @VisibleForTesting static final class LocationFunction { + enum PathType { + LOCATION, + EXEC, + RLOCATION, + } + private static final int MAX_PATHS_SHOWN = 5; private final Label root; private final Supplier>> locationMapSupplier; - private final boolean execPaths; + private final PathType pathType; private final boolean legacyExternalRunfiles; private final boolean multiple; LocationFunction( - Label root, - Supplier>> locationMapSupplier, - boolean execPaths, - boolean legacyExternalRunfiles, - boolean multiple) { + Label root, + Supplier>> locationMapSupplier, + PathType pathType, + boolean legacyExternalRunfiles, + boolean multiple) { this.root = root; this.locationMapSupplier = locationMapSupplier; - this.execPaths = execPaths; + this.pathType = Preconditions.checkNotNull(pathType); this.legacyExternalRunfiles = legacyExternalRunfiles; this.multiple = multiple; } @@ -259,10 +269,11 @@ static final class LocationFunction { * using the {@code repositoryMapping}. * * @param arg The label-like string to be expanded, e.g. ":foo" or "//foo:bar" - * @param repositoryMapping map of {@code RepositoryName}s defined in the main workspace + * @param repositoryMapping map of apparent repository names to {@code RepositoryName}s + * @param workspaceRunfilesDirectory name of the runfiles directory corresponding to the main repository * @return The expanded value */ - public String apply(String arg, RepositoryMapping repositoryMapping) { + public String apply(String arg, RepositoryMapping repositoryMapping, String workspaceRunfilesDirectory) { Label label; try { label = root.getRelativeWithRemapping(arg, repositoryMapping); @@ -271,14 +282,14 @@ public String apply(String arg, RepositoryMapping repositoryMapping) { String.format( "invalid label in %s expression: %s", functionName(), e.getMessage()), e); } - Collection paths = resolveLabel(label); + Collection paths = resolveLabel(label, workspaceRunfilesDirectory); return joinPaths(paths); } /** * Returns all target location(s) of the given label. */ - private Collection resolveLabel(Label unresolved) throws IllegalStateException { + private Collection resolveLabel(Label unresolved, String workspaceRunfilesDirectory) throws IllegalStateException { Collection artifacts = locationMapSupplier.get().get(unresolved); if (artifacts == null) { @@ -288,7 +299,7 @@ private Collection resolveLabel(Label unresolved) throws IllegalStateExc unresolved, functionName())); } - Set paths = getPaths(artifacts); + Set paths = getPaths(artifacts, workspaceRunfilesDirectory); if (paths.isEmpty()) { throw new IllegalStateException( String.format( @@ -313,24 +324,37 @@ private Collection resolveLabel(Label unresolved) throws IllegalStateExc * Extracts list of all executables associated with given collection of label artifacts. * * @param artifacts to get the paths of + * @param workspaceRunfilesDirectory name of the runfiles directory corresponding to the main repository * @return all associated executable paths */ - private Set getPaths(Collection artifacts) { + private Set getPaths(Collection artifacts, String workspaceRunfilesDirectory) { TreeSet paths = Sets.newTreeSet(); for (Artifact artifact : artifacts) { - PathFragment execPath = - execPaths - ? artifact.getExecPath() - : legacyExternalRunfiles - ? artifact.getPathForLocationExpansion() - : artifact.getRunfilesPath(); - if (execPath != null) { // omit middlemen etc - paths.add(execPath.getCallablePathString()); + PathFragment path = getPath(artifact, workspaceRunfilesDirectory); + if (path != null) { // omit middlemen etc + paths.add(path.getCallablePathString()); } } return paths; } + private PathFragment getPath(Artifact artifact, String workspaceRunfilesDirectory) { + switch (pathType) { + case LOCATION: + return legacyExternalRunfiles ? artifact.getPathForLocationExpansion() : artifact.getRunfilesPath(); + case EXEC: + return artifact.getExecPath(); + case RLOCATION: + PathFragment runfilesPath = artifact.getRunfilesPath(); + if (runfilesPath.startsWith(LabelConstants.EXTERNAL_RUNFILES_PATH_PREFIX)) { + return runfilesPath.relativeTo(LabelConstants.EXTERNAL_RUNFILES_PATH_PREFIX); + } else { + return PathFragment.create(workspaceRunfilesDirectory).getRelative(runfilesPath); + } + } + throw new IllegalStateException("Unexpected PathType: " + pathType); + } + private String joinPaths(Collection paths) { return paths.stream().map(ShellEscaper::escapeString).collect(joining(" ")); } @@ -348,27 +372,35 @@ static ImmutableMap allLocationFunctions( return new ImmutableMap.Builder() .put( "location", - new LocationFunction(root, locationMap, execPaths, legacyExternalRunfiles, EXACTLY_ONE)) + new LocationFunction(root, locationMap, execPaths ? PathType.EXEC : PathType.LOCATION, legacyExternalRunfiles, EXACTLY_ONE)) .put( "locations", new LocationFunction( - root, locationMap, execPaths, legacyExternalRunfiles, ALLOW_MULTIPLE)) + root, locationMap, execPaths ? PathType.EXEC : PathType.LOCATION, legacyExternalRunfiles, ALLOW_MULTIPLE)) .put( "rootpath", new LocationFunction( - root, locationMap, USE_LOCATION_PATHS, legacyExternalRunfiles, EXACTLY_ONE)) + root, locationMap, PathType.LOCATION, legacyExternalRunfiles, EXACTLY_ONE)) .put( "rootpaths", new LocationFunction( - root, locationMap, USE_LOCATION_PATHS, legacyExternalRunfiles, ALLOW_MULTIPLE)) + root, locationMap, PathType.LOCATION, legacyExternalRunfiles, ALLOW_MULTIPLE)) .put( "execpath", new LocationFunction( - root, locationMap, USE_EXEC_PATHS, legacyExternalRunfiles, EXACTLY_ONE)) + root, locationMap, PathType.EXEC, legacyExternalRunfiles, EXACTLY_ONE)) .put( "execpaths", new LocationFunction( - root, locationMap, USE_EXEC_PATHS, legacyExternalRunfiles, ALLOW_MULTIPLE)) + root, locationMap, PathType.EXEC, legacyExternalRunfiles, ALLOW_MULTIPLE)) + .put( + "rlocationpath", + new LocationFunction( + root, locationMap, PathType.RLOCATION, legacyExternalRunfiles, EXACTLY_ONE)) + .put( + "rlocationpaths", + new LocationFunction( + root, locationMap, PathType.RLOCATION, legacyExternalRunfiles, ALLOW_MULTIPLE)) .buildOrThrow(); } diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LocationTemplateContext.java b/src/main/java/com/google/devtools/build/lib/analysis/LocationTemplateContext.java index f3e66781c7dec3..0de4a4e498c49b 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/LocationTemplateContext.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/LocationTemplateContext.java @@ -50,6 +50,7 @@ final class LocationTemplateContext implements TemplateContext { private final ImmutableMap functions; private final RepositoryMapping repositoryMapping; private final boolean windowsPath; + private final String workspaceRunfilesDirectory; private LocationTemplateContext( TemplateContext delegate, @@ -58,12 +59,14 @@ private LocationTemplateContext( boolean execPaths, boolean legacyExternalRunfiles, RepositoryMapping repositoryMapping, - boolean windowsPath) { + boolean windowsPath, + String workspaceRunfilesDirectory) { this.delegate = delegate; this.functions = LocationExpander.allLocationFunctions(root, locationMap, execPaths, legacyExternalRunfiles); this.repositoryMapping = repositoryMapping; this.windowsPath = windowsPath; + this.workspaceRunfilesDirectory = workspaceRunfilesDirectory; } public LocationTemplateContext( @@ -83,7 +86,8 @@ public LocationTemplateContext( execPaths, ruleContext.getConfiguration().legacyExternalRunfiles(), ruleContext.getRule().getPackage().getRepositoryMapping(), - windowsPath); + windowsPath, + ruleContext.getWorkspaceName()); } @Override @@ -108,7 +112,7 @@ private String lookupFunctionImpl(String name, String param) throws ExpansionExc try { LocationFunction f = functions.get(name); if (f != null) { - return f.apply(param, repositoryMapping); + return f.apply(param, repositoryMapping, workspaceRunfilesDirectory); } } catch (IllegalStateException e) { throw new ExpansionException(e.getMessage(), e); diff --git a/src/test/java/com/google/devtools/build/lib/analysis/LocationExpanderIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/analysis/LocationExpanderIntegrationTest.java index 4f40434ea75b74..25f04227f20921 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/LocationExpanderIntegrationTest.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/LocationExpanderIntegrationTest.java @@ -121,6 +121,13 @@ public void otherPathExpansion() throws Exception { "genrule(name='foo', outs=['foo.txt'], cmd='never executed')", "sh_library(name='lib', srcs=[':foo'])"); + FileSystemUtils.appendIsoLatin1( + scratch.resolve("WORKSPACE"), "workspace(name='workspace')"); + // Invalidate WORKSPACE to pick up the name. + getSkyframeExecutor() + .invalidateFilesUnderPathForTesting( + reporter, ModifiedFileSet.EVERYTHING_MODIFIED, Root.fromPath(rootDirectory)); + LocationExpander expander = makeExpander("//expansion:lib"); assertThat(expander.expand("foo $(execpath :foo) bar")) .matches("foo .*-out/.*/expansion/foo\\.txt bar"); @@ -130,6 +137,22 @@ public void otherPathExpansion() throws Exception { .matches("foo expansion/foo.txt bar"); assertThat(expander.expand("foo $(rootpaths :foo) bar")) .matches("foo expansion/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpath :foo) bar")) + .isEqualTo("foo workspace/expansion/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpaths :foo) bar")) + .isEqualTo("foo workspace/expansion/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpath //expansion:foo) bar")) + .isEqualTo("foo workspace/expansion/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpaths //expansion:foo) bar")) + .isEqualTo("foo workspace/expansion/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpath @//expansion:foo) bar")) + .isEqualTo("foo workspace/expansion/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpaths @//expansion:foo) bar")) + .isEqualTo("foo workspace/expansion/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpath @workspace//expansion:foo) bar")) + .isEqualTo("foo workspace/expansion/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpaths @workspace//expansion:foo) bar")) + .isEqualTo("foo workspace/expansion/foo.txt bar"); } @Test @@ -158,6 +181,10 @@ public void otherPathExternalExpansion() throws Exception { .matches("foo external/r/p/foo.txt bar"); assertThat(expander.expand("foo $(rootpaths @r//p:foo) bar")) .matches("foo external/r/p/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpath @r//p:foo) bar")) + .isEqualTo("foo r/p/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpath @r//p:foo) bar")) + .isEqualTo("foo r/p/foo.txt bar"); } @Test @@ -183,6 +210,8 @@ public void otherPathExternalExpansionNoLegacyExternalRunfiles() throws Exceptio .matches("foo .*-out/.*/external/r/p/foo\\.txt bar"); assertThat(expander.expand("foo $(rootpath @r//p:foo) bar")).matches("foo ../r/p/foo.txt bar"); assertThat(expander.expand("foo $(rootpaths @r//p:foo) bar")).matches("foo ../r/p/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpath @r//p:foo) bar")).isEqualTo("foo r/p/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpath @r//p:foo) bar")).isEqualTo("foo r/p/foo.txt bar"); } @Test @@ -210,6 +239,8 @@ public void otherPathExternalExpansionNoLegacyExternalRunfilesSiblingRepositoryL .matches("foo .*-out/r/.*/p/foo\\.txt bar"); assertThat(expander.expand("foo $(rootpath @r//p:foo) bar")).matches("foo ../r/p/foo.txt bar"); assertThat(expander.expand("foo $(rootpaths @r//p:foo) bar")).matches("foo ../r/p/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpath @r//p:foo) bar")).isEqualTo("foo r/p/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpaths @r//p:foo) bar")).isEqualTo("foo r/p/foo.txt bar"); } @Test @@ -224,5 +255,7 @@ public void otherPathMultiExpansion() throws Exception { .matches("foo .*-out/.*/expansion/bar\\.txt .*-out/.*/expansion/foo\\.txt bar"); assertThat(expander.expand("foo $(rootpaths :foo) bar")) .matches("foo expansion/bar.txt expansion/foo.txt bar"); + assertThat(expander.expand("foo $(rlocationpaths :foo) bar")) + .isEqualTo("foo __main__/expansion/bar.txt __main__/expansion/foo.txt bar"); } } diff --git a/src/test/java/com/google/devtools/build/lib/analysis/LocationExpanderTest.java b/src/test/java/com/google/devtools/build/lib/analysis/LocationExpanderTest.java index abdeb1ed742df4..81fe420bc0b115 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/LocationExpanderTest.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/LocationExpanderTest.java @@ -60,12 +60,12 @@ public boolean hasErrors() { private LocationExpander makeExpander(RuleErrorConsumer ruleErrorConsumer) throws Exception { LocationFunction f1 = new LocationFunctionBuilder("//a", false) - .setExecPaths(false) + .setPathType(LocationFunction.PathType.LOCATION) .add("//a", "/exec/src/a") .build(); LocationFunction f2 = new LocationFunctionBuilder("//b", true) - .setExecPaths(false) + .setPathType(LocationFunction.PathType.LOCATION) .add("//b", "/exec/src/b") .build(); @@ -74,7 +74,8 @@ private LocationExpander makeExpander(RuleErrorConsumer ruleErrorConsumer) throw ImmutableMap.of( "location", f1, "locations", f2), - RepositoryMapping.ALWAYS_FALLBACK); + RepositoryMapping.ALWAYS_FALLBACK, + "workspace"); } private String expand(String input) throws Exception { @@ -126,7 +127,7 @@ public void noExpansionOnError() throws Exception { @Test public void expansionWithRepositoryMapping() throws Exception { LocationFunction f1 = new LocationFunctionBuilder("//a", false) - .setExecPaths(false) + .setPathType(LocationFunction.PathType.LOCATION) .add("@bar//a", "/exec/src/a") .build(); @@ -137,7 +138,8 @@ public void expansionWithRepositoryMapping() throws Exception { new LocationExpander( new Capture(), ImmutableMap.of("location", f1), - RepositoryMapping.createAllowingFallback(repositoryMapping)); + RepositoryMapping.createAllowingFallback(repositoryMapping), + "workspace"); String value = locationExpander.expand("$(location @foo//a)"); assertThat(value).isEqualTo("src/a"); diff --git a/src/test/java/com/google/devtools/build/lib/analysis/LocationFunctionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/LocationFunctionTest.java index 38addac4979d01..e81d6a0fc155e8 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/LocationFunctionTest.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/LocationFunctionTest.java @@ -49,16 +49,16 @@ public class LocationFunctionTest { public void absoluteAndRelativeLabels() throws Exception { LocationFunction func = new LocationFunctionBuilder("//foo", false).add("//foo", "/exec/src/bar").build(); - assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK)).isEqualTo("src/bar"); - assertThat(func.apply(":foo", RepositoryMapping.ALWAYS_FALLBACK)).isEqualTo("src/bar"); - assertThat(func.apply("foo", RepositoryMapping.ALWAYS_FALLBACK)).isEqualTo("src/bar"); + assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK, null)).isEqualTo("src/bar"); + assertThat(func.apply(":foo", RepositoryMapping.ALWAYS_FALLBACK, null)).isEqualTo("src/bar"); + assertThat(func.apply("foo", RepositoryMapping.ALWAYS_FALLBACK, null)).isEqualTo("src/bar"); } @Test public void pathUnderExecRootUsesDotSlash() throws Exception { LocationFunction func = new LocationFunctionBuilder("//foo", false).add("//foo", "/exec/bar").build(); - assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK)).isEqualTo("./bar"); + assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK, null)).isEqualTo("./bar"); } @Test @@ -67,7 +67,7 @@ public void noSuchLabel() throws Exception { IllegalStateException expected = assertThrows( IllegalStateException.class, - () -> func.apply("//bar", RepositoryMapping.ALWAYS_FALLBACK)); + () -> func.apply("//bar", RepositoryMapping.ALWAYS_FALLBACK, null)); assertThat(expected) .hasMessageThat() .isEqualTo( @@ -81,7 +81,7 @@ public void emptyList() throws Exception { IllegalStateException expected = assertThrows( IllegalStateException.class, - () -> func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK)); + () -> func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK, null)); assertThat(expected) .hasMessageThat() .isEqualTo("label '//foo:foo' in $(location) expression expands to no files"); @@ -94,7 +94,7 @@ public void tooMany() throws Exception { IllegalStateException expected = assertThrows( IllegalStateException.class, - () -> func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK)); + () -> func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK, null)); assertThat(expected) .hasMessageThat() .isEqualTo( @@ -109,7 +109,7 @@ public void noSuchLabelMultiple() throws Exception { IllegalStateException expected = assertThrows( IllegalStateException.class, - () -> func.apply("//bar", RepositoryMapping.ALWAYS_FALLBACK)); + () -> func.apply("//bar", RepositoryMapping.ALWAYS_FALLBACK, null)); assertThat(expected) .hasMessageThat() .isEqualTo( @@ -121,7 +121,7 @@ public void noSuchLabelMultiple() throws Exception { public void fileWithSpace() throws Exception { LocationFunction func = new LocationFunctionBuilder("//foo", false).add("//foo", "/exec/file/with space").build(); - assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK)) + assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK, null)) .isEqualTo("'file/with space'"); } @@ -130,7 +130,7 @@ public void multipleFiles() throws Exception { LocationFunction func = new LocationFunctionBuilder("//foo", true) .add("//foo", "/exec/foo/bar", "/exec/out/foo/foobar") .build(); - assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK)) + assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK, null)) .isEqualTo("foo/bar foo/foobar"); } @@ -139,27 +139,37 @@ public void filesWithSpace() throws Exception { LocationFunction func = new LocationFunctionBuilder("//foo", true) .add("//foo", "/exec/file/with space", "/exec/file/with spaces ") .build(); - assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK)) + assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK, null)) .isEqualTo("'file/with space' 'file/with spaces '"); } @Test public void execPath() throws Exception { LocationFunction func = new LocationFunctionBuilder("//foo", true) - .setExecPaths(true) + .setPathType(LocationFunction.PathType.EXEC) .add("//foo", "/exec/bar", "/exec/out/foobar") .build(); - assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK)) + assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK, null)) .isEqualTo("./bar out/foobar"); } + @Test + public void rlocationPath() throws Exception { + LocationFunction func = new LocationFunctionBuilder("//foo", true) + .setPathType(LocationFunction.PathType.RLOCATION) + .add("//foo", "/exec/bar", "/exec/out/foobar") + .build(); + assertThat(func.apply("//foo", RepositoryMapping.ALWAYS_FALLBACK, "workspace")) + .isEqualTo("workspace/bar workspace/foobar"); + } + @Test public void locationFunctionWithMappingReplace() throws Exception { RepositoryName b = RepositoryName.create("b"); ImmutableMap repositoryMapping = ImmutableMap.of("a", b); LocationFunction func = new LocationFunctionBuilder("//foo", false).add("@b//foo", "/exec/src/bar").build(); - assertThat(func.apply("@a//foo", RepositoryMapping.createAllowingFallback(repositoryMapping))) + assertThat(func.apply("@a//foo", RepositoryMapping.createAllowingFallback(repositoryMapping), null)) .isEqualTo("src/bar"); } @@ -170,7 +180,7 @@ public void locationFunctionWithMappingIgnoreRepo() throws Exception { LocationFunction func = new LocationFunctionBuilder("//foo", false).add("@potato//foo", "/exec/src/bar").build(); assertThat( - func.apply("@potato//foo", RepositoryMapping.createAllowingFallback(repositoryMapping))) + func.apply("@potato//foo", RepositoryMapping.createAllowingFallback(repositoryMapping), null)) .isEqualTo("src/bar"); } } @@ -178,7 +188,7 @@ public void locationFunctionWithMappingIgnoreRepo() throws Exception { final class LocationFunctionBuilder { private final Label root; private final boolean multiple; - private boolean execPaths; + private LocationFunction.PathType pathType = LocationFunction.PathType.LOCATION; private boolean legacyExternalRunfiles; private final Map> labelMap = new HashMap<>(); @@ -189,12 +199,12 @@ final class LocationFunctionBuilder { public LocationFunction build() { return new LocationFunction( - root, Suppliers.ofInstance(labelMap), execPaths, legacyExternalRunfiles, multiple); + root, Suppliers.ofInstance(labelMap), pathType, legacyExternalRunfiles, multiple); } @CanIgnoreReturnValue - public LocationFunctionBuilder setExecPaths(boolean execPaths) { - this.execPaths = execPaths; + public LocationFunctionBuilder setPathType(LocationFunction.PathType pathType) { + this.pathType = pathType; return this; } diff --git a/src/test/py/bazel/runfiles_test.py b/src/test/py/bazel/runfiles_test.py index cb37929531360e..c4c022e889b2de 100644 --- a/src/test/py/bazel/runfiles_test.py +++ b/src/test/py/bazel/runfiles_test.py @@ -312,6 +312,45 @@ def testLegacyExternalRunfilesOption(self): "bin/bin.runfiles_manifest") self.AssertFileContentNotContains(manifest_path, "__main__/external/A") + def testRunfilesLibrariesFindRlocationpathExpansion(self): + self.ScratchDir("A") + self.ScratchFile("A/WORKSPACE") + self.ScratchFile("A/p/BUILD", + ["exports_files(['foo.txt'])"]) + self.ScratchFile("A/p/foo.txt", + ["Hello, World!"]) + self.ScratchFile("WORKSPACE", + ["local_repository(name = 'A', path='A')"]) + self.ScratchFile("pkg/BUILD", [ + "py_binary(", + " name = 'bin',", + " srcs = ['bin.py'],", + " args = [", + " '$(rlocationpath bar.txt)',", + " '$(rlocationpath @A//p:foo.txt)',", + " ],", + " data = [", + " 'bar.txt',", + " '@A//p:foo.txt'", + " ],", + " deps = ['@bazel_tools//tools/python/runfiles'],", + ")", + ]) + self.ScratchFile("pkg/bar.txt", + ["Hello, Bazel!"]) + self.ScratchFile("pkg/bin.py", [ + "import sys", + "from tools.python.runfiles import runfiles", + "r = runfiles.Create()", + "for arg in sys.argv[1:]:", + " print(open(r.Rlocation(arg)).read().strip())", + ]) + _, stdout, _ = self.RunBazel(["run", "//pkg:bin"]) + if len(stdout) != 2: + self.fail("stdout: %s" % stdout) + self.assertEqual(stdout[0], "Hello, Bazel!") + self.assertEqual(stdout[1], "Hello, World!") + if __name__ == "__main__": unittest.main()