com.facebook.buck.parser.AbstractParser.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.parser.AbstractParser.java

Source

/*
 * Copyright 2015-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.parser;

import com.facebook.buck.core.cell.Cell;
import com.facebook.buck.core.exceptions.HumanReadableException;
import com.facebook.buck.core.model.BuildTarget;
import com.facebook.buck.core.model.TargetConfiguration;
import com.facebook.buck.core.model.targetgraph.TargetGraph;
import com.facebook.buck.core.model.targetgraph.TargetGraphAndBuildTargets;
import com.facebook.buck.core.model.targetgraph.TargetNode;
import com.facebook.buck.core.util.graph.AcyclicDepthFirstPostOrderTraversal;
import com.facebook.buck.core.util.graph.GraphTraversable;
import com.facebook.buck.core.util.graph.MutableDirectedGraph;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.parser.api.BuildFileManifest;
import com.facebook.buck.parser.exceptions.BuildFileParseException;
import com.facebook.buck.parser.exceptions.BuildTargetException;
import com.facebook.buck.util.MoreMaps;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
import javax.annotation.Nullable;

/**
 * Evaluates build files using one of the supported interpreters and provides information about
 * build targets defined in them.
 *
 * <p>Computed targets are cached but are automatically invalidated if Watchman reports any
 * filesystem changes that may affect computed results.
 */
abstract class AbstractParser implements Parser {

    protected final PerBuildStateFactory perBuildStateFactory;
    protected final DaemonicParserState permState;
    protected final BuckEventBus eventBus;
    protected final Supplier<ImmutableList<String>> targetPlatforms;

    AbstractParser(DaemonicParserState daemonicParserState, PerBuildStateFactory perBuildStateFactory,
            BuckEventBus eventBus, Supplier<ImmutableList<String>> targetPlatforms) {
        this.perBuildStateFactory = perBuildStateFactory;
        this.permState = daemonicParserState;
        this.eventBus = eventBus;
        this.targetPlatforms = targetPlatforms;
    }

    @Override
    public DaemonicParserState getPermState() {
        return permState;
    }

    @Override
    public PerBuildStateFactory getPerBuildStateFactory() {
        return perBuildStateFactory;
    }

    @VisibleForTesting
    static BuildFileManifest getTargetNodeRawAttributes(PerBuildState state, Cell cell, Path buildFile)
            throws BuildFileParseException {
        Preconditions.checkState(buildFile.isAbsolute());
        return state.getBuildFileManifest(cell, buildFile);
    }

    @Override
    public ImmutableList<TargetNode<?>> getAllTargetNodes(PerBuildState perBuildState, Cell cell, Path buildFile,
            TargetConfiguration targetConfiguration) throws BuildFileParseException {
        return perBuildState.getAllTargetNodes(cell, buildFile, targetConfiguration);
    }

    @Override
    public TargetNode<?> getTargetNode(ParsingContext parsingContext, BuildTarget target)
            throws BuildFileParseException {
        try (PerBuildState state = perBuildStateFactory.create(parsingContext, permState, targetPlatforms.get())) {
            return state.getTargetNode(target);
        }
    }

    @Override
    public TargetNode<?> getTargetNode(PerBuildState perBuildState, BuildTarget target)
            throws BuildFileParseException {
        return perBuildState.getTargetNode(target);
    }

    @Override
    public ListenableFuture<TargetNode<?>> getTargetNodeJob(PerBuildState perBuildState, BuildTarget target)
            throws BuildTargetException {
        return perBuildState.getTargetNodeJob(target);
    }

    @Nullable
    @Override
    public SortedMap<String, Object> getTargetNodeRawAttributes(PerBuildState state, Cell cell,
            TargetNode<?> targetNode) throws BuildFileParseException {
        Cell owningCell = cell.getCell(targetNode.getBuildTarget());
        BuildFileManifest buildFileManifest = getTargetNodeRawAttributes(state, owningCell,
                cell.getAbsolutePathToBuildFile(targetNode.getBuildTarget()));
        return getTargetFromManifest(targetNode, buildFileManifest);
    }

