diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/bazel/repository/DecompressorFactory.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/bazel/repository/DecompressorFactory.java | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/DecompressorFactory.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/DecompressorFactory.java new file mode 100644 index 0000000000..1076f245d6 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/DecompressorFactory.java @@ -0,0 +1,218 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.lib.bazel.repository; + +import com.google.devtools.build.lib.bazel.rules.workspace.HttpArchiveRule; +import com.google.devtools.build.lib.bazel.rules.workspace.HttpJarRule; +import com.google.devtools.build.lib.packages.Target; +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 org.apache.commons.compress.archivers.ArchiveException; +import org.apache.commons.compress.archivers.ArchiveInputStream; +import org.apache.commons.compress.archivers.ArchiveStreamFactory; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.utils.IOUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * Creates decompressors to use on archive. Use {@link DecompressorFactory#create} to get the + * correct type of decompressor for the input archive, then call + * {@link Decompressor#decompress} to decompress it. + */ +public abstract class DecompressorFactory { + + + public static Decompressor create(Target target, Path archivePath) + throws DecompressorException { + String baseName = archivePath.getBaseName(); + + if (target.getTargetKind().startsWith(HttpJarRule.NAME + " ")) { + if (baseName.endsWith(".jar")) { + return new JarDecompressor(target, archivePath); + } else { + throw new DecompressorException( + "Expected " + HttpJarRule.NAME + " " + target.getName() + + " to create file with a .jar suffix (got " + archivePath + ")"); + } + } + + if (target.getTargetKind().startsWith(HttpArchiveRule.NAME + " ")) { + if (baseName.endsWith(".zip") || baseName.endsWith(".jar")) { + return new ZipDecompressor(archivePath); + } else { + throw new DecompressorException( + "Expected " + HttpArchiveRule.NAME + " " + target.getName() + + " to create file with a .zip or .jar suffix (got " + archivePath + ")"); + } + } + + throw new DecompressorException( + "No decompressor found for " + target.getTargetKind() + " rule " + target.getName() + + " (got " + archivePath + ")"); + } + + /** + * General decompressor for an archive. Should be overridden for each specific archive type. + */ + public abstract static class Decompressor { + protected final Path archiveFile; + + private Decompressor(Path archiveFile) { + this.archiveFile = archiveFile; + } + + /** + * This is overridden by archive-specific decompression logic. Often this logic will create + * files and directories under the {@link Decompressor#archiveFile}'s parent directory. + * + * @return the path to the repository directory. That is, the returned path will be a directory + * containing a WORKSPACE file. + */ + public abstract Path decompress() throws DecompressorException; + } + + static class JarDecompressor extends Decompressor { + private final Target target; + + public JarDecompressor(Target target, Path archiveFile) { + super(archiveFile); + this.target = target; + } + + /** + * The .jar can be used compressed, so this just exposes it in a way Bazel can use. + * + * <p>It moves the jar from some-name/foo.jar to some-name/repository/jar/foo.jar and creates a + * BUILD file containing one entry: a .jar. + */ + @Override + public Path decompress() throws DecompressorException { + Path destinationDirectory = archiveFile.getParentDirectory().getRelative("repository"); + // Example: archiveFile is .external-repository/some-name/foo.jar. + String baseName = archiveFile.getBaseName(); + + try { + FileSystemUtils.createDirectoryAndParents(destinationDirectory); + // .external-repository/some-name/repository/WORKSPACE. + Path workspaceFile = destinationDirectory.getRelative("WORKSPACE"); + FileSystemUtils.writeContent(workspaceFile, Charset.forName("UTF-8"), + "# DO NOT EDIT: automatically generated WORKSPACE file for " + target.getTargetKind() + + " rule " + target.getName()); + // .external-repository/some-name/repository/jar. + Path jarDirectory = destinationDirectory.getRelative("jar"); + FileSystemUtils.createDirectoryAndParents(jarDirectory); + // .external-repository/some-name/repository/jar/foo.jar is a symbolic link to the jar in + // .external-repository/some-name. + Path jarSymlink = jarDirectory.getRelative(baseName); + if (!jarSymlink.exists()) { + jarSymlink.createSymbolicLink(archiveFile); + } + // .external-repository/some-name/repository/jar/BUILD defines the //jar target. + Path buildFile = jarDirectory.getRelative("BUILD"); + FileSystemUtils.writeLinesAs(buildFile, Charset.forName("UTF-8"), + "# DO NOT EDIT: automatically generated BUILD file for " + target.getTargetKind() + + " rule " + target.getName(), + "java_import(", + " name = 'jar',", + " jars = ['" + baseName + "'],", + " visibility = ['//visibility:public']", + ")"); + } catch (IOException e) { + throw new DecompressorException(e.getMessage()); + } + return destinationDirectory; + } + } + + private static class ZipDecompressor extends Decompressor { + public ZipDecompressor(Path archiveFile) { + super(archiveFile); + } + + /** + * This unzips the zip file to a sibling directory of {@link Decompressor#archiveFile}. The + * zip file is expected to have the WORKSPACE file at the top level, e.g.: + * + * <pre> + * $ unzip -lf some-repo.zip + * Archive: ../repo.zip + * Length Date Time Name + * --------- ---------- ----- ---- + * 0 2014-11-20 15:50 WORKSPACE + * 0 2014-11-20 16:10 foo/ + * 236 2014-11-20 15:52 foo/BUILD + * ... + * </pre> + */ + @Override + public Path decompress() throws DecompressorException { + Path destinationDirectory = archiveFile.getParentDirectory().getRelative("repository"); + try (InputStream is = new FileInputStream(archiveFile.getPathString())) { + ArchiveInputStream in = new ArchiveStreamFactory().createArchiveInputStream( + ArchiveStreamFactory.ZIP, is); + ZipArchiveEntry entry = (ZipArchiveEntry) in.getNextEntry(); + while (entry != null) { + extractZipEntry(in, entry, destinationDirectory); + entry = (ZipArchiveEntry) in.getNextEntry(); + } + } catch (IOException | ArchiveException e) { + throw new DecompressorException( + "Error extracting " + archiveFile + " to " + destinationDirectory + ": " + + e.getMessage()); + } + return destinationDirectory; + } + + private void extractZipEntry( + ArchiveInputStream in, ZipArchiveEntry entry, Path destinationDirectory) + throws IOException, DecompressorException { + PathFragment relativePath = new PathFragment(entry.getName()); + if (relativePath.isAbsolute()) { + throw new DecompressorException("Failed to extract " + relativePath + + ", zipped paths cannot be absolute"); + } + Path outputPath = destinationDirectory.getRelative(relativePath); + FileSystemUtils.createDirectoryAndParents(outputPath.getParentDirectory()); + if (entry.isDirectory()) { + FileSystemUtils.createDirectoryAndParents(outputPath); + } else { + try (OutputStream out = new FileOutputStream(new File(outputPath.getPathString()))) { + IOUtils.copy(in, out); + } catch (IOException e) { + throw new DecompressorException("Error writing " + outputPath + " from " + + archiveFile); + } + } + } + } + + /** + * Exceptions thrown when something goes wrong decompressing an archive. + */ + public static class DecompressorException extends Exception { + public DecompressorException(String message) { + super(message); + } + } +}
\ No newline at end of file |