Java tutorial
/* * Copyright 2017-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.core.build.engine.impl; import com.facebook.buck.core.build.context.BuildContext; import com.facebook.buck.core.build.engine.BuildResult; import com.facebook.buck.core.rules.BuildRule; import com.facebook.buck.core.rules.pipeline.RulePipelineState; import com.facebook.buck.core.rules.pipeline.SupportsPipelining; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.SortedSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.function.Function; import javax.annotation.Nullable; /** Constructs build rule pipelines for a single build. */ public class BuildRulePipelinesRunner { private final ConcurrentHashMap<SupportsPipelining<? extends RulePipelineState>, BuildRulePipelineStage<? extends RulePipelineState>> rules = new ConcurrentHashMap<>(); /** Gives the factory a way to construct a {@link RunnableWithFuture} to build the given rule. */ public <T extends RulePipelineState> void addRule(SupportsPipelining<T> rule, Function<T, RunnableWithFuture<Optional<BuildResult>>> ruleStepRunnerFactory) { BuildRulePipelineStage<T> pipelineStage = getPipelineStage(rule); SupportsPipelining<T> previousRuleInPipeline = rule.getPreviousRuleInPipeline(); if (previousRuleInPipeline != null) { Preconditions.checkState( previousRuleInPipeline.getPipelineStateFactory() == rule.getPipelineStateFactory(), "To help ensure that rules have pipeline-compatible rule keys, all rules in a pipeline must share a PipelineStateFactory instance."); SortedSet<BuildRule> currentDeps = rule.getBuildDeps(); SortedSet<BuildRule> previousDeps = previousRuleInPipeline.getBuildDeps(); Preconditions.checkState(currentDeps.contains(previousRuleInPipeline), "Each rule in a pipeline must depend on the previous rule in the pipeline."); SetView<BuildRule> extraDeps = Sets.difference(currentDeps, Sets.union(previousDeps, Collections.singleton(previousRuleInPipeline))); Preconditions.checkState(extraDeps.isEmpty(), "Each rule in a pipeline cannot depend on rules which are not also dependencies of the previous rule in the pipeline. " + "This ensures that each rule in the pipeline is ready to build as soon as the previous one completes. " + "%s has extra deps <%s>.", rule, Joiner.on(", ").join(extraDeps)); getPipelineStage(previousRuleInPipeline).setNextStage(pipelineStage); } pipelineStage.setRuleStepRunnerFactory(ruleStepRunnerFactory); } /** * Removes a rule from pipeline eligibility. If a pipeline is already running the rule, waits for * it to complete before returning. */ public void removeRule(SupportsPipelining<?> rule) { BuildRulePipelineStage<? extends RulePipelineState> pipelineStage = rules.remove(rule); if (pipelineStage != null && pipelineStage.pipelineBuilt()) { pipelineStage.cancelAndWait(); } } public boolean runningPipelinesContainRule(SupportsPipelining<?> rule) { BuildRulePipelineStage<?> pipelineStage = getPipelineStage(rule); return pipelineStage.pipelineBuilt(); } public ListenableFuture<Optional<BuildResult>> getFuture(SupportsPipelining<?> rule) { BuildRulePipelineStage<?> pipelineStage = getPipelineStage(rule); Preconditions.checkState(pipelineStage.pipelineBuilt()); return pipelineStage.getFuture(); } public <T extends RulePipelineState> ListenableFuture<Optional<BuildResult>> runPipelineStartingAt( BuildContext context, SupportsPipelining<T> rootRule, ExecutorService executor) { RunnableWithFuture<Optional<BuildResult>> runner = newPipelineRunner(context, rootRule); executor.execute(runner); return runner.getFuture(); } private <T extends RulePipelineState> RunnableWithFuture<Optional<BuildResult>> newPipelineRunner( BuildContext context, SupportsPipelining<T> rootRule) { BuildRulePipelineStage<T> rootPipelineStage = getPipelineStage(rootRule); Preconditions.checkState(!rootPipelineStage.pipelineBuilt()); BuildRulePipeline<T> pipeline = new BuildRulePipeline<>(rootPipelineStage, rootRule.getPipelineStateFactory().newInstance(context, rootRule.getProjectFilesystem(), rootRule.getBuildTarget())); return new RunnableWithFuture<Optional<BuildResult>>() { @Override public ListenableFuture<Optional<BuildResult>> getFuture() { // This runner is created when building the root rule locally. It gives back the future for // that root rule so that the dependents of that rule can begin compiling, while this // runner actually continues building the rest of the pipeline. return rootPipelineStage.getFuture(); } @Override public void run() { pipeline.run(); } }; } private <T extends RulePipelineState> BuildRulePipelineStage<T> getPipelineStage(SupportsPipelining<T> rule) { @SuppressWarnings("unchecked") BuildRulePipelineStage<T> result = (BuildRulePipelineStage<T>) rules.computeIfAbsent(rule, key -> new BuildRulePipelineStage<T>()); return result; } /** * Runs a list of rules one after another on the same thread, allowing each to access shared * state. */ private static class BuildRulePipeline<T extends RulePipelineState> implements Runnable { @Nullable private T state; private final List<BuildRulePipelineStage<T>> rules = new ArrayList<>(); public BuildRulePipeline(BuildRulePipelineStage<T> rootRule, T state) { this.state = state; buildPipeline(rootRule); } private void buildPipeline(BuildRulePipelineStage<T> firstStage) { BuildRulePipelineStage<T> walker = firstStage; while (walker != null) { rules.add(walker); walker.setPipeline(this); walker = walker.getNextStage(); } } public T getState() { return Objects.requireNonNull(state); } @Override public void run() { try { Throwable error = null; for (BuildRulePipelineStage<T> rule : rules) { if (error == null) { rule.run(); error = rule.getError(); } else { // It doesn't really matter what error we use here -- we just want the future to // complete so that Buck doesn't hang. We use the real error in case it ever is shown // to the user (which does not happen as of the time of this comment, but for safety). rule.abort(error); } // If everything is working correctly, each rule in the pipeline should show itself // complete before we start the next one. Just a sanity check against weird behavior // creeping in. Preconditions.checkState(rule.getFuture().isDone() || rule.getFuture().isCancelled()); } } finally { Objects.requireNonNull(state).close(); state = null; rules.clear(); } } } /** * Creates and runs the steps for a single build rule within a pipeline, cascading any failures to * rules later in the pipeline. */ private static class BuildRulePipelineStage<T extends RulePipelineState> implements RunnableWithFuture<Optional<BuildResult>> { private final SettableFuture<Optional<BuildResult>> future = SettableFuture.create(); @Nullable private BuildRulePipelineStage<T> nextStage; @Nullable private Throwable error = null; @Nullable private Function<T, RunnableWithFuture<Optional<BuildResult>>> ruleStepRunnerFactory; @Nullable private BuildRulePipeline<T> pipeline; @Nullable private RunnableWithFuture<Optional<BuildResult>> runner; @SuppressWarnings("CheckReturnValue") private BuildRulePipelineStage() { Futures.catching(future, Throwable.class, throwable -> error = throwable); } public void setRuleStepRunnerFactory( Function<T, RunnableWithFuture<Optional<BuildResult>>> ruleStepsFactory) { Preconditions.checkState(this.ruleStepRunnerFactory == null); this.ruleStepRunnerFactory = ruleStepsFactory; } public void setPipeline(BuildRulePipeline<T> pipeline) { Preconditions.checkState(this.pipeline == null); this.pipeline = pipeline; } public void setNextStage(BuildRulePipelineStage<T> nextStage) { Preconditions.checkState(this.nextStage == null); this.nextStage = nextStage; } @Nullable public BuildRulePipelineStage<T> getNextStage() { return nextStage; } public boolean pipelineBuilt() { return pipeline != null; } public void cancelAndWait() { // For now there's no cancel (cuz it's not hooked up at all), but we can at least wait try { getFuture().get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (ExecutionException e) { // NOPMD // Ignore; the future is hooked up elsewhere and that location will handle the exceptions } } @Nullable public Throwable getError() { return error; } @Override public SettableFuture<Optional<BuildResult>> getFuture() { return future; } @Override public void run() { Objects.requireNonNull(pipeline); Objects.requireNonNull(ruleStepRunnerFactory); runner = ruleStepRunnerFactory.apply(pipeline.getState()); future.setFuture(runner.getFuture()); runner.run(); } public void abort(Throwable error) { future.setException(error); } } }