com.facebook.buck.rules.BuildInfoRecorder.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.rules.BuildInfoRecorder.java

Source

/*
 * Copyright 2013-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.rules;

import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.LogEvent;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.util.DefaultDirectoryTraverser;
import com.facebook.buck.util.DirectoryTraversal;
import com.facebook.buck.util.DirectoryTraverser;
import com.facebook.buck.util.ProjectFilesystem;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonArray;
import com.google.gson.JsonPrimitive;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;

/**
 * Utility for recording the paths to the output files generated by a build rule, as well as any
 * metadata about those output files. This data will be packaged up into an artifact that will be
 * stored in the cache. The metadata will also be written to disk so it can be read on a subsequent
 * build by an {@link OnDiskBuildInfo}.
 */
public class BuildInfoRecorder {

    private static final DirectoryTraverser DEFAULT_DIRECTORY_TRAVERSER = new DefaultDirectoryTraverser();

    private final BuildTarget buildTarget;
    private final Path pathToMetadataDirectory;
    private final ProjectFilesystem projectFilesystem;
    private final Map<String, String> metadataToWrite;
    private final RuleKey ruleKey;

    /**
     * Every value in this set is a path relative to the project root.
     */
    private final Set<Path> pathsToOutputFiles;

    private final Set<Path> pathsToOutputDirectories;
    private final DirectoryTraverser directoryTraverser;

    BuildInfoRecorder(BuildTarget buildTarget, ProjectFilesystem projectFilesystem, RuleKey ruleKey,
            RuleKey rukeKeyWithoutDeps) {
        this(buildTarget, projectFilesystem, ruleKey, rukeKeyWithoutDeps, DEFAULT_DIRECTORY_TRAVERSER);
    }

    BuildInfoRecorder(BuildTarget buildTarget, ProjectFilesystem projectFilesystem, RuleKey ruleKey,
            RuleKey rukeKeyWithoutDeps, DirectoryTraverser directoryTraverser) {
        this.buildTarget = Preconditions.checkNotNull(buildTarget);
        this.pathToMetadataDirectory = BuildInfo.getPathToMetadataDirectory(buildTarget);
        this.projectFilesystem = Preconditions.checkNotNull(projectFilesystem);
        this.metadataToWrite = Maps.newHashMap();

        metadataToWrite.put(BuildInfo.METADATA_KEY_FOR_RULE_KEY, Preconditions.checkNotNull(ruleKey).toString());
        metadataToWrite.put(BuildInfo.METADATA_KEY_FOR_RULE_KEY_WITHOUT_DEPS,
                Preconditions.checkNotNull(rukeKeyWithoutDeps).toString());
        this.ruleKey = ruleKey;
        this.pathsToOutputFiles = Sets.newHashSet();
        this.pathsToOutputDirectories = Sets.newHashSet();
        this.directoryTraverser = directoryTraverser;
    }

    /**
     * Writes the metadata currently stored in memory to the directory returned by
     * {@link BuildInfo#getPathToMetadataDirectory(BuildTarget)}.
     */
    public void writeMetadataToDisk(boolean clearExistingMetadata) throws IOException {
        if (clearExistingMetadata) {
            projectFilesystem.rmdir(pathToMetadataDirectory);
        }
        projectFilesystem.mkdirs(pathToMetadataDirectory);

        for (Map.Entry<String, String> entry : metadataToWrite.entrySet()) {
            projectFilesystem.writeContentsToPath(entry.getValue(),
                    pathToMetadataDirectory.resolve(entry.getKey()));
        }
    }

    /**
     * This key/value pair is stored in memory until {@link #writeMetadataToDisk(boolean)} is invoked.
     */
    public void addMetadata(String key, String value) {
        metadataToWrite.put(Preconditions.checkNotNull(key), Preconditions.checkNotNull(value));
    }

    public void addMetadata(String key, Iterable<String> value) {
        JsonArray values = new JsonArray();
        for (String str : value) {
            values.add(new JsonPrimitive(str));
        }
        addMetadata(key, values.toString());
    }

    /**
     * Creates a zip file of the metadata and recorded artifacts and stores it in the artifact cache.
     */
    public void performUploadToArtifactCache(ArtifactCache artifactCache, BuckEventBus eventBus) {
        // Skip all of this if caching is disabled. Although artifactCache.store() will be a noop,
        // building up the zip is wasted I/O.
        if (!artifactCache.isStoreSupported()) {
            return;
        }

        ImmutableSet.Builder<Path> pathsToIncludeInZipBuilder = ImmutableSet.<Path>builder()
                .addAll(Iterables.transform(metadataToWrite.keySet(), new Function<String, Path>() {
                    @Override
                    public Path apply(String key) {
                        return pathToMetadataDirectory.resolve(key);
                    }
                })).addAll(pathsToOutputFiles);

        try {
            for (Path outputDirectory : pathsToOutputDirectories) {
                pathsToIncludeInZipBuilder.addAll(getEntries(outputDirectory));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        ImmutableSet<Path> pathsToIncludeInZip = pathsToIncludeInZipBuilder.build();
        File zip;
        try {
            zip = File.createTempFile(buildTarget.getFullyQualifiedName().replace('/', '_'), ".zip");
            projectFilesystem.createZip(pathsToIncludeInZip, zip);
        } catch (IOException e) {
            eventBus.post(LogEvent.info("Failed to create zip for %s containing:\n%s", buildTarget,
                    Joiner.on('\n').join(ImmutableSortedSet.copyOf(pathsToIncludeInZip))));
            e.printStackTrace();
            return;
        }
        artifactCache.store(ruleKey, zip);
        zip.delete();
    }

    private List<Path> getEntries(final Path outputDirectory) throws IOException {
        final ImmutableList.Builder<Path> entries = ImmutableList.builder();
        DirectoryTraversal traversal = new DirectoryTraversal(
                projectFilesystem.getFileForRelativePath(outputDirectory)) {
            @Override
            public void visit(File file, String relativePath) throws IOException {
                entries.add(outputDirectory.resolve(relativePath));
            }
        };
        directoryTraverser.traverse(traversal);
        return entries.build();
    }

    /**
     * Fetches the artifact associated with the {@link #buildTarget} for this class and writes it to
     * the specified {@code outputFile}.
     */
    public CacheResult fetchArtifactForBuildable(File outputFile, ArtifactCache artifactCache) {
        Preconditions.checkNotNull(outputFile);
        return artifactCache.fetch(ruleKey, outputFile);
    }

    /**
     * @param pathToArtifact Relative path to the project root.
     */
    public void recordArtifact(Path pathToArtifact) {
        Preconditions.checkNotNull(pathToArtifact);
        pathsToOutputFiles.add(pathToArtifact);
    }

    public void recordArtifactsInDirectory(Path pathToArtifactsDirectory) {
        Preconditions.checkNotNull(pathToArtifactsDirectory);
        pathsToOutputDirectories.add(pathToArtifactsDirectory);
    }

    @Nullable
    @VisibleForTesting
    String getMetadataFor(String key) {
        return metadataToWrite.get(key);
    }
}