Skip to content

Commit aaf87f4

Browse files
hvadehracopybara-github
authored andcommitted
Extend StarlarkRuleContextApi.runfiles to accept depset for symlinks and root symlinks
This is useful for efficiently creating a new instance of Runfiles from an existing one since the RunfilesApi returns a depset of symlinks PiperOrigin-RevId: 408853831
1 parent 7199824 commit aaf87f4

File tree

4 files changed

+110
-14
lines changed

4 files changed

+110
-14
lines changed

src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,10 @@ public Iterable<PathFragment> getExtraPaths(Set<PathFragment> manifestPaths) {
106106
// first one prevents the last one from appearing on the iterator.
107107
//
108108
// The lack of a .equals() method prevents this by making the first entry in the above case not
109-
// equals to the third one if they are not the same instance (which they almost never are)
109+
// equal to the third one if they are not the same instance (which they almost never are).
110110
//
111111
// Goodnight, prince(ss)?, and sweet dreams.
112-
private static final class SymlinkEntry implements SymlinkEntryApi {
112+
public static final class SymlinkEntry implements SymlinkEntryApi {
113113

114114
static final Depset.ElementType TYPE = Depset.ElementType.of(SymlinkEntry.class);
115115

src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import com.google.devtools.build.lib.analysis.LocationExpander;
4141
import com.google.devtools.build.lib.analysis.RuleContext;
4242
import com.google.devtools.build.lib.analysis.Runfiles;
43+
import com.google.devtools.build.lib.analysis.Runfiles.SymlinkEntry;
4344
import com.google.devtools.build.lib.analysis.RunfilesProvider;
4445
import com.google.devtools.build.lib.analysis.ShToolchain;
4546
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
@@ -54,6 +55,7 @@
5455
import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo;
5556
import com.google.devtools.build.lib.cmdline.Label;
5657
import com.google.devtools.build.lib.collect.nestedset.Depset;
58+
import com.google.devtools.build.lib.collect.nestedset.Depset.TypeException;
5759
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
5860
import com.google.devtools.build.lib.collect.nestedset.Order;
5961
import com.google.devtools.build.lib.packages.Aspect;
@@ -912,9 +914,9 @@ public Runfiles runfiles(
912914
Object transitiveFiles,
913915
Boolean collectData,
914916
Boolean collectDefault,
915-
Dict<?, ?> symlinks,
916-
Dict<?, ?> rootSymlinks)
917-
throws EvalException {
917+
Object symlinks,
918+
Object rootSymlinks)
919+
throws EvalException, TypeException {
918920
checkMutable("runfiles");
919921
Runfiles.Builder builder =
920922
new Runfiles.Builder(
@@ -941,15 +943,19 @@ public Runfiles runfiles(
941943
}
942944
builder.addTransitiveArtifacts(transitiveArtifacts);
943945
}
944-
if (!symlinks.isEmpty()) {
946+
if (isDepset(symlinks)) {
947+
builder.addSymlinks(((Depset) symlinks).getSet(SymlinkEntry.class));
948+
} else if (isNonEmptyDict(symlinks)) {
945949
// If Starlark code directly manipulates symlinks, activate more stringent validity checking.
946950
checkConflicts = true;
947951
for (Map.Entry<String, Artifact> entry :
948952
Dict.cast(symlinks, String.class, Artifact.class, "symlinks").entrySet()) {
949953
builder.addSymlink(PathFragment.create(entry.getKey()), entry.getValue());
950954
}
951955
}
952-
if (!rootSymlinks.isEmpty()) {
956+
if (isDepset(rootSymlinks)) {
957+
builder.addRootSymlinks(((Depset) rootSymlinks).getSet(SymlinkEntry.class));
958+
} else if (isNonEmptyDict(rootSymlinks)) {
953959
checkConflicts = true;
954960
for (Map.Entry<String, Artifact> entry :
955961
Dict.cast(rootSymlinks, String.class, Artifact.class, "root_symlinks").entrySet()) {
@@ -963,6 +969,14 @@ public Runfiles runfiles(
963969
return runfiles;
964970
}
965971

972+
private static boolean isNonEmptyDict(Object o) {
973+
return o instanceof Dict && !((Dict<?, ?>) o).isEmpty();
974+
}
975+
976+
private static boolean isDepset(Object o) {
977+
return o instanceof Depset;
978+
}
979+
966980
@Override
967981
public Tuple resolveCommand(
968982
String command,

src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleContextApi.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.google.devtools.build.docgen.annot.DocCategory;
1919
import com.google.devtools.build.lib.cmdline.Label;
2020
import com.google.devtools.build.lib.collect.nestedset.Depset;
21+
import com.google.devtools.build.lib.collect.nestedset.Depset.TypeException;
2122
import com.google.devtools.build.lib.starlarkbuildapi.core.ProviderApi;
2223
import com.google.devtools.build.lib.starlarkbuildapi.core.StructApi;
2324
import com.google.devtools.build.lib.starlarkbuildapi.core.TransitiveInfoCollectionApi;
@@ -564,27 +565,36 @@ String expandLocation(String input, Sequence<?> targets, StarlarkThread thread)
564565
name = "symlinks",
565566
defaultValue = "{}",
566567
named = true,
568+
allowedTypes = {
569+
@ParamType(type = Dict.class),
570+
@ParamType(type = Depset.class, generic1 = SymlinkEntryApi.class)
571+
},
567572
doc =
568-
"The map of symlinks to be added to the runfiles, prefixed by workspace name. See "
573+
"Either a SymlinkEntry depset or the map of symlinks, prefixed by workspace name, "
574+
+ "to be added to the runfiles. See "
569575
+ "<a href=\"../rules.$DOC_EXT#runfiles-symlinks\">Runfiles symlinks</a> in "
570576
+ "the rules guide."),
571577
@Param(
572578
name = "root_symlinks",
573579
defaultValue = "{}",
574580
named = true,
581+
allowedTypes = {
582+
@ParamType(type = Dict.class),
583+
@ParamType(type = Depset.class, generic1 = SymlinkEntryApi.class)
584+
},
575585
doc =
576-
"The map of symlinks to be added to the runfiles. See "
577-
+ "<a href=\"../rules.$DOC_EXT#runfiles-symlinks\">Runfiles symlinks</a> in "
578-
+ "the rules guide.")
586+
"Either a SymlinkEntry depset or a map of symlinks to be added to the runfiles. "
587+
+ "See <a href=\"../rules.$DOC_EXT#runfiles-symlinks\">Runfiles symlinks</a> "
588+
+ "in the rules guide.")
579589
})
580590
RunfilesApi runfiles(
581591
Sequence<?> files,
582592
Object transitiveFiles,
583593
Boolean collectData,
584594
Boolean collectDefault,
585-
Dict<?, ?> symlinks,
586-
Dict<?, ?> rootSymlinks)
587-
throws EvalException;
595+
Object symlinks,
596+
Object rootSymlinks)
597+
throws EvalException, TypeException;
588598

589599
@StarlarkMethod(
590600
name = "resolve_command",

src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,6 +1688,78 @@ public void testForwardingDefaultInfoRetainsDataRunfiles() throws Exception {
16881688
assertThat(forwardedRunfiles.get(0).getPath().getBaseName()).isEqualTo("i_am_a_runfile");
16891689
}
16901690

1691+
@Test
1692+
public void testAccessingRunfilesSymlinksAsDepsets() throws Exception {
1693+
// Arrange
1694+
scratch.file("test/a.py");
1695+
scratch.file("test/b.py");
1696+
scratch.file(
1697+
"test/rule.bzl",
1698+
"def symlink_impl(ctx):",
1699+
" symlinks = {",
1700+
" 'symlink_' + f.short_path: f",
1701+
" for f in ctx.files.symlink",
1702+
" }",
1703+
" root_symlinks = {",
1704+
" 'root_symlink_' + f.short_path: f",
1705+
" for f in ctx.files.symlink",
1706+
" }",
1707+
" runfiles_from_dict = ctx.runfiles(",
1708+
" symlinks=symlinks,",
1709+
" root_symlinks=root_symlinks,",
1710+
" )",
1711+
" runfiles_from_depset = ctx.runfiles(",
1712+
" symlinks = runfiles_from_dict.symlinks,",
1713+
" root_symlinks = runfiles_from_dict.root_symlinks,",
1714+
" )",
1715+
" ",
1716+
" return DefaultInfo(runfiles = runfiles_from_depset,)",
1717+
"symlink_rule = rule(",
1718+
" implementation = symlink_impl,",
1719+
" attrs = {",
1720+
" 'symlink': attr.label(allow_files=True),",
1721+
" },",
1722+
")");
1723+
scratch.file(
1724+
"test/BUILD",
1725+
"load('//test:rule.bzl', 'symlink_rule')",
1726+
"symlink_rule(name = 'lib_with_symlink', symlink = ':a.py')",
1727+
"sh_binary(",
1728+
" name = 'test_with_symlink',",
1729+
" srcs = ['test/b.py'],",
1730+
" data = [':lib_with_symlink'],",
1731+
")");
1732+
setRuleContext(createRuleContext("//test:test_with_symlink"));
1733+
1734+
// Act
1735+
Object symlinkPaths =
1736+
ev.eval("[s.path for s in ruleContext.attr.data[0].data_runfiles.symlinks.to_list()]");
1737+
Object rootSymlinkPaths =
1738+
ev.eval("[s.path for s in ruleContext.attr.data[0].data_runfiles.root_symlinks.to_list()]");
1739+
1740+
// Assert
1741+
assertThat(symlinkPaths).isInstanceOf(Sequence.class);
1742+
Sequence<?> symlinkPathsList = (Sequence) symlinkPaths;
1743+
assertThat(symlinkPathsList).containsExactly("symlink_test/a.py").inOrder();
1744+
Object symlinkFilenames =
1745+
ev.eval(
1746+
"[s.target_file.short_path for s in"
1747+
+ " ruleContext.attr.data[0].data_runfiles.symlinks.to_list()]");
1748+
assertThat(symlinkFilenames).isInstanceOf(Sequence.class);
1749+
Sequence<?> symlinkFilenamesList = (Sequence) symlinkFilenames;
1750+
assertThat(symlinkFilenamesList).containsExactly("test/a.py").inOrder();
1751+
assertThat(rootSymlinkPaths).isInstanceOf(Sequence.class);
1752+
Sequence<?> rootSymlinkPathsList = (Sequence) rootSymlinkPaths;
1753+
assertThat(rootSymlinkPathsList).containsExactly("root_symlink_test/a.py").inOrder();
1754+
Object rootSymlinkFilenames =
1755+
ev.eval(
1756+
"[s.target_file.short_path for s in"
1757+
+ " ruleContext.attr.data[0].data_runfiles.root_symlinks.to_list()]");
1758+
assertThat(rootSymlinkFilenames).isInstanceOf(Sequence.class);
1759+
Sequence<?> rootSymlinkFilenamesList = (Sequence) rootSymlinkFilenames;
1760+
assertThat(rootSymlinkFilenamesList).containsExactly("test/a.py").inOrder();
1761+
}
1762+
16911763
@Test
16921764
public void runfiles_merge() throws Exception {
16931765
scratch.file("test/a.py");

0 commit comments

Comments
 (0)