com.google.devtools.build.android.PackedResourceTarExpander.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.android.PackedResourceTarExpander.java

Source

// 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.base.Throwables;
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) {
                Throwables.propagate(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));
    }
}