| /* |
| * Copyright 2014-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.ConsoleEvent; |
| import com.facebook.buck.event.ThrowableConsoleEvent; |
| import com.facebook.buck.io.MoreFiles; |
| import com.facebook.buck.log.Logger; |
| import com.facebook.buck.model.BuildTarget; |
| import com.facebook.buck.step.Step; |
| import com.facebook.buck.step.StepRunner; |
| import com.facebook.buck.util.concurrent.MoreFutures; |
| import com.facebook.buck.zip.Unzip; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Supplier; |
| import com.google.common.base.Suppliers; |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.util.concurrent.FutureCallback; |
| import com.google.common.util.concurrent.Futures; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.common.util.concurrent.SettableFuture; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.util.List; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.ExecutionException; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * A build engine used to build a {@link BuildRule} which also caches the results. If the current |
| * {@link RuleKey} of the build rules matches the one on disk, it does not do any work. It also |
| * tries to fetch its output from an {@link ArtifactCache} to avoid doing any computation. |
| */ |
| public class CachingBuildEngine implements BuildEngine { |
| |
| private static final Logger LOG = Logger.get(CachingBuildEngine.class); |
| |
| /** |
| * Key for {@link OnDiskBuildInfo} to identify the ABI key for the deps of a build rule. |
| */ |
| @VisibleForTesting |
| public static final String ABI_KEY_FOR_DEPS_ON_DISK_METADATA = "ABI_KEY_FOR_DEPS"; |
| |
| /** |
| * These are the values returned by {@link #build(BuildContext, BuildRule)}. |
| * This must always return the same value for the build of each target. |
| */ |
| private final ConcurrentMap<BuildTarget, SettableFuture<BuildRuleSuccess>> results = |
| Maps.newConcurrentMap(); |
| |
| private final ConcurrentMap<BuildTarget, RuleKey> ruleKeys = Maps.newConcurrentMap(); |
| |
| private final long skipLocalBuildDepth; |
| |
| public CachingBuildEngine(long skipLocalBuildDepth) { |
| Preconditions.checkArgument(skipLocalBuildDepth >= 0L); |
| this.skipLocalBuildDepth = skipLocalBuildDepth; |
| } |
| |
| @VisibleForTesting |
| public CachingBuildEngine() { |
| this(0L); |
| } |
| |
| @VisibleForTesting |
| public SettableFuture<BuildRuleSuccess> createFutureFor(BuildTarget buildTarget) { |
| SettableFuture<BuildRuleSuccess> newFuture = SettableFuture.create(); |
| SettableFuture<BuildRuleSuccess> result = results.putIfAbsent( |
| buildTarget, |
| newFuture); |
| return result == null ? newFuture : result; |
| } |
| |
| @VisibleForTesting |
| void setBuildRuleResult( |
| BuildTarget buildTarget, |
| BuildRuleSuccess success) { |
| createFutureFor(buildTarget).set(success); |
| } |
| |
| @Override |
| public boolean isRuleBuilt(BuildTarget buildTarget) throws InterruptedException { |
| SettableFuture<BuildRuleSuccess> resultFuture = results.get(buildTarget); |
| return resultFuture != null && MoreFutures.isSuccess(resultFuture); |
| } |
| |
| @Nullable |
| @Override |
| public RuleKey getRuleKey(BuildTarget buildTarget) { |
| return ruleKeys.get(buildTarget); |
| } |
| |
| @Override |
| public final ListenableFuture<BuildRuleSuccess> build( |
| final BuildContext context, |
| final BuildRule rule) { |
| |
| final SettableFuture<BuildRuleSuccess> newFuture = SettableFuture.create(); |
| SettableFuture<BuildRuleSuccess> existingFuture = results.putIfAbsent( |
| rule.getBuildTarget(), |
| newFuture); |
| |
| // If the future was already in results for this build rule, return what was there. |
| if (existingFuture != null) { |
| return existingFuture; |
| } |
| |
| // Build all of the deps first and then schedule a callback for this rule to build itself once |
| // all of those rules are done building. |
| try { |
| // Invoke every dep's build() method and create an uber-ListenableFuture that represents the |
| // successful completion of all deps. |
| List<ListenableFuture<BuildRuleSuccess>> builtDeps = |
| Lists.newArrayListWithCapacity(rule.getDeps().size()); |
| for (BuildRule dep : rule.getDeps()) { |
| builtDeps.add(build(context, dep)); |
| } |
| ListenableFuture<List<BuildRuleSuccess>> allBuiltDeps = Futures.allAsList(builtDeps); |
| |
| // Schedule this rule to build itself once all of the deps are built. |
| context.getStepRunner().addCallback(allBuiltDeps, |
| new FutureCallback<List<BuildRuleSuccess>>() { |
| |
| private final BuckEventBus eventBus = context.getEventBus(); |
| |
| private final OnDiskBuildInfo onDiskBuildInfo = context.createOnDiskBuildInfoFor( |
| rule.getBuildTarget()); |
| |
| /** |
| * It is imperative that: |
| * <ol> |
| * <li>The {@link BuildInfoRecorder} is not constructed until all of the |
| * {@link BuildRule}'s {@code deps} are guaranteed to be built. This ensures that |
| * the {@link RuleKey} will be available before the {@link BuildInfoRecorder} is |
| * constructed. |
| * <p> |
| * This is why a {@link Supplier} is used. |
| * <li>Only one {@link BuildInfoRecorder} is created per {@link BuildRule}. This |
| * ensures that all build-related information for a {@link BuildRule} goes though |
| * a single recorder, whose data will be persisted in {@link #onSuccess(List)}. |
| * <p> |
| * This is why {@link Suppliers#memoize(Supplier)} is used. |
| * </ol> |
| */ |
| private final Supplier<BuildInfoRecorder> buildInfoRecorder = Suppliers.memoize( |
| new Supplier<BuildInfoRecorder>() { |
| @Override |
| public BuildInfoRecorder get() { |
| RuleKey ruleKey; |
| RuleKey ruleKeyWithoutDeps; |
| ruleKey = rule.getRuleKey(); |
| ruleKeyWithoutDeps = rule.getRuleKeyWithoutDeps(); |
| |
| return context.createBuildInfoRecorder( |
| rule.getBuildTarget(), ruleKey, ruleKeyWithoutDeps); |
| } |
| }); |
| |
| private boolean startOfBuildWasRecordedOnTheEventBus = false; |
| |
| @Override |
| public void onSuccess(List<BuildRuleSuccess> deps) { |
| // Record the start of the build. |
| eventBus.logVerboseAndPost(LOG, BuildRuleEvent.started(rule)); |
| startOfBuildWasRecordedOnTheEventBus = true; |
| |
| BuildResult result = null; |
| try { |
| ruleKeys.putIfAbsent(rule.getBuildTarget(), rule.getRuleKey()); |
| result = buildOnceDepsAreBuilt( |
| rule, |
| context, |
| onDiskBuildInfo, |
| buildInfoRecorder.get(), |
| shouldTryToFetchFromCache(rule)); |
| if (result.getStatus() == BuildRuleStatus.SUCCESS) { |
| recordBuildRuleSuccess(result); |
| } |
| } catch (InterruptedException | RuntimeException e) { |
| // StepRunner#addCallback doesn't return a future which means that when we add a |
| // callback to the step runner in CachingBuildEngine#build, there is no way to |
| // have the exception thrown in the callback to be forwarded to the future we |
| // return from it. |
| // For now, we'll just catch the RuntimeException. |
| // TODO(simons, t5597862): Consider modifying StepRunner#addCallback |
| result = new BuildResult(e); |
| } |
| if (result.getStatus() == BuildRuleStatus.FAIL) { |
| recordBuildRuleFailure(result); |
| |
| // Reset interrupted flag once failure has been recorded. |
| if (result.getFailure() instanceof InterruptedException) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| } |
| |
| private void recordBuildRuleSuccess(BuildResult result) |
| throws InterruptedException { |
| // Make sure that all of the local files have the same values they would as if the |
| // rule had been built locally. |
| BuildRuleSuccess.Type success = result.getSuccess(); |
| if (success != null && success.shouldWriteRecordedMetadataToDiskAfterBuilding()) { |
| try { |
| boolean clearExistingMetadata = success.shouldClearAndOverwriteMetadataOnDisk(); |
| buildInfoRecorder.get().writeMetadataToDisk(clearExistingMetadata); |
| } catch (IOException e) { |
| eventBus.post(ThrowableConsoleEvent.create( |
| e, |
| "Failed to write metadata to disk for %s.", |
| rule)); |
| onFailure(e); |
| } |
| } |
| |
| doHydrationAfterBuildStepsFinish(rule, result, onDiskBuildInfo); |
| |
| // Do the post to the event bus immediately after the future is set so that the |
| // build time measurement is as accurate as possible. |
| logBuildRuleFinished(result); |
| |
| // Only now that the rule should be in a completely valid state, resolve the future. |
| BuildRuleSuccess buildRuleSuccess = new BuildRuleSuccess(rule, result.getSuccess()); |
| newFuture.set(buildRuleSuccess); |
| |
| // Finally, upload to the artifact cache. |
| if (success != null && success.shouldUploadResultingArtifact()) { |
| buildInfoRecorder.get().performUploadToArtifactCache(context.getArtifactCache(), |
| eventBus); |
| } |
| } |
| |
| @Override |
| public void onFailure(Throwable failure) { |
| recordBuildRuleFailure(new BuildResult(failure)); |
| } |
| |
| private void recordBuildRuleFailure(BuildResult result) { |
| // TODO(mbolin): Delete all files produced by the rule, as they are not guaranteed to |
| // be valid at this point? |
| try { |
| onDiskBuildInfo.deleteExistingMetadata(); |
| } catch (IOException e) { |
| eventBus.post(ThrowableConsoleEvent.create( |
| e, |
| "Error when deleting metadata for %s.", |
| rule)); |
| } |
| |
| // Note that startOfBuildWasRecordedOnTheEventBus will be false if onSuccess() was |
| // never invoked. |
| if (startOfBuildWasRecordedOnTheEventBus) { |
| logBuildRuleFinished(result); |
| } |
| |
| // It seems possible (albeit unlikely) that something could go wrong in |
| // recordBuildRuleSuccess() after buildRuleResult has been resolved such that Buck |
| // would attempt to resolve the future again, which would fail. |
| newFuture.setException(Preconditions.checkNotNull(result.getFailure())); |
| } |
| |
| private void logBuildRuleFinished(BuildResult result) { |
| eventBus.logVerboseAndPost( |
| LOG, |
| BuildRuleEvent.finished( |
| rule, |
| result.getStatus(), |
| result.getCacheResult(), |
| Optional.fromNullable(result.getSuccess()))); |
| } |
| }); |
| } catch (Throwable failure) { |
| // This is a defensive catch block: if buildRuleResult is never satisfied, then Buck will |
| // hang because a callback that is waiting for this rule's future to complete will never be |
| // executed. |
| newFuture.setException(failure); |
| } |
| |
| return newFuture; |
| } |
| |
| |
| /** |
| * This method is invoked once all of this rule's dependencies are built. |
| * <p> |
| * This method should be executed on a fresh Runnable in BuildContext's ListeningExecutorService, |
| * so there is no reason to schedule new work in a new Runnable. |
| * <p> |
| * All exit paths through this method should resolve {@link #results} before exiting. To |
| * that end, this method should never throw an exception, or else Buck will hang waiting for |
| * {@link #results} to be resolved. |
| * |
| * @param shouldTryToFetchFromCache Making requests to Cassandra can be expensive, so we do not |
| * attempt to fetch from the cache if any of the transitive dependencies gets rebuilt. |
| */ |
| private BuildResult buildOnceDepsAreBuilt(BuildRule rule, |
| final BuildContext context, |
| OnDiskBuildInfo onDiskBuildInfo, |
| BuildInfoRecorder buildInfoRecorder, |
| boolean shouldTryToFetchFromCache) { |
| // Compute the current RuleKey and compare it to the one stored on disk. |
| RuleKey ruleKey = rule.getRuleKey(); |
| Optional<RuleKey> cachedRuleKey = onDiskBuildInfo.getRuleKey(); |
| |
| // If the RuleKeys match, then there is nothing to build. |
| if (ruleKey.equals(cachedRuleKey.orNull())) { |
| return new BuildResult(BuildRuleSuccess.Type.MATCHING_RULE_KEY, |
| CacheResult.LOCAL_KEY_UNCHANGED_HIT); |
| } |
| |
| // Deciding whether we need to rebuild is tricky business. We want to rebuild as little as |
| // possible while always being sound. |
| // |
| // For java_library rules that depend only on their first-order deps, |
| // they only need to rebuild themselves if any of the following conditions hold: |
| // (1) The definition of the build rule has changed. |
| // (2) Any of the input files (which includes resources as well as .java files) have changed. |
| // (3) The ABI of any of its dependent java_library rules has changed. |
| // |
| // For other types of build rules, we have to be more conservative when rebuilding. In those |
| // cases, we rebuild if any of the following conditions hold: |
| // (1) The definition of the build rule has changed. |
| // (2) Any of the input files have changed. |
| // (3) Any of the RuleKeys of this rule's deps have changed. |
| // |
| // Because a RuleKey for a rule will change if any of its transitive deps have changed, that |
| // means a change in one of the leaves can result in almost all rules being rebuilt, which is |
| // slow. Fortunately, we limit the effects of this when building Java code when checking the ABI |
| // of deps instead of the RuleKey for deps. |
| AbiRule abiRule = checkIfRuleOrBuildableIsAbiRule(rule); |
| if (abiRule != null) { |
| RuleKey ruleKeyNoDeps = rule.getRuleKeyWithoutDeps(); |
| Optional<RuleKey> cachedRuleKeyNoDeps = onDiskBuildInfo.getRuleKeyWithoutDeps(); |
| if (ruleKeyNoDeps.equals(cachedRuleKeyNoDeps.orNull())) { |
| // The RuleKey for the definition of this build rule and its input files has not changed. |
| // Therefore, if the ABI of its deps has not changed, there is nothing to rebuild. |
| Sha1HashCode abiKeyForDeps = abiRule.getAbiKeyForDeps(); |
| Optional<Sha1HashCode> cachedAbiKeyForDeps = onDiskBuildInfo.getHash( |
| ABI_KEY_FOR_DEPS_ON_DISK_METADATA); |
| if (abiKeyForDeps.equals(cachedAbiKeyForDeps.orNull())) { |
| return new BuildResult(BuildRuleSuccess.Type.MATCHING_DEPS_ABI_AND_RULE_KEY_NO_DEPS, |
| CacheResult.LOCAL_KEY_UNCHANGED_HIT); |
| } |
| } |
| } |
| |
| CacheResult cacheResult; |
| if (shouldTryToFetchFromCache) { |
| // Before deciding to build, check the ArtifactCache. |
| // The fetched file is now a ZIP file, so it needs to be unzipped. |
| try { |
| cacheResult = tryToFetchArtifactFromBuildCacheAndOverlayOnTopOfProjectFilesystem( |
| rule, |
| buildInfoRecorder, |
| context.getArtifactCache(), |
| context.getProjectRoot(), |
| context); |
| } catch (InterruptedException e) { |
| return new BuildResult(e); |
| } |
| } else { |
| cacheResult = CacheResult.SKIP; |
| } |
| |
| // Run the steps to build this rule since it was not found in the cache. |
| if (cacheResult.isSuccess()) { |
| return new BuildResult(BuildRuleSuccess.Type.FETCHED_FROM_CACHE, cacheResult); |
| } |
| |
| // The only remaining option is to build locally. |
| try { |
| executeCommandsNowThatDepsAreBuilt(rule, context, buildInfoRecorder); |
| } catch (Exception e) { |
| // If the build fails, delete all of the on disk metadata. |
| return new BuildResult(e); |
| } |
| |
| return new BuildResult(BuildRuleSuccess.Type.BUILT_LOCALLY, cacheResult); |
| } |
| |
| /** |
| * @return whether we found a local build chain of the given depth by recursing down the |
| * dependency chain. |
| */ |
| private boolean hasLocalBuildChain(BuildRule rule, long depth) { |
| |
| // Look up the success result for this `BuildRule`. |
| BuildRuleSuccess success; |
| try { |
| success = Preconditions.checkNotNull(getBuildRuleResult(rule.getBuildTarget())); |
| } catch (InterruptedException | ExecutionException e) { |
| // This shouldn't ever happen, as the only way we can get to this point is if the |
| // previous build rules in the dep tree generated results. |
| throw new IllegalStateException(e); |
| } |
| |
| // If we built this locally, and caching is enabled for this rule, it means we likely had |
| // a cache miss, and may have a local build chain for the given depth. |
| if (success.getType() == BuildRuleSuccess.Type.BUILT_LOCALLY && |
| rule.getCacheMode() == CacheMode.ENABLED) { |
| |
| // If the given `depth` is zero, we've found our local build chain, so return true. |
| if (depth == 0) { |
| return true; |
| |
| // Otherwise, recurse on our deps looking for the local build chain. |
| } else { |
| for (BuildRule dep : rule.getDeps()) { |
| if (hasLocalBuildChain(dep, depth - 1)) { |
| return true; |
| } |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns {@code true} if none of the {@link BuildRuleSuccess} objects are built locally. |
| */ |
| private boolean shouldTryToFetchFromCache(BuildRule rule) { |
| |
| // If this rule explicitly disables caching, we won't try to fetch. |
| if (rule.getCacheMode() == CacheMode.DISABLED) { |
| return false; |
| } |
| |
| // Otherwise, look for a sequence of local builds, which we use as a heuristic to avoid |
| // fetching this rule from cache, since this will likely result in a miss. |
| if (skipLocalBuildDepth > 0) { |
| for (BuildRule dep : rule.getDeps()) { |
| if (hasLocalBuildChain(dep, skipLocalBuildDepth - 1)) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| private CacheResult tryToFetchArtifactFromBuildCacheAndOverlayOnTopOfProjectFilesystem( |
| BuildRule rule, |
| BuildInfoRecorder buildInfoRecorder, |
| ArtifactCache artifactCache, |
| Path projectRoot, |
| BuildContext buildContext) throws InterruptedException { |
| // Create a temp file whose extension must be ".zip" for Filesystems.newFileSystem() to infer |
| // that we are creating a zip-based FileSystem. |
| File zipFile; |
| try { |
| zipFile = File.createTempFile( |
| MoreFiles.sanitize(rule.getFullyQualifiedName()), |
| ".zip"); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| |
| // TODO(mbolin): Change ArtifactCache.fetch() so that it returns a File instead of takes one. |
| // Then we could download directly from Cassandra into the on-disk cache and unzip it from |
| // there. |
| CacheResult cacheResult = buildInfoRecorder.fetchArtifactForBuildable(zipFile, artifactCache); |
| if (!cacheResult.isSuccess()) { |
| zipFile.delete(); |
| return cacheResult; |
| } |
| |
| // We unzip the file in the root of the project directory. |
| // Ideally, the following would work: |
| // |
| // Path pathToZip = Paths.get(zipFile.getAbsolutePath()); |
| // FileSystem fs = FileSystems.newFileSystem(pathToZip, /* loader */ null); |
| // Path root = Iterables.getOnlyElement(fs.getRootDirectories()); |
| // MoreFiles.copyRecursively(root, projectRoot); |
| // |
| // Unfortunately, this does not appear to work, in practice, because MoreFiles fails when trying |
| // to resolve a Path for a zip entry against a file Path on disk. |
| |
| try { |
| Unzip.extractZipFile(zipFile.toPath().toAbsolutePath(), |
| projectRoot.toAbsolutePath(), |
| /* overwriteExistingFiles */ true); |
| } catch (IOException e) { |
| // In the wild, we have seen some inexplicable failures during this step. For now, we try to |
| // give the user as much information as we can to debug the issue, but return CacheResult.MISS |
| // so that Buck will fall back on doing a local build. |
| buildContext.getEventBus().post(ConsoleEvent.warning( |
| "Failed to unzip the artifact for %s at %s.\n" + |
| "The rule will be built locally, " + |
| "but here is the stacktrace of the failed unzip call:\n" + |
| rule.getBuildTarget(), |
| zipFile.getAbsolutePath(), |
| Throwables.getStackTraceAsString(e))); |
| return CacheResult.MISS; |
| } |
| |
| // We only delete the ZIP file when it has been unzipped successfully. Otherwise, we leave it |
| // around for debugging purposes. |
| zipFile.delete(); |
| return cacheResult; |
| } |
| |
| /** |
| * Execute the commands for this build rule. Requires all dependent rules are already built |
| * successfully. |
| */ |
| private void executeCommandsNowThatDepsAreBuilt( |
| BuildRule rule, |
| BuildContext context, |
| BuildInfoRecorder buildInfoRecorder) |
| throws Exception { |
| LOG.debug("Building locally: %s", rule); |
| |
| // Get and run all of the commands. |
| BuildableContext buildableContext = new DefaultBuildableContext( |
| buildInfoRecorder); |
| List<Step> steps = rule.getBuildSteps(context, buildableContext); |
| |
| AbiRule abiRule = checkIfRuleOrBuildableIsAbiRule(rule); |
| if (abiRule != null) { |
| buildableContext.addMetadata( |
| ABI_KEY_FOR_DEPS_ON_DISK_METADATA, |
| abiRule.getAbiKeyForDeps().getHash()); |
| } |
| |
| StepRunner stepRunner = context.getStepRunner(); |
| for (Step step : steps) { |
| stepRunner.runStepForBuildTarget(step, rule.getBuildTarget()); |
| |
| // Check for interruptions that may have been ignored by step. |
| if (Thread.interrupted()) { |
| Thread.currentThread().interrupt(); |
| throw new InterruptedException(); |
| } |
| } |
| |
| LOG.debug("Build completed: %s", rule); |
| } |
| |
| @VisibleForTesting |
| public void doHydrationAfterBuildStepsFinish( |
| BuildRule rule, |
| BuildResult result, |
| OnDiskBuildInfo onDiskBuildInfo) { |
| // Give the rule a chance to populate its internal data structures now that all of the |
| // files should be in a valid state. |
| InitializableFromDisk<?> initializable = deriveInitializable(rule); |
| if (initializable != null) { |
| doInitializeFromDisk(initializable, onDiskBuildInfo); |
| } |
| |
| // Only now that the rule should be in a completely valid state, resolve the future. |
| BuildRuleSuccess buildRuleSuccess = new BuildRuleSuccess(rule, result.getSuccess()); |
| results.get(rule.getBuildTarget()).set(buildRuleSuccess); |
| } |
| |
| /** |
| * We're moving to a world where BuildRule becomes Buildable. In many cases, that refactoring |
| * hasn't happened yet, and in that case "self" and self's Buildable are the same instance. We |
| * don't want to call initializeFromDisk more than once, so handle this case gracefully-ish. |
| */ |
| @Nullable |
| private InitializableFromDisk<?> deriveInitializable(BuildRule rule) { |
| if (rule instanceof InitializableFromDisk) { |
| return (InitializableFromDisk<?>) rule; |
| } |
| |
| return null; |
| } |
| |
| private <T> void doInitializeFromDisk(InitializableFromDisk<T> initializable, |
| OnDiskBuildInfo onDiskBuildInfo) { |
| BuildOutputInitializer<T> buildOutputInitializer = initializable.getBuildOutputInitializer(); |
| T buildOutput = buildOutputInitializer.initializeFromDisk(onDiskBuildInfo); |
| buildOutputInitializer.setBuildOutput(buildOutput); |
| } |
| |
| @Nullable |
| @Override |
| public BuildRuleSuccess getBuildRuleResult(BuildTarget buildTarget) |
| throws ExecutionException, InterruptedException { |
| SettableFuture<BuildRuleSuccess> result = results.get(buildTarget); |
| if (result == null) { |
| return null; |
| } |
| return result.get(); |
| } |
| |
| @Nullable |
| private AbiRule checkIfRuleOrBuildableIsAbiRule(BuildRule rule) { |
| if (rule instanceof AbiRule) { |
| return (AbiRule) rule; |
| } |
| return null; |
| } |
| } |