com.facebook.buck.rules.modern.builders.ActionRunner.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.rules.modern.builders.ActionRunner.java

Source

/*
 * Copyright 2018-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.modern.builders;

import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.LeafEvents;
import com.facebook.buck.rules.modern.builders.FileTreeBuilder.InputFile;
import com.facebook.buck.rules.modern.builders.FileTreeBuilder.ProtocolTreeBuilder;
import com.facebook.buck.rules.modern.builders.Protocol.Digest;
import com.facebook.buck.rules.modern.builders.Protocol.Directory;
import com.facebook.buck.rules.modern.builders.Protocol.OutputDirectory;
import com.facebook.buck.rules.modern.builders.Protocol.OutputFile;
import com.facebook.buck.rules.modern.builders.Protocol.Tree;
import com.facebook.buck.util.Ansi;
import com.facebook.buck.util.CapturingPrintStream;
import com.facebook.buck.util.Console;
import com.facebook.buck.util.DefaultProcessExecutor;
import com.facebook.buck.util.ProcessExecutor.Result;
import com.facebook.buck.util.ProcessExecutorParams;
import com.facebook.buck.util.ProcessExecutorParams.Builder;
import com.facebook.buck.util.RichStream;
import com.facebook.buck.util.Scope;
import com.facebook.buck.util.Verbosity;
import com.facebook.buck.util.function.ThrowingSupplier;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.MoreFiles;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

/**
 * Runs an action (command + environment) in a directory and returns the results (exit code,
 * stdout/stderr, and outputs).
 */
public class ActionRunner {
    private final Protocol protocol;
    private final BuckEventBus eventBus;

    public ActionRunner(Protocol protocol, BuckEventBus eventBus) {
        this.protocol = protocol;
        this.eventBus = eventBus;
    }

    /** Results of an action. */
    public static class ActionResult {
        public final ImmutableList<OutputFile> outputFiles;
        public final ImmutableList<OutputDirectory> outputDirectories;
        public final ImmutableMap<Protocol.Digest, ThrowingSupplier<InputStream, IOException>> requiredData;
        public final int exitCode;
        public final String stderr;
        public final String stdout;

        ActionResult(ImmutableList<OutputFile> outputFiles, ImmutableList<OutputDirectory> outputDirectories,
                ImmutableMap<Digest, ThrowingSupplier<InputStream, IOException>> requiredData, int exitCode,
                String stderr, String stdout) {
            this.outputFiles = outputFiles;
            this.outputDirectories = outputDirectories;
            this.requiredData = requiredData;
            this.exitCode = exitCode;
            this.stderr = stderr;
            this.stdout = stdout;
        }
    }

    /** Runs an action and returns the result. */
    public ActionResult runAction(ImmutableList<String> command, ImmutableMap<String, String> environment,
            Set<Path> outputs, Path buildDir) throws IOException, InterruptedException {
        Console console;
        Builder paramsBuilder;
        try (Scope ignored = LeafEvents.scope(eventBus, "preparing_action")) {
            paramsBuilder = ProcessExecutorParams.builder();
            paramsBuilder.setCommand(command);
            paramsBuilder.setEnvironment(environment);
            paramsBuilder.setDirectory(buildDir);
            CapturingPrintStream stdOut = new CapturingPrintStream();
            CapturingPrintStream stdErr = new CapturingPrintStream();
            console = new Console(Verbosity.STANDARD_INFORMATION, stdOut, stdErr, Ansi.withoutTty());
        }

        Result result;
        try (Scope ignored = LeafEvents.scope(eventBus, "subprocess")) {
            result = new DefaultProcessExecutor(console).launchAndExecute(paramsBuilder.build());
        }

        ImmutableList.Builder<OutputFile> outputFiles;
        ImmutableList.Builder<OutputDirectory> outputDirectories;
        ImmutableMap.Builder<Digest, ThrowingSupplier<InputStream, IOException>> requiredData;
        try (Scope ignored = LeafEvents.scope(eventBus, "collecting_outputs")) {
            outputFiles = ImmutableList.builder();
            outputDirectories = ImmutableList.builder();
            requiredData = ImmutableMap.builder();
            if (result.getExitCode() == 0) {
                // TODO(cjhopman): Should outputs be returned on failure?
                collectOutputs(outputs, buildDir, outputFiles, outputDirectories, requiredData);
            }
        }

        return new ActionResult(outputFiles.build(), outputDirectories.build(), requiredData.build(),
                result.getExitCode(), result.getStderr().get(), result.getStdout().get());
    }

    private void collectOutputs(Set<Path> outputs, Path buildDir,
            ImmutableList.Builder<OutputFile> outputFilesBuilder,
            ImmutableList.Builder<OutputDirectory> outputDirectoriesBuilder,
            ImmutableMap.Builder<Digest, ThrowingSupplier<InputStream, IOException>> requiredDataBuilder)
            throws IOException {
        for (Path output : outputs) {
            Path path = buildDir.resolve(output);
            Preconditions.checkState(Files.exists(path));
            if (Files.isDirectory(path)) {
                FileTreeBuilder builder = new FileTreeBuilder();

                try (Stream<Path> contents = Files.walk(path)) {
                    RichStream.from(contents).forEachThrowing(entry -> {
                        if (Files.isRegularFile(entry)) {
                            builder.addFile(path.relativize(entry),
                                    () -> new InputFile(hashFile(entry).toString(), (int) Files.size(entry),
                                            Files.isExecutable(entry), () -> new FileInputStream(entry.toFile())));
                        }
                    });
                }

                List<Directory> directories = new ArrayList<>();
                Digest digest = builder
                        .buildTree(new ProtocolTreeBuilder(requiredDataBuilder::put, directories::add, protocol));
                Preconditions.checkState(!directories.isEmpty());
                Tree tree = protocol.newTree(directories.get(directories.size() - 1), directories);
                byte[] treeData = protocol.toByteArray(tree);
                Digest treeDigest = protocol.computeDigest(treeData);

                outputDirectoriesBuilder.add(protocol.newOutputDirectory(output, digest, treeDigest));
                requiredDataBuilder.put(treeDigest, () -> new ByteArrayInputStream(treeData));
            } else {
                long size = Files.size(path);
                boolean isExecutable = Files.isExecutable(path);
                Digest digest = protocol.newDigest(hashFile(path).toString(), (int) size);

                ThrowingSupplier<InputStream, IOException> dataSupplier = () -> new FileInputStream(path.toFile());
                outputFilesBuilder.add(protocol.newOutputFile(output, digest, isExecutable, dataSupplier));
                requiredDataBuilder.put(digest, dataSupplier);
            }
        }
    }

    private HashCode hashFile(Path file) throws IOException {
        return MoreFiles.asByteSource(file).hash(Hashing.sha1());
    }
}