    @Override
    public ListenableFuture<SortedMap<String, Object>> getTargetNodeRawAttributesJob(PerBuildState state, Cell cell,
            TargetNode<?> targetNode) throws BuildFileParseException {
        Cell owningCell = cell.getCell(targetNode.getBuildTarget());
        ListenableFuture<BuildFileManifest> buildFileManifestFuture = state.getBuildFileManifestJob(owningCell,
                cell.getAbsolutePathToBuildFile(targetNode.getBuildTarget()));
        return Futures.transform(buildFileManifestFuture,
                buildFileManifest -> getTargetFromManifest(targetNode, buildFileManifest),
                MoreExecutors.directExecutor());
    }

    @Nullable
    private static SortedMap<String, Object> getTargetFromManifest(TargetNode<?> targetNode,
            BuildFileManifest buildFileManifest) {
        String shortName = targetNode.getBuildTarget().getShortName();

        if (!buildFileManifest.getTargets().containsKey(shortName)) {
            return null;
        }

        SortedMap<String, Object> attributes = new TreeMap<>(buildFileManifest.getTargets().get(shortName));
        attributes.put(InternalTargetAttributeNames.DIRECT_DEPENDENCIES,
                targetNode.getParseDeps().stream().map(Object::toString).collect(ImmutableList.toImmutableList()));
        return attributes;
    }

    /**
     * @deprecated Prefer {@link #getTargetNodeRawAttributes(PerBuildState, Cell, TargetNode)} and
     *     reusing a PerBuildState instance, especially when calling in a loop.
     */
    @Nullable
    @Deprecated
    @Override
    public SortedMap<String, Object> getTargetNodeRawAttributes(ParsingContext parsingContext,
            TargetNode<?> targetNode) throws BuildFileParseException {

        try (PerBuildState state = perBuildStateFactory.create(parsingContext, permState, targetPlatforms.get())) {
            return getTargetNodeRawAttributes(state, parsingContext.getCell(), targetNode);
        }
    }

    private RuntimeException propagateRuntimeCause(RuntimeException e)
            throws IOException, InterruptedException, BuildFileParseException {
        Throwables.throwIfInstanceOf(e, HumanReadableException.class);

        Throwable t = e.getCause();
        if (t != null) {
            Throwables.throwIfInstanceOf(t, IOException.class);
            Throwables.throwIfInstanceOf(t, InterruptedException.class);
            Throwables.throwIfInstanceOf(t, BuildFileParseException.class);
            Throwables.throwIfInstanceOf(t, BuildTargetException.class);
        }
        return e;
    }

    @Override
    public TargetGraph buildTargetGraph(ParsingContext parsingContext, Iterable<BuildTarget> toExplore)
            throws IOException, InterruptedException, BuildFileParseException {
        if (Iterables.isEmpty(toExplore)) {
            return TargetGraph.EMPTY;
        }

        AtomicLong processedBytes = new AtomicLong();
        try (PerBuildState state = perBuildStateFactory.create(parsingContext, permState, targetPlatforms.get(),
                processedBytes)) {
            return buildTargetGraph(state, toExplore, processedBytes);
        }
    }

