Skip to content

Commit 75f1c55

Browse files
tjgqMivr
authored andcommitted
Add a target_type argument to ctx.actions.symlink.
This is necessary to create the right kind of filesystem object on Windows (junction for directories, symlink for files) when the target does not yet exist. This argument is only allowed in conjunction with `target_path`. For `target_file`, I have a different plan (infer the type from the artifact) which will be implemented separately. Fixes #26701. Progress on #21747. RELNOTES: `ctx.actions.symlink` now accepts a `target_type` argument. PiperOrigin-RevId: 820670309 Change-Id: I2f29adfd074c404a0b15be369a97fcdfb84fbdad
1 parent 7817780 commit 75f1c55

File tree

7 files changed

+119
-10
lines changed

7 files changed

+119
-10
lines changed

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
import com.google.devtools.build.lib.supplier.InterruptibleSupplier;
7171
import com.google.devtools.build.lib.util.OS;
7272
import com.google.devtools.build.lib.vfs.PathFragment;
73+
import com.google.devtools.build.lib.vfs.SymlinkTargetType;
7374
import com.google.protobuf.GeneratedMessage;
7475
import java.nio.charset.StandardCharsets;
7576
import java.util.Arrays;
@@ -250,6 +251,7 @@ public void symlink(
250251
FileApi output,
251252
Object /* Artifact or None */ targetFile,
252253
Object /* String or None */ targetPath,
254+
Object /* String or None */ targetType,
253255
Boolean isExecutable,
254256
Object /* String or None */ progressMessageUnchecked,
255257
Object useExecRootForSourceObject,
@@ -277,6 +279,10 @@ public void symlink(
277279

278280
Action action;
279281
if (targetFile != Starlark.NONE) {
282+
if (targetType != Starlark.NONE) {
283+
throw Starlark.errorf("\"target_type\" cannot be used with \"target_file\"");
284+
}
285+
280286
Artifact inputArtifact = (Artifact) targetFile;
281287
if (outputArtifact.isSymlink()) {
282288
throw Starlark.errorf(
@@ -322,9 +328,24 @@ public void symlink(
322328
throw Starlark.errorf("\"is_executable\" cannot be True when using \"target_path\"");
323329
}
324330

331+
SymlinkTargetType symlinkTargetType = SymlinkTargetType.UNSPECIFIED;
332+
if (targetType instanceof String targetTypeStr) {
333+
symlinkTargetType =
334+
switch (targetTypeStr) {
335+
case "file" -> SymlinkTargetType.FILE;
336+
case "directory" -> SymlinkTargetType.DIRECTORY;
337+
default ->
338+
throw Starlark.errorf("\"target_type\" must be one of \"file\" or \"directory\"");
339+
};
340+
}
341+
325342
action =
326343
UnresolvedSymlinkAction.create(
327-
ruleContext.getActionOwner(), outputArtifact, (String) targetPath, progressMessage);
344+
ruleContext.getActionOwner(),
345+
outputArtifact,
346+
(String) targetPath,
347+
symlinkTargetType,
348+
progressMessage);
328349
}
329350
registerAction(action);
330351
}

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.google.devtools.build.lib.util.Fingerprint;
3535
import com.google.devtools.build.lib.vfs.Path;
3636
import com.google.devtools.build.lib.vfs.PathFragment;
37+
import com.google.devtools.build.lib.vfs.SymlinkTargetType;
3738
import java.io.IOException;
3839
import javax.annotation.Nullable;
3940

@@ -47,19 +48,29 @@ public final class UnresolvedSymlinkAction extends AbstractAction {
4748
private static final String GUID = "0f302651-602c-404b-881c-58913193cfe7";
4849

4950
private final String target;
51+
private final SymlinkTargetType targetType;
5052
private final String progressMessage;
5153

5254
private UnresolvedSymlinkAction(
53-
ActionOwner owner, Artifact primaryOutput, String target, String progressMessage) {
55+
ActionOwner owner,
56+
Artifact primaryOutput,
57+
String target,
58+
SymlinkTargetType targetType,
59+
String progressMessage) {
5460
super(owner, NestedSetBuilder.emptySet(Order.STABLE_ORDER), ImmutableSet.of(primaryOutput));
5561
this.target = target;
62+
this.targetType = targetType;
5663
this.progressMessage = progressMessage;
5764
}
5865

5966
public static UnresolvedSymlinkAction create(
60-
ActionOwner owner, Artifact primaryOutput, String target, String progressMessage) {
67+
ActionOwner owner,
68+
Artifact primaryOutput,
69+
String target,
70+
SymlinkTargetType targetType,
71+
String progressMessage) {
6172
Preconditions.checkArgument(primaryOutput.isSymlink());
62-
return new UnresolvedSymlinkAction(owner, primaryOutput, target, progressMessage);
73+
return new UnresolvedSymlinkAction(owner, primaryOutput, target, targetType, progressMessage);
6374
}
6475

6576
@Override
@@ -68,7 +79,7 @@ public ActionResult execute(ActionExecutionContext actionExecutionContext)
6879

6980
Path outputPath = actionExecutionContext.getInputPath(getPrimaryOutput());
7081
try {
71-
outputPath.createSymbolicLink(getTargetPathFragment());
82+
outputPath.createSymbolicLink(getTargetPathFragment(), targetType);
7283
} catch (IOException e) {
7384
String message =
7485
String.format(
@@ -88,11 +99,12 @@ protected void computeKey(
8899
Fingerprint fp) {
89100
fp.addString(GUID);
90101
fp.addString(target);
102+
fp.addString(targetType.name());
91103
}
92104

93105
@Override
94106
public String describeKey() {
95-
return String.format("GUID: %s\ntarget: %s\n", GUID, target);
107+
return String.format("GUID: %s\ntarget: %s\ntype: %s\n", GUID, target, targetType.name());
96108
}
97109

98110
@Override

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,21 @@ public interface StarlarkActionFactoryApi extends StarlarkValue {
235235
doc =
236236
"The exact path that the output symlink will point to. No normalization or other "
237237
+ "processing is applied."),
238+
@Param(
239+
name = "target_type",
240+
allowedTypes = {
241+
@ParamType(type = String.class),
242+
@ParamType(type = NoneType.class),
243+
},
244+
named = true,
245+
positional = false,
246+
defaultValue = "None",
247+
doc =
248+
"May only be used with <code>target_path</code>, not <code>target_file</code>. If"
249+
+ " specified, it must be one of 'file' or 'directory', indicating the target"
250+
+ " path's expected type.<p>On Windows, this determines which kind of"
251+
+ " filesystem object to create (junction for a directory, symlink for a file)."
252+
+ " It has no effect on other operating systems."),
238253
@Param(
239254
name = "is_executable",
240255
named = true,
@@ -270,6 +285,7 @@ void symlink(
270285
FileApi output,
271286
Object targetFile,
272287
Object targetPath,
288+
Object targetType,
273289
Boolean isExecutable,
274290
Object progressMessage,
275291
Object useExecRootForSourceObject,

src/main/java/com/google/devtools/build/lib/vfs/SymlinkTargetType.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
//
1515
package com.google.devtools.build.lib.vfs;
1616

17+
import com.google.devtools.build.lib.skyframe.serialization.EnumCodec;
18+
1719
/**
1820
* Indicates the file type at the other end of a symlink.
1921
*
@@ -27,5 +29,19 @@ public enum SymlinkTargetType {
2729
/** The target is a regular file. */
2830
FILE,
2931
/** The target is a directory. */
30-
DIRECTORY,
32+
DIRECTORY;
33+
34+
/**
35+
* Codec for {@link SymlinkTargetType}.
36+
*
37+
* <p>{@link com.google.devtools.build.lib.skyframe.serialization.AutoRegistry} excludes the
38+
* entire com.google.devtools.build.lib.vfs java package from having DynamicCodec support.
39+
* Therefore, we need to provide our own codec.
40+
*/
41+
@SuppressWarnings("unused") // found by CLASSPATH-scanning magic
42+
private static final class Codec extends EnumCodec<SymlinkTargetType> {
43+
Codec() {
44+
super(SymlinkTargetType.class);
45+
}
46+
}
3147
}

src/test/java/com/google/devtools/build/lib/analysis/starlark/UnresolvedSymlinkActionTest.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import com.google.devtools.build.lib.vfs.Path;
4141
import com.google.devtools.build.lib.vfs.PathFragment;
4242
import com.google.devtools.build.lib.vfs.Root;
43+
import com.google.devtools.build.lib.vfs.SymlinkTargetType;
4344
import com.google.devtools.build.lib.vfs.SyscallCache;
4445
import org.junit.Before;
4546
import org.junit.Test;
@@ -71,6 +72,7 @@ public final void setUp() throws Exception {
7172
NULL_ACTION_OWNER,
7273
outputArtifact,
7374
"../some/relative/path",
75+
SymlinkTargetType.UNSPECIFIED,
7476
"Creating unresolved symlink");
7577
}
7678

@@ -88,12 +90,48 @@ public void testOutputArtifactIsOutput() {
8890
public void testTargetAffectsKey() {
8991
UnresolvedSymlinkAction action1 =
9092
UnresolvedSymlinkAction.create(
91-
NULL_ACTION_OWNER, outputArtifact, "some/path", "Creating unresolved symlink");
93+
NULL_ACTION_OWNER,
94+
outputArtifact,
95+
"some/path",
96+
SymlinkTargetType.UNSPECIFIED,
97+
"Creating unresolved symlink");
9298
UnresolvedSymlinkAction action2 =
9399
UnresolvedSymlinkAction.create(
94-
NULL_ACTION_OWNER, outputArtifact, "some/other/path", "Creating unresolved symlink");
100+
NULL_ACTION_OWNER,
101+
outputArtifact,
102+
"some/other/path",
103+
SymlinkTargetType.UNSPECIFIED,
104+
"Creating unresolved symlink");
105+
106+
assertThat(computeKey(action1)).isNotEqualTo(computeKey(action2));
107+
}
108+
109+
@Test
110+
public void testTargetTypeAffectsKey() {
111+
UnresolvedSymlinkAction action1 =
112+
UnresolvedSymlinkAction.create(
113+
NULL_ACTION_OWNER,
114+
outputArtifact,
115+
"some/path",
116+
SymlinkTargetType.UNSPECIFIED,
117+
"Creating unresolved symlink");
118+
UnresolvedSymlinkAction action2 =
119+
UnresolvedSymlinkAction.create(
120+
NULL_ACTION_OWNER,
121+
outputArtifact,
122+
"some/path",
123+
SymlinkTargetType.FILE,
124+
"Creating unresolved symlink");
125+
UnresolvedSymlinkAction action3 =
126+
UnresolvedSymlinkAction.create(
127+
NULL_ACTION_OWNER,
128+
outputArtifact,
129+
"some/path",
130+
SymlinkTargetType.DIRECTORY,
131+
"Creating unresolved symlink");
95132

96133
assertThat(computeKey(action1)).isNotEqualTo(computeKey(action2));
134+
assertThat(computeKey(action2)).isNotEqualTo(computeKey(action3));
97135
}
98136

99137
@Test

src/test/java/com/google/devtools/build/lib/remote/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ java_library(
216216
"//src/main/java/com/google/devtools/build/lib/vfs",
217217
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
218218
"//src/test/java/com/google/devtools/build/lib/buildtool/util",
219+
"//src/test/java/com/google/devtools/build/lib/testutil:TestUtils",
219220
"//third_party:guava",
220221
"//third_party:junit4",
221222
"//third_party:truth",

src/test/java/com/google/devtools/build/lib/remote/BuildWithoutTheBytesIntegrationTestBase.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2043,7 +2043,11 @@ def _symlink_impl(ctx):
20432043
ctx.actions.symlink(output = link, target_file = ctx.file.target_artifact)
20442044
elif ctx.attr.target_path and not ctx.file.target_artifact:
20452045
link = ctx.actions.declare_symlink(ctx.attr.name)
2046-
ctx.actions.symlink(output = link, target_path = ctx.attr.target_path)
2046+
ctx.actions.symlink(
2047+
output = link,
2048+
target_path = ctx.attr.target_path,
2049+
target_type = ctx.attr.target_type or None,
2050+
)
20472051
else:
20482052
fail("exactly one of target_artifact or target_path must be set")
20492053
@@ -2054,6 +2058,7 @@ def _symlink_impl(ctx):
20542058
attrs = {
20552059
"target_artifact": attr.label(allow_single_file = True),
20562060
"target_path": attr.string(),
2061+
"target_type": attr.string(),
20572062
},
20582063
)
20592064
""");

0 commit comments

Comments
 (0)