blob: ea31a862c0786508d35ec4e8dc5622b07aed50c2 [file] [log] [blame]
// Copyright 2015 The Bazel Authors. 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.android;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSet;
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.io.OutputStream;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.logging.Logger;
/**
* Unpacks specially named tar files in a resource file tree.
*
* <p>Scans a list of Resource directories looking for "raw/blaze_internal_packed_resources.tar".
* When found, it is unpacked into a new resource directory.</p>
*/
// TODO(bazel-team): Remove when Android support library version is handled by configurable
// attribute.
class PackedResourceTarExpander implements DirectoryModifier {
private static final Logger LOGGER = Logger.getLogger(PackedResourceTarExpander.class.getName());
private static final class ConditionallyLinkingVisitor extends SimpleFileVisitor<Path> {
private final Path fileToexclude;
private Path out;
private Path workingDirectory;
private ConditionallyLinkingVisitor(Path fileToExclude, Path out, Path workingDirectory) {
this.fileToexclude = fileToExclude;
this.out = out;
this.workingDirectory = workingDirectory;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
Files.createDirectories(out.resolve(workingDirectory.relativize(dir)));
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
if (!fileToexclude.equals(file)) {
// TODO(bazel-team): Change to a symlink when the merge code supports symlinks.
Files.copy(file, out.resolve(workingDirectory.relativize(file)));
//Files.createSymbolicLink(out.resolve(workingDirectory.relativize(file)), file);
}
return super.visitFile(file, attrs);
}
}
private final Path out;
private Path workingDirectory;
public PackedResourceTarExpander(Path out, Path workingDirectory) {
this.out = out;
this.workingDirectory = workingDirectory;
}
@Override
public ImmutableList<Path> modify(ImmutableList<Path> resourceRoots) {
final Builder<Path> outDirs = ImmutableList.builder();
for (final Path unresolvedRoot : resourceRoots) {
Path root = unresolvedRoot.toAbsolutePath();
try {
final Path packedResources =
root.resolve("raw/blaze_internal_packed_resources.tar");
if (Files.exists(packedResources)) {
Preconditions.checkArgument(root.startsWith(workingDirectory),
"%s is not under %s", root, workingDirectory);
final Path resourcePrefix = workingDirectory.relativize(root);
final Path targetDirectory = out.resolve(resourcePrefix);
outDirs.add(targetDirectory);
copyRemainingResources(root, packedResources);
// Group the unpacked resource by the path they came from.
final Path tarOut =
out.resolve("blaze_internal_packed_resources").resolve(resourcePrefix);
unTarPackedResources(tarOut, packedResources);
outDirs.add(tarOut);
} else {
outDirs.add(root);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return outDirs.build();
}
private void unTarPackedResources(final Path tarOut, final Path packedResources)
throws IOException {
LOGGER.fine(String.format("Found packed resources: %s", packedResources));
try (InputStream inputStream = Files.newInputStream(packedResources);
TarArchiveInputStream tarStream = new TarArchiveInputStream(inputStream)) {
byte[] temp = new byte[4 * 1024];
for (TarArchiveEntry entry = tarStream.getNextTarEntry(); entry != null;
entry = tarStream.getNextTarEntry()) {
if (!entry.isFile()) {
continue;
}
int read = tarStream.read(temp);
// packed tars can start with a ./. This can cause issues, so remove it.
final Path entryPath = tarOut.resolve(entry.getName().replace("^\\./", ""));
Files.createDirectories(entryPath.getParent());
final OutputStream entryOutStream = Files.newOutputStream(entryPath);
while (read > -1) {
entryOutStream.write(temp, 0, read);
read = tarStream.read(temp);
}
entryOutStream.flush();
entryOutStream.close();
}
}
}
private void copyRemainingResources(final Path resourcePath, final Path packedResources)
throws IOException {
Files.walkFileTree(resourcePath, ImmutableSet.of(FileVisitOption.FOLLOW_LINKS),
Integer.MAX_VALUE, new ConditionallyLinkingVisitor(packedResources, out, workingDirectory));
}
}