Skip to content

Commit

Permalink
Add repository_ctx.rename() for renaming files or directories
Browse files Browse the repository at this point in the history
This allows the directory structure of third-party archives to be rearranged without platform-specific commands, and without requiring symlink support from the platform.

Closes #24020.

PiperOrigin-RevId: 694246834
Change-Id: I4831d610c5fb841640af1b1b864172ee38319a6b
  • Loading branch information
jmillikin authored and copybara-github committed Nov 7, 2024
1 parent e89be2f commit 467ea0c
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.google.devtools.build.lib.bazel.debug.proto.WorkspaceLogProtos.ExtractEvent;
import com.google.devtools.build.lib.bazel.debug.proto.WorkspaceLogProtos.FileEvent;
import com.google.devtools.build.lib.bazel.debug.proto.WorkspaceLogProtos.OsEvent;
import com.google.devtools.build.lib.bazel.debug.proto.WorkspaceLogProtos.RenameEvent;
import com.google.devtools.build.lib.bazel.debug.proto.WorkspaceLogProtos.SymlinkEvent;
import com.google.devtools.build.lib.bazel.debug.proto.WorkspaceLogProtos.TemplateEvent;
import com.google.devtools.build.lib.bazel.debug.proto.WorkspaceLogProtos.WhichEvent;
Expand Down Expand Up @@ -261,6 +262,23 @@ public static WorkspaceRuleEvent newOsEvent(String context, Location location) {
return new WorkspaceRuleEvent(result.build());
}

/** Creates a new WorkspaceRuleEvent for a rename event. */
public static WorkspaceRuleEvent newRenameEvent(
String src, String dst, String context, Location location) {
RenameEvent e = WorkspaceLogProtos.RenameEvent.newBuilder().setSrc(src).setDst(dst).build();

WorkspaceLogProtos.WorkspaceEvent.Builder result =
WorkspaceLogProtos.WorkspaceEvent.newBuilder();
result = result.setRenameEvent(e);
if (location != null) {
result = result.setLocation(location.toString());
}
if (context != null) {
result = result.setContext(context);
}
return new WorkspaceRuleEvent(result.build());
}

/** Creates a new WorkspaceRuleEvent for a symlink event. */
public static WorkspaceRuleEvent newSymlinkEvent(
String from, String to, String context, Location location) {
Expand Down Expand Up @@ -324,7 +342,7 @@ public static WorkspaceRuleEvent newWhichEvent(
return new WorkspaceRuleEvent(result.build());
}

/*
/**
* @return a message to log for this event
*/
public String logMessage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ message OsEvent {
// Takes no inputs
}

// Information on "rename" event in repository_ctx.
message RenameEvent {
// The path of the existing file or directory to rename.
string src = 1;
// The new name of the file or directory.
string dst = 2;
}

// Information on "symlink" event in repository_ctx.
message SymlinkEvent {
// path to which the symlink will point to
Expand Down Expand Up @@ -162,5 +170,6 @@ message WorkspaceEvent {
ReadEvent read_event = 12;
DeleteEvent delete_event = 13;
PatchEvent patch_event = 14;
RenameEvent rename_event = 15;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,74 @@ public boolean delete(Object pathObject, StarlarkThread thread)
}
}

@StarlarkMethod(
name = "rename",
doc =
"""
Renames the file or directory from <code>src</code> to <code>dst</code>. \
Parent directories are created as needed. Fails if the destination path
already exists. Both paths must be located within the repository.
""",
useStarlarkThread = true,
parameters = {
@Param(
name = "src",
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = Label.class),
@ParamType(type = StarlarkPath.class)
},
doc =
"""
The path of the existing file or directory to rename, relative
to the repository directory.
"""),
@Param(
name = "dst",
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = Label.class),
@ParamType(type = StarlarkPath.class)
},
doc =
"""
The new name to which the file or directory will be renamed to,
relative to the repository directory.
"""),
})
public void rename(Object srcName, Object dstName, StarlarkThread thread)
throws RepositoryFunctionException, EvalException, InterruptedException {
StarlarkPath srcPath = getPath(srcName);
StarlarkPath dstPath = getPath(dstName);
WorkspaceRuleEvent w =
WorkspaceRuleEvent.newRenameEvent(
srcPath.toString(),
dstPath.toString(),
identifyingStringForLogging,
thread.getCallerLocation());
env.getListener().post(w);
try {
checkInOutputDirectory("write", srcPath);
checkInOutputDirectory("write", dstPath);
if (dstPath.exists()) {
throw new RepositoryFunctionException(
new IOException("Could not rename " + srcPath + " to " + dstPath + ": already exists"),
Transience.TRANSIENT);
}
makeDirectories(dstPath.getPath());
srcPath.getPath().renameTo(dstPath.getPath());
} catch (IOException e) {
throw new RepositoryFunctionException(
new IOException(
"Could not rename " + srcPath + " to " + dstPath + ": " + e.getMessage(), e),
Transience.TRANSIENT);
} catch (InvalidPathException e) {
throw new RepositoryFunctionException(
Starlark.errorf("Could not rename %s to %s: %s", srcPath, dstPath, e.getMessage()),
Transience.PERSISTENT);
}
}

@StarlarkMethod(
name = "patch",
doc =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,63 @@ public void testRemoteExec() throws Exception {
assertThat(starlarkExecutionResult.getStderr()).isEqualTo("test-stderr");
}

@Test
public void testRename() throws Exception {
setUpContextForRule("test");
context.createFile(context.getPath("foo"), "foobar", true, true, thread);

context.rename(context.getPath("foo"), context.getPath("bar/baz"), thread);
testOutputFile(outputDirectory.getChild("bar").getChild("baz"), "foobar");

try {
context.rename(context.getPath("/foo"), context.getPath("bar"), thread);
fail("Expected error on renaming path outside of the repository directory");
} catch (RepositoryFunctionException ex) {
assertThat(ex)
.hasCauseThat()
.hasMessageThat()
.isEqualTo("Cannot write outside of the repository directory for path /foo");
}

try {
context.rename(context.getPath("foo"), context.getPath("/bar"), thread);
fail("Expected error on renaming path outside of the repository directory");
} catch (RepositoryFunctionException ex) {
assertThat(ex)
.hasCauseThat()
.hasMessageThat()
.isEqualTo("Cannot write outside of the repository directory for path /bar");
}
}

@Test
public void testRenameConflict() throws Exception {
setUpContextForRule("test");
context.createFile(context.getPath("foo"), "fooooo", true, true, thread);
context.createFile(context.getPath("bar"), "baaaar", true, true, thread);
context.createFile(context.getPath("baz.d/baz"), "baaaaz", true, true, thread);

try {
context.rename(context.getPath("foo"), context.getPath("bar"), thread);
fail("Expected error on renaming to a path that already exists");
} catch (RepositoryFunctionException ex) {
assertThat(ex)
.hasCauseThat()
.hasMessageThat()
.isEqualTo("Could not rename /outputDir/foo to /outputDir/bar: already exists");
}

try {
context.rename(context.getPath("foo"), context.getPath("baz.d"), thread);
fail("Expected error on renaming to a path that already exists");
} catch (RepositoryFunctionException ex) {
assertThat(ex)
.hasCauseThat()
.hasMessageThat()
.isEqualTo("Could not rename /outputDir/foo to /outputDir/baz.d: already exists");
}
}

@Test
public void testSymlink() throws Exception {
setUpContextForRule("test");
Expand Down

0 comments on commit 467ea0c

Please sign in to comment.