/*
* Copyright 2016-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 static com.facebook.buck.util.concurrent.MoreFutures.propagateCauseIfInstanceOf;
import static com.google.common.base.Throwables.throwIfInstanceOf;
import com.facebook.buck.event.SimplePerfEvent;
import com.facebook.buck.json.BuildFileParseException;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetException;
import com.facebook.buck.rules.Cell;
import com.facebook.buck.rules.TargetNode;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.UncheckedExecutionException;
import java.nio.file.Path;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
/**
* Abstract node parsing pipeline. Allows implementors to define their own logic for creating nodes
* of type T.
*
* @param <T> The type of node this pipeline will produce (raw nodes, target nodes, etc)
*/
public abstract class ParsePipeline<T> implements AutoCloseable {
private static final Logger LOG = Logger.get(ParsePipeline.class);
private final AtomicBoolean shuttingDown;
private final long minimumPerfEventTimeMs;
public ParsePipeline() {
this.shuttingDown = new AtomicBoolean(false);
this.minimumPerfEventTimeMs = LOG.isVerboseEnabled() ? 0 : 1;
}
/**
* Obtain all {@link TargetNode}s from a build file. This may block if the file is not cached.
*
* @param cell the {@link Cell} that the build file belongs to.
* @param buildFile absolute path to the file to process.
* @return all targets from the file
* @throws BuildFileParseException for syntax errors.
*/
public final ImmutableSet<T> getAllNodes(
final Cell cell, final Path buildFile, AtomicLong processedBytes)
throws BuildFileParseException {
Preconditions.checkState(!shuttingDown.get());
try {
return getAllNodesJob(cell, buildFile, processedBytes).get();
} catch (Exception e) {
throwIfInstanceOf(e.getCause(), BuildFileParseException.class);
propagateCauseIfInstanceOf(e, ExecutionException.class);
propagateCauseIfInstanceOf(e, UncheckedExecutionException.class);
throw new RuntimeException(e);
}
}
/**
* Obtain a {@link TargetNode}. This may block if the node is not cached.
*
* @param cell the {@link Cell} that the {@link BuildTarget} belongs to.
* @param buildTarget name of the node we're looking for. The build file path is derived from it.
* @return the node
* @throws BuildFileParseException for syntax errors in the build file.
* @throws BuildTargetException if the buildTarget is malformed
*/
public final T getNode(final Cell cell, final BuildTarget buildTarget, AtomicLong processedBytes)
throws BuildFileParseException, BuildTargetException {
Preconditions.checkState(!shuttingDown.get());
try {
return getNodeJob(cell, buildTarget, processedBytes).get();
} catch (Exception e) {
if (e.getCause() != null) {
throwIfInstanceOf(e.getCause(), BuildFileParseException.class);
throwIfInstanceOf(e.getCause(), BuildTargetException.class);
}
propagateCauseIfInstanceOf(e, ExecutionException.class);
propagateCauseIfInstanceOf(e, UncheckedExecutionException.class);
throw new RuntimeException(e);
}
}
/**
* Asynchronously obtain all {@link TargetNode}s from a build file. This will leverage previously
* cached raw contents of the file (if present) but will always loop over the contents, so
* repeated calls (with the same args) are not free.
*
* <p>returned future may throw {@link BuildFileParseException} and {@link
* HumanReadableException}.
*
* @param cell the {@link Cell} that the build file belongs to.
* @param buildFile absolute path to the file to process.
* @param processedBytes
* @return future.
*/
public abstract ListenableFuture<ImmutableSet<T>> getAllNodesJob(
Cell cell, Path buildFile, AtomicLong processedBytes) throws BuildTargetException;
/**
* Asynchronously get the {@link TargetNode}. This leverages the cache.
*
* @param cell the {@link Cell} that the build file belongs to.
* @param buildTarget name of the node we're looking for. The build file path is derived from it.
* @param processedBytes An accumulator for the number of bytes which were read by the Parser in
* order to get this node.
* @return future.
* @throws BuildTargetException when the buildTarget is malformed.
*/
public abstract ListenableFuture<T> getNodeJob(
Cell cell, BuildTarget buildTarget, AtomicLong processedBytes) throws BuildTargetException;
@Override
public void close() {
shuttingDown.set(true);
// At this point external callers should not schedule more work, internally job creation
// should also stop. Any scheduled futures should eventually cancel themselves (all of the
// AsyncFunctions that interact with the Cache are wired to early-out if `shuttingDown` is
// true).
// We could block here waiting for all ongoing work to complete, however the user has already
// gotten everything they want out of the pipeline, so the only interesting thing that could
// happen here are exceptions thrown by the ProjectBuildFileParser as its shutting down. These
// aren't critical enough to warrant bringing down the entire process, as they don't affect the
// state that has already been extracted from the parser.
}
protected final boolean shuttingDown() {
return shuttingDown.get();
}
/**
* @return minimum duration time for performance events to be logged ( for use with {@link
* SimplePerfEvent}s). This is on the base class to make it simpler to enable verbose tracing
* for all of the parsing pipelines.
*/
protected final long getMinimumPerfEventTimeMs() {
return minimumPerfEventTimeMs;
}
}