download_and_extract: Show valid prefix suggestions in case of an invalid prefix.
This commit also generalizes exception handling for IOException from
every decompressor's `decompress` to the general `decompress`. Also, it
has introduced a new `IOException`: `CouldNotFindPrefixException` that
handles the suggestions and prints them to the user.
Fixes #7321,
Closes #7333.
PiperOrigin-RevId: 234103706
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/CompressedTarFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/CompressedTarFunction.java
index b3141c7..9a8bde0 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/CompressedTarFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/CompressedTarFunction.java
@@ -16,20 +16,18 @@
import com.google.common.base.Optional;
import com.google.devtools.build.lib.bazel.repository.DecompressorValue.Decompressor;
-import com.google.devtools.build.lib.rules.repository.RepositoryFunction.RepositoryFunctionException;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
-import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
-
-import java.util.Date;
-import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
-import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
-
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
/**
* Common code for unarchiving a compressed TAR file.
@@ -39,9 +37,10 @@
throws IOException;
@Override
- public Path decompress(DecompressorDescriptor descriptor) throws RepositoryFunctionException {
+ public Path decompress(DecompressorDescriptor descriptor) throws IOException {
Optional<String> prefix = descriptor.prefix();
boolean foundPrefix = false;
+ Set<String> availablePrefixes = new HashSet<>();
try (InputStream decompressorStream = getDecompressorStream(descriptor)) {
TarArchiveInputStream tarStream = new TarArchiveInputStream(decompressorStream);
@@ -49,6 +48,15 @@
while ((entry = tarStream.getNextTarEntry()) != null) {
StripPrefixedPath entryPath = StripPrefixedPath.maybeDeprefix(entry.getName(), prefix);
foundPrefix = foundPrefix || entryPath.foundPrefix();
+
+ if (prefix.isPresent() && !foundPrefix) {
+ Optional<String> suggestion =
+ CouldNotFindPrefixException.maybeMakePrefixSuggestion(entryPath.getPathFragment());
+ if (suggestion.isPresent()) {
+ availablePrefixes.add(suggestion.get());
+ }
+ }
+
if (entryPath.skip()) {
continue;
}
@@ -90,14 +98,10 @@
}
}
}
- } catch (IOException e) {
- throw new RepositoryFunctionException(e, Transience.TRANSIENT);
- }
- if (prefix.isPresent() && !foundPrefix) {
- throw new RepositoryFunctionException(
- new IOException("Prefix " + prefix.get() + " was given, but not found in the archive"),
- Transience.PERSISTENT);
+ if (prefix.isPresent() && !foundPrefix) {
+ throw new CouldNotFindPrefixException(prefix.get(), availablePrefixes);
+ }
}
return descriptor.repositoryPath();
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/DecompressorValue.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/DecompressorValue.java
index 0065eef..de0bea3 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/DecompressorValue.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/DecompressorValue.java
@@ -14,19 +14,64 @@
package com.google.devtools.build.lib.bazel.repository;
+import com.google.common.base.Optional;
import com.google.devtools.build.lib.rules.repository.RepositoryFunction.RepositoryFunctionException;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import com.google.devtools.build.skyframe.SkyValue;
+import java.io.IOException;
+import java.util.Set;
/**
* The contents of decompressed archive.
*/
public class DecompressorValue implements SkyValue {
+
/** Implementation of a decompression algorithm. */
public interface Decompressor {
- Path decompress(DecompressorDescriptor descriptor) throws RepositoryFunctionException;
+
+ /** Exception reporting about absence of an expected prefix in an archive. */
+ class CouldNotFindPrefixException extends IOException {
+
+ CouldNotFindPrefixException(String prefix, Set<String> availablePrefixes) {
+
+ super(CouldNotFindPrefixException.prepareErrorMessage(prefix, availablePrefixes));
+ }
+
+ private static String prepareErrorMessage(String prefix, Set<String> availablePrefixes) {
+ String error = "Prefix \"" + prefix + "\" was given, but not found in the archive. ";
+ String suggestion = "Here are possible prefixes for this archive: ";
+ String suggestionBody = "";
+
+ if (availablePrefixes.isEmpty()) {
+ suggestion =
+ "We could not find any directory in this archive"
+ + " (maybe there is no need for `strip_prefix`?)";
+ } else {
+ // Add a list of possible suggestion wrapped with `"` and separated by `, `.
+ suggestionBody = "\"" + String.join("\", \"", availablePrefixes) + "\".";
+ }
+
+ return error + suggestion + suggestionBody;
+ }
+
+ private static boolean isValidPrefixSuggestion(PathFragment pathFragment) {
+ return pathFragment.segmentCount() > 1;
+ }
+
+ public static Optional<String> maybeMakePrefixSuggestion(PathFragment pathFragment) {
+ if (isValidPrefixSuggestion(pathFragment)) {
+ return Optional.of(pathFragment.getSegment(0));
+ } else {
+ return Optional.absent();
+ }
+ }
+ }
+
+ Path decompress(DecompressorDescriptor descriptor)
+ throws IOException, RepositoryFunctionException;
}
private final Path directory;
@@ -76,7 +121,17 @@
}
public static Path decompress(DecompressorDescriptor descriptor)
- throws RepositoryFunctionException, InterruptedException {
- return descriptor.getDecompressor().decompress(descriptor);
+ throws RepositoryFunctionException {
+ try {
+ return descriptor.getDecompressor().decompress(descriptor);
+ } catch (IOException e) {
+ Path destinationDirectory = descriptor.archivePath().getParentDirectory();
+ throw new RepositoryFunctionException(
+ new IOException(
+ String.format(
+ "Error extracting %s to %s: %s",
+ descriptor.archivePath(), destinationDirectory, e.getMessage())),
+ Transience.TRANSIENT);
+ }
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/ZipDecompressor.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/ZipDecompressor.java
index 7af6b06..c023904 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/ZipDecompressor.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/ZipDecompressor.java
@@ -18,11 +18,9 @@
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.devtools.build.lib.bazel.repository.DecompressorValue.Decompressor;
-import com.google.devtools.build.lib.rules.repository.RepositoryFunction.RepositoryFunctionException;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
-import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import com.google.devtools.build.zip.ZipFileEntry;
import com.google.devtools.build.zip.ZipReader;
import java.io.File;
@@ -33,8 +31,9 @@
import java.nio.file.StandardCopyOption;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
-
+import java.util.Set;
import javax.annotation.Nullable;
/**
@@ -73,7 +72,7 @@
*/
@Override
@Nullable
- public Path decompress(DecompressorDescriptor descriptor) throws RepositoryFunctionException {
+ public Path decompress(DecompressorDescriptor descriptor) throws IOException {
Path destinationDirectory = descriptor.archivePath().getParentDirectory();
Optional<String> prefix = descriptor.prefix();
boolean foundPrefix = false;
@@ -92,17 +91,20 @@
for (Map.Entry<Path, PathFragment> symlink : symlinks.entrySet()) {
symlink.getKey().createSymbolicLink(symlink.getValue());
}
- } catch (IOException e) {
- throw new RepositoryFunctionException(new IOException(
- String.format("Error extracting %s to %s: %s",
- descriptor.archivePath(), destinationDirectory, e.getMessage())),
- Transience.TRANSIENT);
- }
- if (prefix.isPresent() && !foundPrefix) {
- throw new RepositoryFunctionException(
- new IOException("Prefix " + prefix.get() + " was given, but not found in the zip"),
- Transience.PERSISTENT);
+ if (prefix.isPresent() && !foundPrefix) {
+ Set<String> prefixes = new HashSet<>();
+ for (ZipFileEntry entry : entries) {
+ StripPrefixedPath entryPath =
+ StripPrefixedPath.maybeDeprefix(entry.getName(), Optional.absent());
+ Optional<String> suggestion =
+ CouldNotFindPrefixException.maybeMakePrefixSuggestion(entryPath.getPathFragment());
+ if (suggestion.isPresent()) {
+ prefixes.add(suggestion.get());
+ }
+ }
+ throw new CouldNotFindPrefixException(prefix.get(), prefixes);
+ }
}
return destinationDirectory;