Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@
* formats while all formats that collect multiple entries inside a single (potentially compressed)
* archive are archiver formats. This class handles the former, compressor formats.
*
* <p>It ignores the {@link DecompressorDescriptor#prefix()} setting because compressed files cannot
* contain directories.
* <p>It ignores the {@link DecompressorDescriptor#prefix()} and {@link
* DecompressorDescriptor#stripComponents()} setting because compressed files cannot contain
* directories.
*/
public abstract class CompressedFunction implements Decompressor {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ public Path decompress(DecompressorDescriptor descriptor)
"Failed to extract %s, tarred paths cannot be absolute", strippedRelativePath));
}

strippedRelativePath = strippedRelativePath.stripComponents(descriptor.stripComponents());
if (strippedRelativePath == PathFragment.EMPTY_FRAGMENT) {
continue;
}

Path filePath = descriptor.destinationPath().getRelative(strippedRelativePath);
if (!filePath.startsWith(descriptor.destinationPath())) {
throw new IOException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public record DecompressorDescriptor(
Path archivePath,
Path destinationPath,
Optional<String> prefix,
int stripComponents,
ImmutableMap<String, String> renameFiles) {
public DecompressorDescriptor {
requireNonNull(context, "context");
Expand All @@ -45,6 +46,7 @@ public record DecompressorDescriptor(
public static Builder builder() {
return new AutoBuilder_DecompressorDescriptor_Builder()
.setContext("")
.setStripComponents(0)
.setRenameFiles(ImmutableMap.of());
}

Expand All @@ -60,8 +62,22 @@ public abstract static class Builder {

public abstract Builder setPrefix(String prefix);

public abstract Builder setStripComponents(int stripComponents);

public abstract Builder setRenameFiles(Map<String, String> renameFiles);

public abstract DecompressorDescriptor build();
public abstract DecompressorDescriptor autoBuild();

public DecompressorDescriptor build() {
DecompressorDescriptor d = autoBuild();
if (d.stripComponents() < 0) {
throw new IllegalArgumentException("'strip_components' must be non-negative");
}
if (d.stripComponents() != 0 && d.prefix().isPresent() && !d.prefix().get().isEmpty()) {
throw new IllegalArgumentException(
"Only one of 'strip_prefix' or 'strip_components' can be set");
}
return d;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@ public Path decompress(DecompressorDescriptor descriptor)
if (entryPath.skip()) {
continue;
}
extract7zEntry(sevenZFile, entry, destinationDirectory, entryPath.getPathFragment());
PathFragment pathFragment =
entryPath.getPathFragment().stripComponents(descriptor.stripComponents());
if (pathFragment == PathFragment.EMPTY_FRAGMENT) {
continue;
}
extract7zEntry(sevenZFile, entry, destinationDirectory, pathFragment);
}

if (prefix.isPresent() && !foundPrefix) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,13 @@ public Path decompress(DecompressorDescriptor descriptor)
if (entryPath.skip()) {
continue;
}
PathFragment pathFragment =
entryPath.getPathFragment().stripComponents(descriptor.stripComponents());
if (pathFragment == PathFragment.EMPTY_FRAGMENT) {
continue;
}
extractZipEntry(
reader, entry, destinationDirectory, entryPath.getPathFragment(), prefix, symlinks);
reader, entry, destinationDirectory, pathFragment, prefix, symlinks);
}

if (prefix.isPresent() && !foundPrefix) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,8 @@ field is for debugging purposes only and should not be relied upon as a stable A
be used to strip it from extracted files.

<p>For compatibility, this parameter may also be used under the deprecated name
<code>stripPrefix</code>.
<code>stripPrefix</code>. Only one of <code>strip_prefix</code> or
<code>strip_components</code> can be used.
"""),
@Param(
name = "allow_fail",
Expand Down Expand Up @@ -1043,6 +1044,16 @@ field is for debugging purposes only and should not be relied upon as a stable A
positional = false,
named = true,
defaultValue = "''"),
@Param(
name = "strip_components",
positional = false,
named = true,
defaultValue = "0",
doc =
"""
Strip the given number of leading components from file paths on extraction. Only one of
<code>strip_components</code> or <code>strip_prefix</code> can be used.
"""),
})
public StructImpl downloadAndExtract(
Object url,
Expand All @@ -1057,9 +1068,12 @@ public StructImpl downloadAndExtract(
String integrity,
Dict<?, ?> renameFiles, // <String, String> expected
String oldStripPrefix,
StarlarkInt stripComponentsI,
StarlarkThread thread)
throws RepositoryFunctionException, InterruptedException, EvalException {
stripPrefix = renamedStripPrefix("download_and_extract", stripPrefix, oldStripPrefix);
int stripComponents = Starlark.toInt(stripComponentsI, "strip_components");
validateStripping("download_and_extract", stripPrefix, stripComponents);
ImmutableMap<URI, Map<String, List<String>>> authHeaders =
getAuthHeaders(getAuthContents(authUnchecked, "auth"));

Expand Down Expand Up @@ -1162,6 +1176,7 @@ public StructImpl downloadAndExtract(
.setArchivePath(downloadedPath)
.setDestinationPath(outputPath.getPath())
.setPrefix(stripPrefix)
.setStripComponents(stripComponents)
.setRenameFiles(renameFilesMap)
.build(),
// Type does NOT need to be passed here, as the existing code renames the archive path to
Expand Down Expand Up @@ -1260,7 +1275,8 @@ private static void deleteTreeWithRetries(Path downloadDirectory)
used to strip it from extracted files.

<p>For compatibility, this parameter may also be used under the deprecated name
<code>stripPrefix</code>.
<code>stripPrefix</code>. Only one of <code>strip_prefix</code> or
<code>strip_components</code> can be set.
"""),
@Param(
name = "rename_files",
Expand Down Expand Up @@ -1291,6 +1307,16 @@ private static void deleteTreeWithRetries(Path downloadDirectory)
positional = false,
named = true,
defaultValue = "''"),
@Param(
name = "strip_components",
positional = false,
named = true,
defaultValue = "0",
doc =
"""
Strip the given number of leading components from file paths on extraction. Only one of
<code>strip_components</code> or <code>strip_prefix</code> can be set.
"""),
@Param(
name = "type",
defaultValue = "''",
Expand All @@ -1314,10 +1340,13 @@ public void extract(
Dict<?, ?> renameFiles, // <String, String> expected
String watchArchive,
String oldStripPrefix,
StarlarkInt stripComponentsI,
String type,
StarlarkThread thread)
throws RepositoryFunctionException, InterruptedException, EvalException {
stripPrefix = renamedStripPrefix("extract", stripPrefix, oldStripPrefix);
int stripComponents = Starlark.toInt(stripComponentsI, "strip_components");
validateStripping("extract", stripPrefix, stripComponents);
StarlarkPath archivePath = getPath(archive);

if (!archivePath.exists()) {
Expand Down Expand Up @@ -1355,6 +1384,7 @@ public void extract(
.setArchivePath(archivePath.getPath())
.setDestinationPath(outputPath.getPath())
.setPrefix(stripPrefix)
.setStripComponents(stripComponents)
.setRenameFiles(renameFilesMap)
.build(),
Optional.ofNullable(type).filter(s -> !s.isBlank()));
Expand Down Expand Up @@ -1409,6 +1439,21 @@ private static String renamedStripPrefix(String method, String stripPrefix, Stri
method);
}

private static void validateStripping(String method, String stripPrefix, int stripComponents)
throws EvalException {
if (stripComponents < 0) {
throw Starlark.errorf(
"%s() has an invalid argument for 'strip_components': %d. Must be non-negative.",
method, stripComponents);
}

if (!stripPrefix.isEmpty() && stripComponents > 0) {
throw Starlark.errorf(
"%s() got multiple strip values. Only one of 'strip_prefix' or 'strip_components' can be set",
method);
}
}

@StarlarkMethod(
name = "file",
doc = "Generates a file in the repository directory with the provided content.",
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/google/devtools/build/lib/vfs/PathFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,21 @@ private PathFragment subFragmentImpl(int beginIndex, int endIndex) {
return makePathFragment(normalizedPath.substring(starti, endi), driveStrLength);
}

/** Strip <code>numComponents</code> leading components from file names on extraction. */
public PathFragment stripComponents(int numComponents) {
if (numComponents == 0) {
return this;
}
if (numComponents < 0) {
throw new IllegalArgumentException(
String.format("Invalid number of components (%d)", numComponents));
}
if (numComponents >= this.segmentCount()) {
return EMPTY_FRAGMENT;
}
return this.subFragment(numComponents);
}

/**
* Returns an {@link Iterable} that lazily yields the segments of this path.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ public void testDecompressWithPrefix() throws Exception {
archiveDescriptor.assertOutputFiles(outputDir, INNER_FOLDER_NAME);
}

/**
* Test decompressing a tar.gz file with hard link file and symbolic link file inside and
* stripping the first component.
*/
@Test
public void testDecompressWithStripComponents() throws Exception {
DecompressorDescriptor.Builder descriptorBuilder =
archiveDescriptor.createDescriptorBuilder().setStripComponents(1);
Path outputDir = decompress(descriptorBuilder.build());

archiveDescriptor.assertOutputFiles(outputDir, INNER_FOLDER_NAME);
}

/**
* Test decompressing a tar.gz file, with some entries being renamed during the extraction
* process.
Expand Down Expand Up @@ -116,6 +129,24 @@ public void testDecompressWithRenamedFilesAndPrefix() throws Exception {
assertThat(innerDir.getRelative("renamedFile").exists()).isTrue();
}

/** Test that entry renaming is applied prior to component stripping. */
@Test
public void testDecompressWithRenamedFilesAndStripComponents() throws Exception {
String innerDirName = ROOT_FOLDER_NAME + "/" + INNER_FOLDER_NAME;

HashMap<String, String> renameFiles = new HashMap<>();
renameFiles.put(innerDirName + "/hardLinkFile", innerDirName + "/renamedFile");
DecompressorDescriptor.Builder descriptorBuilder =
archiveDescriptor
.createDescriptorBuilder()
.setStripComponents(1)
.setRenameFiles(renameFiles);
Path outputDir = decompress(descriptorBuilder.build());

Path innerDir = outputDir.getRelative(INNER_FOLDER_NAME);
assertThat(innerDir.getRelative("renamedFile").exists()).isTrue();
}

private Path decompress(DecompressorDescriptor descriptor) throws Exception {
return new CompressedTarFunction() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ public void testDecompressWithPrefix() throws Exception {
assertThat(files).contains(REGULAR_FILENAME);
}

/** Test decompressing a .7z file and stripping components. */
@Test
public void testDecompressWithStripComponents() throws Exception {
DecompressorDescriptor.Builder descriptorBuilder =
archiveDescriptor().createDescriptorBuilder().setStripComponents(1);
Path outputDir = decompress(descriptorBuilder.build());
Path fileDir = outputDir.getRelative(INNER_FOLDER_NAME);

ImmutableList<String> files =
fileDir.readdir(Symlinks.NOFOLLOW).stream().map(Dirent::getName).collect(toImmutableList());
assertThat(files).contains(REGULAR_FILENAME);
}

/** Test decompressing a .7z with entries being renamed during the extraction process. */
@Test
public void testDecompressWithRenamedFiles() throws Exception {
Expand Down Expand Up @@ -136,6 +149,36 @@ public void testDecompressWithRenamedFilesAndPrefix() throws Exception {
assertThat(fileDir.getRelative("renamedFile").getFileSize()).isNotEqualTo(0);
}

/** Test that entry renaming is applied prior to stripping components. */
@Test
public void testDecompressWithRenamedFilesAndStripComponents() throws Exception {
String innerDirName = ROOT_FOLDER_NAME + "/" + INNER_FOLDER_NAME;

HashMap<String, String> renameFiles = new HashMap<>();
renameFiles.put(innerDirName + "/" + REGULAR_FILENAME, innerDirName + "/renamedFile");
DecompressorDescriptor.Builder descriptorBuilder =
archiveDescriptor()
.createDescriptorBuilder()
.setStripComponents(1)
.setRenameFiles(renameFiles);
Path outputDir = decompress(descriptorBuilder.build());

Path fileDir = outputDir.getRelative(INNER_FOLDER_NAME);
ImmutableList<String> files =
fileDir.readdir(Symlinks.NOFOLLOW).stream().map(Dirent::getName).collect(toImmutableList());
assertThat(files).contains("renamedFile");
assertThat(fileDir.getRelative("renamedFile").getFileSize()).isNotEqualTo(0);
}

/** Test decompressing a .7z file where everything is stripped */
@Test
public void testDecompressStripAllComponents() throws Exception {
Path outputDir =
decompress(archiveDescriptor().createDescriptorBuilder().setStripComponents(1_000).build());

assertThat(outputDir.exists()).isFalse();
}

private File archiveDir;
private File extractionDir;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void testDecompressWithoutPrefix() throws Exception {
}

/**
* Test decompressing a tar.gz file with hard link file and symbolic link file inside and
* Test decompressing a zip file with hard link file and symbolic link file inside and
* stripping a prefix
*/
@Test
Expand All @@ -93,6 +93,20 @@ public void testDecompressWithPrefix() throws Exception {
archiveDescriptor.assertOutputFiles(outputDir, INNER_FOLDER_NAME);
}

/**
* Test decompressing a zip file with hard link file and symbolic link file inside and
* stripping a component
*/
@Test
public void testDecompressWithStripComponents() throws Exception {
TestArchiveDescriptor archiveDescriptor = new TestArchiveDescriptor(ARCHIVE_NAME, "out", false);
DecompressorDescriptor.Builder descriptorBuilder =
archiveDescriptor.createDescriptorBuilder().setStripComponents(1);
Path outputDir = decompress(descriptorBuilder.build());

archiveDescriptor.assertOutputFiles(outputDir, INNER_FOLDER_NAME);
}

/**
* Test decompressing a zip file, with some entries being renamed during the extraction process.
*/
Expand Down Expand Up @@ -130,6 +144,25 @@ public void testDecompressWithRenamedFilesAndPrefix() throws Exception {
assertThat(innerDir.getRelative("renamedFile").exists()).isTrue();
}

/** Test that entry renaming is applied prior to stripping components. */
@Test
public void testDecompressWithRenamedFilesAndStripComponents() throws Exception {
TestArchiveDescriptor archiveDescriptor = new TestArchiveDescriptor(ARCHIVE_NAME, "out", false);
String innerDirName = ROOT_FOLDER_NAME + "/" + INNER_FOLDER_NAME;

HashMap<String, String> renameFiles = new HashMap<>();
renameFiles.put(innerDirName + "/hardLinkFile", innerDirName + "/renamedFile");
DecompressorDescriptor.Builder descriptorBuilder =
archiveDescriptor
.createDescriptorBuilder()
.setStripComponents(1)
.setRenameFiles(renameFiles);
Path outputDir = decompress(descriptorBuilder.build());

Path innerDir = outputDir.getRelative(INNER_FOLDER_NAME);
assertThat(innerDir.getRelative("renamedFile").exists()).isTrue();
}

private Path decompress(DecompressorDescriptor descriptor) throws Exception {
return ZipDecompressor.INSTANCE.decompress(descriptor);
}
Expand Down
Loading
Loading