in src/com/facebook/buck/rules/AbstractCachingBuildRule.java [131:287]
public final ListenableFuture<BuildRuleSuccess> build(final BuildContext context) {
// We use hasBuildStarted as a lock so that we can minimize how much we need to synchronize.
synchronized(hasBuildStarted) {
if (hasBuildStarted.get()) {
return buildRuleResult;
} else {
hasBuildStarted.set(true);
}
}
// 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(getDeps().size());
for (BuildRule dep : getDeps()) {
builtDeps.add(dep.build(context));
}
ListenableFuture<List<BuildRuleSuccess>> allBuiltDeps = Futures.allAsList(builtDeps);
// Schedule this rule to build itself once all of the deps are built.
Futures.addCallback(allBuiltDeps,
new FutureCallback<List<BuildRuleSuccess>>() {
private final BuckEventBus eventBus = context.getEventBus();
private final OnDiskBuildInfo onDiskBuildInfo = context.createOnDiskBuildInfoFor(
getBuildTarget());
/**
* It is imperative that:
* <ol>
* <li>The {@link BuildInfoRecorder} is not constructed until all of the
* {@link Buildable}'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 Buildable}. This
* ensures that all build-related information for a {@link Buildable} 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() {
AbstractBuildRule buildRule = AbstractCachingBuildRule.this;
RuleKey ruleKey;
RuleKey ruleKeyWithoutDeps;
try {
ruleKey = buildRule.getRuleKey();
ruleKeyWithoutDeps = buildRule.getRuleKeyWithoutDeps();
} catch (IOException e) {
throw new RuntimeException(e);
}
return context.createBuildInfoRecorder(
buildRule.getBuildTarget(), ruleKey, ruleKeyWithoutDeps);
}
});
private boolean startOfBuildWasRecordedOnTheEventBus = false;
@Override
public void onSuccess(List<BuildRuleSuccess> deps) {
// Record the start of the build.
eventBus.post(BuildRuleEvent.started(AbstractCachingBuildRule.this));
startOfBuildWasRecordedOnTheEventBus = true;
try {
BuildResult result = buildOnceDepsAreBuilt(
context, onDiskBuildInfo, buildInfoRecorder.get());
if (result.isSuccess()) {
recordBuildRuleSuccess(result);
} else {
recordBuildRuleFailure(result);
}
} catch (IOException e) {
onFailure(e);
}
}
private void recordBuildRuleSuccess(BuildResult result) {
// Make sure that all of the local files have the same values they would as if the
// rule had been built locally.
if (result.success.shouldWriteRecordedMetadataToDiskAfterBuilding()) {
try {
buildInfoRecorder.get().writeMetadataToDisk();
} catch (IOException e) {
onFailure(e);
}
}
// Give the rule a chance to populate its internal data structures now that all of the
// files should be in a valid state.
if (result.success.shouldInitializeFromDiskAfterBuilding()) {
initializeFromDisk(onDiskBuildInfo);
}
// Only now that the rule should be in a completely valid state, resolve the future.
BuildRuleSuccess buildRuleSuccess = new BuildRuleSuccess(
AbstractCachingBuildRule.this, result.success);
buildRuleResult.set(buildRuleSuccess);
// Do the post to the event bus immediately after the future is set so that the
// build time measurement is as accurate as possible.
eventBus.post(BuildRuleEvent.finished(AbstractCachingBuildRule.this,
result.status,
result.cacheResult,
Optional.of(result.success)));
// Finally, upload to the artifact cache.
if (result.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 genfiles and metadata, as they are not guaranteed to be
// valid at this point?
// Note that startOfBuildWasRecordedOnTheEventBus will be false if onSuccess() was
// never invoked.
if (startOfBuildWasRecordedOnTheEventBus) {
eventBus.post(BuildRuleEvent.finished(AbstractCachingBuildRule.this,
result.status,
result.cacheResult,
Optional.<BuildRuleSuccess.Type>absent()));
}
// 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.
buildRuleResult.setException(result.failure);
}
},
context.getExecutor());
} 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.
buildRuleResult.setException(failure);
}
return buildRuleResult;
}