// Dispatch a job for the given rule (if we haven't already) and return a future tracking it's // result. private synchronized ListenableFuture<BuildResult> getBuildRuleResult( final BuildRule rule, final BuildContext context, final ConcurrentLinkedQueue<ListenableFuture<Void>> asyncCallbacks) { // If the rule is already executing, return it's result future from the cache. Optional<ListenableFuture<BuildResult>> existingResult = Optional.fromNullable(results.get(rule.getBuildTarget())); if (existingResult.isPresent()) { return existingResult.get(); } // Otherwise submit a new job for this rule, cache the future, and return it. ListenableFuture<RuleKey> ruleKey = calculateRuleKey(rule, context); ListenableFuture<BuildResult> result = Futures.transform( ruleKey, new AsyncFunction<RuleKey, BuildResult>() { @Override public ListenableFuture<BuildResult> apply(@Nonnull RuleKey input) throws Exception { return processBuildRule(rule, context, asyncCallbacks); } }, service); results.put(rule.getBuildTarget(), result); return result; }
@Test public void upperBoundGenericTypesCauseValuesToBeSetToTheUpperBound() throws ConstructorArgMarshalException, NoSuchBuildTargetException { class Dto { public List<? extends SourcePath> yup; } ProjectFilesystem projectFilesystem = new FakeProjectFilesystem(); SourcePathResolver pathResolver = new SourcePathResolver(new BuildRuleResolver()); BuildRule rule = new FakeBuildRule(BuildTargetFactory.newInstance("//will:happen"), pathResolver); ruleResolver.addToIndex(rule); Dto dto = new Dto(); marshaller.populate( createCellRoots(filesystem), filesystem, buildRuleFactoryParams(), dto, ImmutableSet.<BuildTarget>builder(), ImmutableSet.<BuildTargetPattern>builder(), ImmutableMap.<String, Object>of( "yup", ImmutableList.of(rule.getBuildTarget().getFullyQualifiedName()))); BuildTargetSourcePath path = new BuildTargetSourcePath(rule.getBuildTarget()); assertEquals(ImmutableList.of(path), dto.yup); }
public Builder set(String key, @Nullable ImmutableSortedSet<BuildRule> val) { setKey(key); if (val != null) { for (BuildRule buildRule : val) { setVal(buildRule.getRuleKey()); } } return separate(); }
// Dispatch and return a future resolving to a list of all results of this rules dependencies. private ListenableFuture<List<BuildResult>> getDepResults( BuildRule rule, BuildContext context, ConcurrentLinkedQueue<ListenableFuture<Void>> asyncCallbacks) { List<ListenableFuture<BuildResult>> depResults = Lists.newArrayListWithExpectedSize(rule.getDeps().size()); for (BuildRule dep : rule.getDeps()) { depResults.add(getBuildRuleResultWithRuntimeDeps(dep, context, asyncCallbacks)); } return Futures.allAsList(depResults); }
public static Builder builder(BuildRule rule) { return new Builder(rule.getFullyQualifiedName()) // Keyed as "buck.type" rather than "type" in case a build rule has its own "type" argument. .set("buck.type", rule.getType().getDisplayName()) .setStrings( "deps", Iterables.transform( rule.getDeps(), new Function<BuildRule, String>() { @Override public String apply(BuildRule input) { return String.format( "%s:%s", input.getFullyQualifiedName(), input.getType().getDisplayName()); } })); }
@VisibleForTesting void setBuildRuleResult( BuildRule buildRule, BuildRuleSuccessType success, CacheResult cacheResult) { results.put( buildRule.getBuildTarget(), Futures.immediateFuture(BuildResult.success(buildRule, success, cacheResult))); }
/** Ensure that build rules with the same inputs but different deps have unique RuleKeys. */ @Test public void testRuleKeyDependsOnDeps() throws Exception { FakeProjectFilesystem filesystem = new FakeProjectFilesystem(); FileHashCache hashCache = new DefaultFileHashCache(filesystem); BuildRuleResolver ruleResolver1 = new BuildRuleResolver(TargetGraph.EMPTY, new BuildTargetNodeToBuildRuleTransformer()); BuildRuleResolver ruleResolver2 = new BuildRuleResolver(TargetGraph.EMPTY, new BuildTargetNodeToBuildRuleTransformer()); RuleKeyBuilderFactory ruleKeyBuilderFactory1 = new DefaultRuleKeyBuilderFactory(hashCache, new SourcePathResolver(ruleResolver1)); RuleKeyBuilderFactory ruleKeyBuilderFactory2 = new DefaultRuleKeyBuilderFactory(hashCache, new SourcePathResolver(ruleResolver2)); // Create a dependent build rule, //src/com/facebook/buck/cli:common. JavaLibraryBuilder builder = JavaLibraryBuilder.createBuilder( BuildTargetFactory.newInstance("//src/com/facebook/buck/cli:common")); BuildRule commonJavaLibrary = builder.build(ruleResolver1); builder.build(ruleResolver2); // Create a java_library() rule with no deps. Path mainSrc = Paths.get("src/com/facebook/buck/cli/Main.java"); filesystem.mkdirs(mainSrc.getParent()); filesystem.writeContentsToPath("hello", mainSrc); JavaLibraryBuilder javaLibraryBuilder = JavaLibraryBuilder.createBuilder( BuildTargetFactory.newInstance("//src/com/facebook/buck/cli:cli")) .addSrc(mainSrc); BuildRule libraryNoCommon = javaLibraryBuilder.build(ruleResolver1, filesystem); // Create the same java_library() rule, but with a dep on //src/com/facebook/buck/cli:common. javaLibraryBuilder.addDep(commonJavaLibrary.getBuildTarget()); BuildRule libraryWithCommon = javaLibraryBuilder.build(ruleResolver2, filesystem); // Assert that the RuleKeys are distinct. RuleKey r1 = ruleKeyBuilderFactory1.build(libraryNoCommon); RuleKey r2 = ruleKeyBuilderFactory2.build(libraryWithCommon); assertThat( "Rule keys should be distinct because the deps of the rules are different.", r1, not(equalTo(r2))); }
/** * Execute the commands for this build rule. Requires all dependent rules are already built * successfully. */ private void executeCommandsNowThatDepsAreBuilt( BuildRule rule, BuildContext context, BuildableContext buildableContext, BuildInfoRecorder buildInfoRecorder) throws InterruptedException, StepFailedException { LOG.debug("Building locally: %s", rule); // Attempt to get an approximation of how long it takes to actually run the command. @SuppressWarnings("PMD.PrematureDeclaration") long start = System.nanoTime(); // Get and run all of the commands. List<Step> steps = rule.getBuildSteps(context, buildableContext); AbiRule abiRule = checkIfRuleOrBuildableIsAbiRule(rule); if (abiRule != null) { buildInfoRecorder.addBuildMetadata( ABI_KEY_FOR_DEPS_ON_DISK_METADATA, abiRule.getAbiKeyForDeps().getHash()); } StepRunner stepRunner = context.getStepRunner(); Optional<BuildTarget> optionalTarget = Optional.of(rule.getBuildTarget()); for (Step step : steps) { stepRunner.runStepForBuildTarget(step, optionalTarget); // Check for interruptions that may have been ignored by step. if (Thread.interrupted()) { Thread.currentThread().interrupt(); throw new InterruptedException(); } } long end = System.nanoTime(); LOG.debug( "Build completed: %s %s (%dns)", rule.getType(), rule.getFullyQualifiedName(), end - start); }
private synchronized ListenableFuture<RuleKey> calculateRuleKey( final BuildRule rule, final BuildContext context) { ListenableFuture<RuleKey> ruleKey = ruleKeys.get(rule.getBuildTarget()); if (ruleKey == null) { // Grab all the dependency rule key futures. Since our rule key calculation depends on this // one, we need to wait for them to complete. List<ListenableFuture<RuleKey>> depKeys = Lists.newArrayListWithExpectedSize(rule.getDeps().size()); for (BuildRule dep : rule.getDeps()) { depKeys.add(calculateRuleKey(dep, context)); } // Setup a future to calculate this rule key once the dependencies have been calculated. ruleKey = Futures.transform( Futures.allAsList(depKeys), new Function<List<RuleKey>, RuleKey>() { @Override public RuleKey apply(List<RuleKey> input) { context.getEventBus().logVerboseAndPost(LOG, BuildRuleEvent.started(rule)); try { return rule.getRuleKey(); } finally { context.getEventBus().logVerboseAndPost(LOG, BuildRuleEvent.suspended(rule)); } } }, service); // Record the rule key future. ruleKeys.put(rule.getBuildTarget(), ruleKey); } return ruleKey; }
private void executePostBuildSteps( BuildRule rule, Iterable<Step> postBuildSteps, BuildContext context) throws InterruptedException, StepFailedException { LOG.debug("Running post-build steps for %s", rule); StepRunner stepRunner = context.getStepRunner(); Optional<BuildTarget> optionalTarget = Optional.of(rule.getBuildTarget()); for (Step step : postBuildSteps) { stepRunner.runStepForBuildTarget(step, optionalTarget); // Check for interruptions that may have been ignored by step. if (Thread.interrupted()) { Thread.currentThread().interrupt(); throw new InterruptedException(); } } LOG.debug("Finished running post-build steps for %s", rule); }
public Builder set(String key, @Nullable BuildRule val) { return setKey(key).setVal(val != null ? val.getRuleKey() : null); }
@Override public String toString() { return rule.getFullyQualifiedName(); }
private CacheResult tryToFetchArtifactFromBuildCacheAndOverlayOnTopOfProjectFilesystem( BuildRule rule, RuleKey ruleKey, BuildInfoRecorder buildInfoRecorder, ArtifactCache artifactCache, ProjectFilesystem filesystem, 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. Path zipFile; try { zipFile = Files.createTempFile( "buck_artifact_" + MoreFiles.sanitize(rule.getBuildTarget().getShortName()), ".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 the remote cache into the on-disk cache and unzip it // from there. CacheResult cacheResult = buildInfoRecorder.fetchArtifactForBuildable(ruleKey, zipFile, artifactCache); if (!cacheResult.getType().isSuccess()) { try { Files.delete(zipFile); } catch (IOException e) { LOG.warn(e, "failed to delete %s", zipFile); } 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. ArtifactCacheEvent.Started started = ArtifactCacheEvent.started( ArtifactCacheEvent.Operation.DECOMPRESS, ImmutableSet.of(ruleKey)); buildContext.getEventBus().post(started); try { Unzip.extractZipFile( zipFile.toAbsolutePath(), filesystem, Unzip.ExistingFileMode.OVERWRITE_AND_CLEAN_DIRECTORIES); // We only delete the ZIP file when it has been unzipped successfully. Otherwise, we leave it // around for debugging purposes. Files.delete(zipFile); if (cacheResult.getType() == CacheResult.Type.HIT) { // If we have a hit, also write out the build metadata. Path metadataDir = BuildInfo.getPathToMetadataDirectory(rule.getBuildTarget()); for (Map.Entry<String, String> ent : cacheResult.getMetadata().entrySet()) { Path dest = metadataDir.resolve(ent.getKey()); filesystem.createParentDirs(dest); filesystem.writeContentsToPath(ent.getValue(), dest); } } } 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.toAbsolutePath(), Throwables.getStackTraceAsString(e))); return CacheResult.miss(); } finally { buildContext.getEventBus().post(ArtifactCacheEvent.finished(started)); } return cacheResult; }
private ListenableFuture<BuildResult> processBuildRule( final BuildRule rule, final BuildContext context, ConcurrentLinkedQueue<ListenableFuture<Void>> asyncCallbacks) throws InterruptedException { // Log to the event bus. context.getEventBus().logVerboseAndPost(LOG, BuildRuleEvent.resumed(rule)); final OnDiskBuildInfo onDiskBuildInfo = context.createOnDiskBuildInfoFor(rule.getBuildTarget()); final BuildInfoRecorder buildInfoRecorder = context .createBuildInfoRecorder(rule.getBuildTarget()) .addBuildMetadata(BuildInfo.METADATA_KEY_FOR_RULE_KEY, rule.getRuleKey().toString()) .addBuildMetadata( BuildInfo.METADATA_KEY_FOR_RULE_KEY_WITHOUT_DEPS, rule.getRuleKeyWithoutDeps().toString()); final BuildableContext buildableContext = new DefaultBuildableContext(buildInfoRecorder); // Dispatch the build job for this rule. ListenableFuture<BuildResult> buildResult = processBuildRule( rule, context, onDiskBuildInfo, buildInfoRecorder, buildableContext, asyncCallbacks); // If we're performing a deep build, guarantee that all dependencies will *always* get // materialized locally by chaining up to our result future. if (buildMode == BuildMode.DEEP) { buildResult = MoreFutures.chainExceptions(getDepResults(rule, context, asyncCallbacks), buildResult); } // Setup a callback to handle either the cached or built locally cases. AsyncFunction<BuildResult, BuildResult> callback = new AsyncFunction<BuildResult, BuildResult>() { @Override public ListenableFuture<BuildResult> apply(@Nonnull BuildResult input) throws Exception { // If we weren't successful, exit now. if (input.getStatus() != BuildRuleStatus.SUCCESS) { return Futures.immediateFuture(input); } // We shouldn't see any build fail result at this point. BuildRuleSuccessType success = Preconditions.checkNotNull(input.getSuccess()); // If we didn't build the rule locally, reload the recorded paths from the build // metadata. if (success != BuildRuleSuccessType.BUILT_LOCALLY) { for (String str : onDiskBuildInfo.getValues(BuildInfo.METADATA_KEY_FOR_RECORDED_PATHS).get()) { buildInfoRecorder.recordArtifact(Paths.get(str)); } } // If the success type means the rule has potentially changed it's outputs... if (success.outputsHaveChanged()) { // The build has succeeded, whether we've fetched from cache, or built locally. // So run the post-build steps. if (rule instanceof HasPostBuildSteps) { executePostBuildSteps( rule, ((HasPostBuildSteps) rule).getPostBuildSteps(context, buildableContext), context); } // Invalidate any cached hashes for the output paths, since we've updated them. for (Path path : buildInfoRecorder.getRecordedPaths()) { fileHashCache.invalidate(path); } } // If this rule uses dep files and we built locally, make sure we store the new dep file // list and re-calculate the dep file rule key. if (useDependencyFileRuleKey(rule) && success == BuildRuleSuccessType.BUILT_LOCALLY) { // Query the rule for the actual inputs it used, and verify these are relative. ImmutableList<Path> inputs = ((SupportsDependencyFileRuleKey) rule).getInputsAfterBuildingLocally(); for (Path path : inputs) { Preconditions.checkState( !path.isAbsolute(), String.format( "%s: reported absolute path as an input: %s", rule.getBuildTarget(), path)); } // Record the inputs into our metadata for next time. ImmutableList<String> inputStrings = FluentIterable.from(inputs).transform(Functions.toStringFunction()).toList(); buildInfoRecorder.addMetadata(BuildInfo.METADATA_KEY_FOR_DEP_FILE, inputStrings); // Re-calculate and store the depfile rule key for next time. Optional<RuleKey> depFileRuleKey = calculateDepFileRuleKey( rule, Optional.of(inputStrings), /* allowMissingInputs */ false); Preconditions.checkState(depFileRuleKey.isPresent()); buildInfoRecorder.addBuildMetadata( BuildInfo.METADATA_KEY_FOR_DEP_FILE_RULE_KEY, depFileRuleKey.get().toString()); } // Make sure that all of the local files have the same values they would as if the // rule had been built locally. buildInfoRecorder.addBuildMetadata( BuildInfo.METADATA_KEY_FOR_TARGET, rule.getBuildTarget().toString()); buildInfoRecorder.addMetadata( BuildInfo.METADATA_KEY_FOR_RECORDED_PATHS, FluentIterable.from(buildInfoRecorder.getRecordedPaths()) .transform(Functions.toStringFunction())); if (success.shouldWriteRecordedMetadataToDiskAfterBuilding()) { try { boolean clearExistingMetadata = success.shouldClearAndOverwriteMetadataOnDisk(); buildInfoRecorder.writeMetadataToDisk(clearExistingMetadata); } catch (IOException e) { throw new IOException( String.format("Failed to write metadata to disk for %s.", rule), e); } } // Give the rule a chance to populate its internal data structures now that all of // the files should be in a valid state. try { if (rule instanceof InitializableFromDisk) { doInitializeFromDisk((InitializableFromDisk<?>) rule, onDiskBuildInfo); } } catch (IOException e) { throw new IOException(String.format("Error initializing %s from disk.", rule), e); } return Futures.immediateFuture(input); } }; buildResult = Futures.transform(buildResult, callback); // Handle either build success or failure. final SettableFuture<BuildResult> result = SettableFuture.create(); asyncCallbacks.add( MoreFutures.addListenableCallback( buildResult, new FutureCallback<BuildResult>() { // TODO(mbolin): Delete all files produced by the rule, as they are not guaranteed // to be valid at this point? private void cleanupAfterError() { try { onDiskBuildInfo.deleteExistingMetadata(); } catch (Throwable t) { context .getEventBus() .post( ThrowableConsoleEvent.create( t, "Error when deleting metadata for %s.", rule)); } } private void uploadToCache(BuildRuleSuccessType success) { // Collect up all the rule keys we have index the artifact in the cache with. Set<RuleKey> ruleKeys = Sets.newHashSet(); // If the rule key has changed (and is not already in the cache), we need to push // the artifact to cache using the new key. if (success.shouldUploadResultingArtifact()) { ruleKeys.add(rule.getRuleKey()); } // If the input-based rule key has changed, we need to push the artifact to cache // using the new key. if (rule instanceof SupportsInputBasedRuleKey && success.shouldUploadResultingArtifactInputBased()) { ruleKeys.add( onDiskBuildInfo .getRuleKey(BuildInfo.METADATA_KEY_FOR_INPUT_BASED_RULE_KEY) .get()); } // If we have any rule keys to push to the cache with, do the upload now. if (!ruleKeys.isEmpty()) { try { buildInfoRecorder.performUploadToArtifactCache( ImmutableSet.copyOf(ruleKeys), context.getArtifactCache(), context.getEventBus()); } catch (Throwable t) { context .getEventBus() .post( ThrowableConsoleEvent.create( t, "Error uploading to cache for %s.", rule)); } } } private void handleResult(BuildResult input) { Optional<Long> outputSize = Optional.absent(); Optional<HashCode> outputHash = Optional.absent(); Optional<BuildRuleSuccessType> successType = Optional.absent(); if (input.getStatus() == BuildRuleStatus.FAIL) { // Make this failure visible for other rules, so that they can stop early. firstFailure = input.getFailure(); // If we failed, cleanup the state of this rule. cleanupAfterError(); } // Unblock dependents. result.set(input); if (input.getStatus() == BuildRuleStatus.SUCCESS) { BuildRuleSuccessType success = Preconditions.checkNotNull(input.getSuccess()); successType = Optional.of(success); uploadToCache(success); // Calculate the hash and size of the rule outputs. try { outputSize = Optional.of(buildInfoRecorder.getOutputSize()); outputHash = Optional.of(buildInfoRecorder.getOutputHash(fileHashCache)); } catch (IOException e) { context .getEventBus() .post( ThrowableConsoleEvent.create( e, "Error getting output hash and size for %s.", rule)); } } // Log the result to the event bus. context .getEventBus() .logVerboseAndPost( LOG, BuildRuleEvent.finished( rule, input.getStatus(), input.getCacheResult(), successType, outputHash, outputSize)); } @Override public void onSuccess(BuildResult input) { handleResult(input); } @Override public void onFailure(@Nonnull Throwable thrown) { handleResult(BuildResult.failure(rule, thrown)); // Reset interrupted flag once failure has been recorded. if (thrown instanceof InterruptedException) { Thread.currentThread().interrupt(); } } })); return result; }
private ListenableFuture<BuildResult> processBuildRule( final BuildRule rule, final BuildContext context, final OnDiskBuildInfo onDiskBuildInfo, final BuildInfoRecorder buildInfoRecorder, final BuildableContext buildableContext, ConcurrentLinkedQueue<ListenableFuture<Void>> asyncCallbacks) throws InterruptedException { // If we've already seen a failure, exit early. if (!context.isKeepGoing() && firstFailure != null) { return Futures.immediateFuture(BuildResult.canceled(rule, firstFailure)); } // 1. Check if it's already built. Optional<RuleKey> cachedRuleKey = onDiskBuildInfo.getRuleKey(BuildInfo.METADATA_KEY_FOR_RULE_KEY); if (cachedRuleKey.isPresent() && rule.getRuleKey().equals(cachedRuleKey.get())) { return Futures.immediateFuture( BuildResult.success( rule, BuildRuleSuccessType.MATCHING_RULE_KEY, CacheResult.localKeyUnchangedHit())); } // 2. Rule key cache lookup. final CacheResult cacheResult = tryToFetchArtifactFromBuildCacheAndOverlayOnTopOfProjectFilesystem( rule, rule.getRuleKey(), buildInfoRecorder, context.getArtifactCache(), context.getProjectFilesystem(), context); if (cacheResult.getType().isSuccess()) { return Futures.immediateFuture( BuildResult.success(rule, BuildRuleSuccessType.FETCHED_FROM_CACHE, cacheResult)); } // Log to the event bus. context.getEventBus().logVerboseAndPost(LOG, BuildRuleEvent.suspended(rule)); // 3. Build deps. return Futures.transform( getDepResults(rule, context, asyncCallbacks), new AsyncFunction<List<BuildResult>, BuildResult>() { @Override public ListenableFuture<BuildResult> apply(@Nonnull List<BuildResult> depResults) throws Exception { // Log to the event bus. context.getEventBus().logVerboseAndPost(LOG, BuildRuleEvent.resumed(rule)); // If any dependency wasn't successful, cancel ourselves. for (BuildResult depResult : depResults) { if (depResult.getStatus() != BuildRuleStatus.SUCCESS) { return Futures.immediateFuture( BuildResult.canceled(rule, Preconditions.checkNotNull(depResult.getFailure()))); } } // If we've already seen a failure, exit early. if (!context.isKeepGoing() && firstFailure != null) { return Futures.immediateFuture(BuildResult.canceled(rule, firstFailure)); } // Dep-file rule keys. if (useDependencyFileRuleKey(rule)) { // Try to get the current dep-file rule key. Optional<RuleKey> depFileRuleKey = calculateDepFileRuleKey( rule, onDiskBuildInfo.getValues(BuildInfo.METADATA_KEY_FOR_DEP_FILE), /* allowMissingInputs */ true); if (depFileRuleKey.isPresent()) { // Check the input-based rule key says we're already built. Optional<RuleKey> lastDepFileRuleKey = onDiskBuildInfo.getRuleKey(BuildInfo.METADATA_KEY_FOR_DEP_FILE_RULE_KEY); if (lastDepFileRuleKey.isPresent() && lastDepFileRuleKey.get().equals(depFileRuleKey.get())) { return Futures.immediateFuture( BuildResult.success( rule, BuildRuleSuccessType.MATCHING_DEP_FILE_RULE_KEY, CacheResult.localKeyUnchangedHit())); } } } // Input-based rule keys. if (rule instanceof SupportsInputBasedRuleKey) { // Calculate the input-based rule key and record it in the metadata. RuleKey inputRuleKey = inputBasedRuleKeyBuilderFactory.newInstance(rule).build(); buildInfoRecorder.addBuildMetadata( BuildInfo.METADATA_KEY_FOR_INPUT_BASED_RULE_KEY, inputRuleKey.toString()); // Check the input-based rule key says we're already built. Optional<RuleKey> lastInputRuleKey = onDiskBuildInfo.getRuleKey(BuildInfo.METADATA_KEY_FOR_INPUT_BASED_RULE_KEY); if (lastInputRuleKey.isPresent() && lastInputRuleKey.get().equals(inputRuleKey)) { return Futures.immediateFuture( BuildResult.success( rule, BuildRuleSuccessType.MATCHING_INPUT_BASED_RULE_KEY, CacheResult.localKeyUnchangedHit())); } // Try to fetch the artifact using the input-based rule key. CacheResult cacheResult = tryToFetchArtifactFromBuildCacheAndOverlayOnTopOfProjectFilesystem( rule, inputRuleKey, buildInfoRecorder, context.getArtifactCache(), context.getProjectFilesystem(), context); if (cacheResult.getType().isSuccess()) { return Futures.immediateFuture( BuildResult.success( rule, BuildRuleSuccessType.FETCHED_FROM_CACHE_INPUT_BASED, cacheResult)); } } // 4. ABI check // 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.getRuleKey(BuildInfo.METADATA_KEY_FOR_RULE_KEY_WITHOUT_DEPS); 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 Futures.immediateFuture( BuildResult.success( rule, BuildRuleSuccessType.MATCHING_DEPS_ABI_AND_RULE_KEY_NO_DEPS, AbstractCacheResult.localKeyUnchangedHit())); } } } // 5. build the rule executeCommandsNowThatDepsAreBuilt(rule, context, buildableContext, buildInfoRecorder); return Futures.immediateFuture( BuildResult.success(rule, BuildRuleSuccessType.BUILT_LOCALLY, cacheResult)); } }, service); }