    private TargetGraph buildTargetGraph(PerBuildState state, Iterable<BuildTarget> toExplore,
            AtomicLong processedBytes) throws IOException, InterruptedException, BuildFileParseException {

        if (Iterables.isEmpty(toExplore)) {
            return TargetGraph.EMPTY;
        }

        MutableDirectedGraph<TargetNode<?>> graph = new MutableDirectedGraph<>();
        Map<BuildTarget, TargetNode<?>> index = new HashMap<>();

        ParseEvent.Started parseStart = ParseEvent.started(toExplore);
        eventBus.post(parseStart);

        GraphTraversable<BuildTarget> traversable = target -> {
            TargetNode<?> node;
            try {
                node = state.getTargetNode(target);
            } catch (BuildFileParseException e) {
                throw new RuntimeException(e);
            }

            // this second lookup loop may *seem* pointless, but it allows us to report which node is
            // referring to a node we can't find - something that's very difficult in this Traversable
            // visitor pattern otherwise.
            // it's also work we need to do anyways. the getTargetNode() result is cached, so that
            // when we come around and re-visit that node there won't actually be any work performed.
            for (BuildTarget dep : node.getParseDeps()) {
                try {
                    state.getTargetNode(dep);
                } catch (BuildFileParseException e) {
                    throw ParserMessages.createReadableExceptionWithWhenSuffix(target, dep, e);
                } catch (HumanReadableException e) {
                    throw ParserMessages.createReadableExceptionWithWhenSuffix(target, dep, e);
                }
            }
            return node.getParseDeps().iterator();
        };

        AcyclicDepthFirstPostOrderTraversal<BuildTarget> targetNodeTraversal = new AcyclicDepthFirstPostOrderTraversal<>(
                traversable);

        TargetGraph targetGraph = null;
        try {
            for (BuildTarget target : targetNodeTraversal.traverse(toExplore)) {
                TargetNode<?> targetNode = state.getTargetNode(target);

                Preconditions.checkNotNull(targetNode, "No target node found for %s", target);
                assertTargetIsCompatible(state, targetNode);

                graph.addNode(targetNode);
                MoreMaps.putCheckEquals(index, target, targetNode);
                if (target.isFlavored()) {
                    BuildTarget unflavoredTarget = target.withoutFlavors();
                    MoreMaps.putCheckEquals(index, unflavoredTarget, state.getTargetNode(unflavoredTarget));
                }
                for (BuildTarget dep : targetNode.getParseDeps()) {
                    graph.addEdge(targetNode, state.getTargetNode(dep));
                }
            }

            targetGraph = new TargetGraph(graph, ImmutableMap.copyOf(index));
            return targetGraph;
        } catch (AcyclicDepthFirstPostOrderTraversal.CycleException e) {
            throw new HumanReadableException(e.getMessage());
        } catch (RuntimeException e) {
            throw propagateRuntimeCause(e);
        } finally {
            eventBus.post(ParseEvent.finished(parseStart, processedBytes.get(), Optional.ofNullable(targetGraph)));
        }
    }

    @Override
    public synchronized TargetGraphAndBuildTargets buildTargetGraphWithoutConfigurationTargets(
            ParsingContext parsingContext, Iterable<? extends TargetNodeSpec> targetNodeSpecs,
            TargetConfiguration targetConfiguration)
            throws BuildFileParseException, IOException, InterruptedException {
        return buildTargetGraphForTargetNodeSpecs(parsingContext, targetNodeSpecs, targetConfiguration, true);
    }

    @Override
    public synchronized TargetGraphAndBuildTargets buildTargetGraphWithConfigurationTargets(
            ParsingContext parsingContext, Iterable<? extends TargetNodeSpec> targetNodeSpecs,
            TargetConfiguration targetConfiguration)
            throws BuildFileParseException, IOException, InterruptedException {
        return buildTargetGraphForTargetNodeSpecs(parsingContext, targetNodeSpecs, targetConfiguration, false);
    }

    private synchronized TargetGraphAndBuildTargets buildTargetGraphForTargetNodeSpecs(
            ParsingContext parsingContext, Iterable<? extends TargetNodeSpec> targetNodeSpecs,
            TargetConfiguration targetConfiguration, boolean excludeConfigurationTargets)
            throws BuildFileParseException, IOException, InterruptedException {

        AtomicLong processedBytes = new AtomicLong();
        try (PerBuildState state = perBuildStateFactory.create(parsingContext, permState, targetPlatforms.get(),
                processedBytes)) {

            ImmutableSet<BuildTarget> buildTargets = collectBuildTargetsFromTargetNodeSpecs(parsingContext, state,
                    targetNodeSpecs, targetConfiguration, excludeConfigurationTargets);
            TargetGraph graph = buildTargetGraph(state, buildTargets, processedBytes);

            return TargetGraphAndBuildTargets.of(graph, buildTargets);
        }
    }

    protected abstract ImmutableSet<BuildTarget> collectBuildTargetsFromTargetNodeSpecs(
            ParsingContext parsingContext, PerBuildState state, Iterable<? extends TargetNodeSpec> targetNodeSpecs,
            TargetConfiguration targetConfiguration, boolean excludeConfigurationTargets)
            throws InterruptedException;

    /**
     * Verifies that the provided target node is compatible with the target platform.
     *
     * @throws com.facebook.buck.core.exceptions.HumanReadableException if the target not is not
     *     compatible with the target platform.
     */
    @SuppressWarnings("unused")
    protected void assertTargetIsCompatible(PerBuildState state, TargetNode<?> targetNode) {
    }

    @Override
    public String toString() {
        return permState.toString();
    }
}