| /* |
| * Copyright 2012-present Facebook, Inc. |
| * |
| * 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.facebook.buck.zip; |
| |
| import static com.facebook.buck.zip.ZipOutputStreams.HandleDuplicates.OVERWRITE_EXISTING; |
| import static java.util.logging.Level.SEVERE; |
| |
| import com.facebook.buck.event.LogEvent; |
| import com.facebook.buck.step.ExecutionContext; |
| import com.facebook.buck.step.Step; |
| import com.facebook.buck.util.DirectoryTraversal; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableSet; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.util.Set; |
| |
| /** |
| * A {@link com.facebook.buck.step.Step} that creates a ZIP archive.. |
| */ |
| public class ZipStep implements Step { |
| |
| public static final int MIN_COMPRESSION_LEVEL = 0; |
| public static final int DEFAULT_COMPRESSION_LEVEL = 6; |
| public static final int MAX_COMPRESSION_LEVEL = 9; |
| |
| private final String absolutePathToZipFile; |
| private final ImmutableSet<String> paths; |
| private final boolean junkPaths; |
| private final int compressionLevel; |
| private final File baseDir; |
| |
| |
| /** |
| * Create a {@link ZipStep} to create or update a zip archive. |
| * |
| * Note that paths added to the archive are always relative to the working directory.<br/> |
| * For example, if you're in {@code /dir} and you add {@code file.txt}, you get |
| * an archive containing just the file. If you were in {@code /} and added |
| * {@code dir/file.txt}, you would get an archive containing the file within a directory. |
| * |
| * |
| * @param absolutePathToZipFile path to archive to create. |
| * @param paths a set of files and/or directories to work on. The entire working directory is |
| * assumed if this set is empty. |
| * @param junkPaths if {@code true}, the relative paths of added archive entries are discarded, |
| * i.e. they are all placed in the root of the archive. |
| * @param compressionLevel between 0 (store) and 9. |
| * @param baseDir working directory for {@code zip} command. |
| * If {@code null}, project directory root is used instead. |
| */ |
| public ZipStep( |
| String absolutePathToZipFile, |
| Set<String> paths, |
| boolean junkPaths, |
| int compressionLevel, |
| File baseDir) { |
| Preconditions.checkArgument(compressionLevel >= MIN_COMPRESSION_LEVEL && |
| compressionLevel <= MAX_COMPRESSION_LEVEL, "compressionLevel out of bounds."); |
| this.absolutePathToZipFile = Preconditions.checkNotNull(absolutePathToZipFile); |
| this.paths = ImmutableSet.copyOf(Preconditions.checkNotNull(paths)); |
| this.junkPaths = junkPaths; |
| this.compressionLevel = compressionLevel; |
| this.baseDir = Preconditions.checkNotNull(baseDir); |
| } |
| |
| |
| @Override |
| public int execute(ExecutionContext context) { |
| File original = new File(absolutePathToZipFile); |
| if (original.exists()) { |
| context.getBuckEventBus().post( |
| LogEvent.create(SEVERE, "Attempting to overwrite an existing zip: %s", original) |
| ); |
| return 1; |
| } |
| |
| try ( |
| BufferedOutputStream baseOut = new BufferedOutputStream(new FileOutputStream(absolutePathToZipFile)); |
| CustomZipOutputStream out = ZipOutputStreams.newOutputStream(baseOut, OVERWRITE_EXISTING); |
| ) { |
| DirectoryTraversal traversal = new DirectoryTraversal(baseDir) { |
| |
| @Override |
| public void visit(File file, String relativePath) throws IOException { |
| if (!paths.isEmpty() && !paths.contains(relativePath)) { |
| return; |
| } |
| |
| String name = junkPaths ? file.getName() : relativePath; |
| if (file.isDirectory()) { |
| // Lame. |
| name += "/"; |
| } |
| |
| CustomZipEntry entry = new CustomZipEntry(name); |
| entry.setTime(file.lastModified()); |
| entry.setSize(file.length()); |
| entry.setCompressionLevel(compressionLevel); |
| |
| out.putNextEntry(entry); |
| Files.copy(file.toPath(), out); |
| out.closeEntry(); |
| } |
| }; |
| |
| traversal.traverse(); |
| |
| return 0; |
| } catch (IOException e) { |
| return 1; |
| } |
| |
| } |
| |
| @Override |
| public String getDescription(ExecutionContext context) { |
| StringBuilder args = new StringBuilder("zip "); |
| |
| // Don't add extra fields, neither do the Android tools. |
| args.append("-X "); |
| |
| // recurse |
| args.append("-r "); |
| |
| // compression level |
| args.append("-").append(compressionLevel).append(" "); |
| |
| // unk paths |
| if (junkPaths) { |
| args.append("-j "); |
| } |
| |
| // destination archive |
| args.append(absolutePathToZipFile).append(" "); |
| |
| // files to add to archive |
| if (paths.isEmpty()) { |
| // Add the contents of workingDirectory to archive. |
| args.append("-i* "); |
| args.append(". "); |
| } else { |
| // Add specified paths, relative to workingDirectory. |
| for (String path : paths) { |
| args.append(path).append(" "); |
| } |
| } |
| |
| return args.toString(); |
| } |
| |
| @Override |
| public String getShortName() { |
| return "zip"; |
| } |
| |
| } |