/*
* 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.distributed;
import com.facebook.buck.android.AndroidPlatformTarget;
import com.facebook.buck.cli.BuckConfig;
import com.facebook.buck.cli.MetadataChecker;
import com.facebook.buck.command.Build;
import com.facebook.buck.json.BuildFileParseException;
import com.facebook.buck.jvm.java.JavaBuckConfig;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.parser.BuildTargetParser;
import com.facebook.buck.parser.BuildTargetPatternParser;
import com.facebook.buck.parser.DefaultParserTargetNodeFactory;
import com.facebook.buck.parser.ParserTargetNodeFactory;
import com.facebook.buck.rules.ActionGraphAndResolver;
import com.facebook.buck.rules.CachingBuildEngine;
import com.facebook.buck.rules.CachingBuildEngineBuckConfig;
import com.facebook.buck.rules.Cell;
import com.facebook.buck.rules.CellPathResolver;
import com.facebook.buck.rules.RuleKeyDiagnosticsMode;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.TargetGraphAndBuildTargets;
import com.facebook.buck.rules.TargetNode;
import com.facebook.buck.rules.TargetNodeFactory;
import com.facebook.buck.rules.coercer.ConstructorArgMarshaller;
import com.facebook.buck.rules.coercer.DefaultTypeCoercerFactory;
import com.facebook.buck.rules.coercer.PathTypeCoercer;
import com.facebook.buck.rules.coercer.TypeCoercerFactory;
import com.facebook.buck.rules.keys.DefaultRuleKeyCache;
import com.facebook.buck.rules.keys.RuleKeyFactories;
import com.facebook.buck.step.DefaultStepRunner;
import com.facebook.buck.util.cache.DefaultFileHashCache;
import com.facebook.buck.util.cache.ProjectFileHashCache;
import com.facebook.buck.util.cache.StackedFileHashCache;
import com.facebook.buck.util.concurrent.ConcurrencyLimit;
import com.facebook.buck.versions.VersionException;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
public class DistBuildSlaveExecutor {
private static final Logger LOG = Logger.get(DistBuildSlaveExecutor.class);
private final DistBuildExecutorArgs args;
@Nullable private TargetGraph targetGraph;
@Nullable private ActionGraphAndResolver actionGraphAndResolver;
@Nullable private DistBuildCachingEngineDelegate cachingBuildEngineDelegate;
public DistBuildSlaveExecutor(DistBuildExecutorArgs args) {
this.args = args;
}
public int buildAndReturnExitCode() throws IOException, InterruptedException {
createBuildEngineDelegate();
LocalBuilder localBuilder = new LocalBuilderImpl();
DistBuildModeRunner runner = null;
switch (args.getDistBuildMode()) {
case REMOTE_BUILD:
runner =
new RemoteBuildModeRunner(
localBuilder, args.getState().getRemoteState().getTopLevelTargets());
break;
case COORDINATOR:
runner = newCoordinatorMode();
break;
case MINION:
runner = newMinionMode(localBuilder);
break;
case COORDINATOR_AND_MINION:
runner =
new CoordinatorAndMinionModeRunner(newCoordinatorMode(), newMinionMode(localBuilder));
break;
default:
LOG.error("Unknown distributed build mode [%s].", args.getDistBuildMode().toString());
return -1;
}
return runner.runAndReturnExitCode();
}
private MinionModeRunner newMinionMode(LocalBuilder localBuilder) {
return new MinionModeRunner(
args.getCoordinatorAddress(),
args.getCoordinatorPort(),
localBuilder,
args.getStampedeId());
}
private CoordinatorModeRunner newCoordinatorMode() {
BuildTargetsQueue queue =
BuildTargetsQueue.newQueue(
Preconditions.checkNotNull(actionGraphAndResolver).getResolver(),
fullyQualifiedNameToBuildTarget(args.getState().getRemoteState().getTopLevelTargets()));
return new CoordinatorModeRunner(args.getCoordinatorPort(), queue, args.getStampedeId());
}
private TargetGraph createTargetGraph() throws IOException, InterruptedException {
if (targetGraph != null) {
return targetGraph;
}
DistBuildTargetGraphCodec codec = createGraphCodec();
TargetGraphAndBuildTargets targetGraphAndBuildTargets =
Preconditions.checkNotNull(
codec.createTargetGraph(
args.getState().getRemoteState().getTargetGraph(),
Functions.forMap(args.getState().getCells())));
try {
if (args.getRemoteRootCellConfig().getBuildVersions()) {
targetGraph =
args.getVersionedTargetGraphCache()
.toVersionedTargetGraph(
args.getBuckEventBus(),
args.getRemoteRootCellConfig(),
new DefaultTypeCoercerFactory(
PathTypeCoercer.PathExistenceVerificationMode.DO_NOT_VERIFY),
targetGraphAndBuildTargets)
.getTargetGraph();
} else {
targetGraph = targetGraphAndBuildTargets.getTargetGraph();
}
} catch (VersionException e) {
throw new RuntimeException(e);
}
return targetGraph;
}
private ActionGraphAndResolver createActionGraphAndResolver()
throws IOException, InterruptedException {
if (actionGraphAndResolver != null) {
return actionGraphAndResolver;
}
createTargetGraph();
actionGraphAndResolver =
Preconditions.checkNotNull(
args.getActionGraphCache()
.getActionGraph(
args.getBuckEventBus(),
/* checkActionGraphs */ false,
/* skipActionGraphCache */ false,
Preconditions.checkNotNull(targetGraph),
args.getCacheKeySeed()));
return actionGraphAndResolver;
}
private DistBuildCachingEngineDelegate createBuildEngineDelegate()
throws IOException, InterruptedException {
if (cachingBuildEngineDelegate != null) {
return cachingBuildEngineDelegate;
}
StackedFileHashCaches caches = createStackedFileHashesAndPreload();
createActionGraphAndResolver();
SourcePathRuleFinder ruleFinder =
new SourcePathRuleFinder(Preconditions.checkNotNull(actionGraphAndResolver).getResolver());
cachingBuildEngineDelegate =
new DistBuildCachingEngineDelegate(
new SourcePathResolver(ruleFinder),
ruleFinder,
caches.remoteStateCache,
caches.materializingCache);
return cachingBuildEngineDelegate;
}
private StackedFileHashCache createStackOfDefaultFileHashCache()
throws InterruptedException, IOException {
ImmutableList.Builder<ProjectFileHashCache> allCachesBuilder = ImmutableList.builder();
Cell rootCell = args.getState().getRootCell();
// 1. Add all cells (including the root cell).
for (Path cellPath : rootCell.getKnownRoots()) {
Cell cell = rootCell.getCell(cellPath);
allCachesBuilder.add(DefaultFileHashCache.createDefaultFileHashCache(cell.getFilesystem()));
}
// 2. Add the Operating System roots.
allCachesBuilder.addAll(DefaultFileHashCache.createOsRootDirectoriesCaches());
return new StackedFileHashCache(allCachesBuilder.build());
}
private Supplier<AndroidPlatformTarget> getExplodingAndroidSupplier() {
return AndroidPlatformTarget.EXPLODING_ANDROID_PLATFORM_TARGET_SUPPLIER;
}
private List<BuildTarget> fullyQualifiedNameToBuildTarget(Iterable<String> buildTargets) {
List<BuildTarget> targets = new ArrayList<>();
CellPathResolver distBuildCellPathResolver =
args.getState().getRootCell().getCellPathResolver();
for (String fullyQualifiedBuildTarget : buildTargets) {
BuildTarget target =
BuildTargetParser.INSTANCE.parse(
fullyQualifiedBuildTarget,
BuildTargetPatternParser.fullyQualified(),
distBuildCellPathResolver);
targets.add(target);
}
return targets;
}
private DistBuildTargetGraphCodec createGraphCodec() {
TypeCoercerFactory typeCoercerFactory =
new DefaultTypeCoercerFactory(PathTypeCoercer.PathExistenceVerificationMode.DO_NOT_VERIFY);
ParserTargetNodeFactory<TargetNode<?, ?>> parserTargetNodeFactory =
DefaultParserTargetNodeFactory.createForDistributedBuild(
new ConstructorArgMarshaller(typeCoercerFactory),
new TargetNodeFactory(typeCoercerFactory));
DistBuildTargetGraphCodec targetGraphCodec =
new DistBuildTargetGraphCodec(
parserTargetNodeFactory,
new Function<TargetNode<?, ?>, Map<String, Object>>() {
@Nullable
@Override
public Map<String, Object> apply(TargetNode<?, ?> input) {
try {
return args.getParser()
.getRawTargetNode(
args.getBuckEventBus(),
args.getRootCell().getCell(input.getBuildTarget()),
/* enableProfiling */ false,
args.getExecutorService(),
input);
} catch (BuildFileParseException e) {
throw new RuntimeException(e);
}
}
},
new HashSet<>(args.getState().getRemoteState().getTopLevelTargets()));
return targetGraphCodec;
}
private class LocalBuilderImpl implements LocalBuilder {
private final BuckConfig distBuildConfig;
private final CachingBuildEngineBuckConfig engineConfig;
public LocalBuilderImpl() {
this.distBuildConfig = args.getRemoteRootCellConfig();
this.engineConfig = distBuildConfig.getView(CachingBuildEngineBuckConfig.class);
}
@Override
public int buildLocallyAndReturnExitCode(Iterable<String> targetsToBuild)
throws IOException, InterruptedException {
// TODO(ruibm): Fix this to work with Android.
MetadataChecker.checkAndCleanIfNeeded(args.getRootCell());
try (CachingBuildEngine buildEngine =
new CachingBuildEngine(
Preconditions.checkNotNull(cachingBuildEngineDelegate),
args.getExecutorService(),
args.getExecutorService(),
new DefaultStepRunner(),
engineConfig.getBuildEngineMode(),
engineConfig.getBuildMetadataStorage(),
engineConfig.getBuildDepFiles(),
engineConfig.getBuildMaxDepFileCacheEntries(),
engineConfig.getBuildArtifactCacheSizeLimit(),
Preconditions.checkNotNull(actionGraphAndResolver).getResolver(),
args.getBuildInfoStoreManager(),
engineConfig.getResourceAwareSchedulingInfo(),
RuleKeyFactories.of(
distBuildConfig.getKeySeed(),
cachingBuildEngineDelegate.getFileHashCache(),
actionGraphAndResolver.getResolver(),
engineConfig.getBuildInputRuleKeyFileSizeLimit(),
new DefaultRuleKeyCache<>()));
Build build =
new Build(
Preconditions.checkNotNull(actionGraphAndResolver).getActionGraph(),
Preconditions.checkNotNull(actionGraphAndResolver).getResolver(),
args.getRootCell(),
Optional.empty(),
getExplodingAndroidSupplier(),
buildEngine,
args.getArtifactCache(),
distBuildConfig.getView(JavaBuckConfig.class).createDefaultJavaPackageFinder(),
args.getConsole(),
/* defaultTestTimeoutMillis */ 1000,
/* isCodeCoverageEnabled */ false,
/* isInclNoLocationClassesEnabled */ false,
/* isDebugEnabled */ false,
/* shouldReportAbsolutePaths */ false,
RuleKeyDiagnosticsMode.NEVER,
args.getBuckEventBus(),
args.getPlatform(),
ImmutableMap.of(),
args.getClock(),
new ConcurrencyLimit(
4,
distBuildConfig.getResourceAllocationFairness(),
4,
distBuildConfig.getDefaultResourceAmounts(),
distBuildConfig.getMaximumResourceAmounts().withCpu(4)),
Optional.empty(),
Optional.empty(),
Optional.empty(),
args.getExecutors())) {
return build.executeAndPrintFailuresToEventBus(
fullyQualifiedNameToBuildTarget(targetsToBuild),
/* isKeepGoing */ true,
args.getBuckEventBus(),
args.getConsole(),
Optional.empty());
}
}
}
private static class StackedFileHashCaches {
public final StackedFileHashCache remoteStateCache;
public final StackedFileHashCache materializingCache;
private StackedFileHashCaches(
StackedFileHashCache remoteStateCache, StackedFileHashCache materializingCache) {
this.remoteStateCache = remoteStateCache;
this.materializingCache = materializingCache;
}
}
private StackedFileHashCaches createStackedFileHashesAndPreload()
throws InterruptedException, IOException {
StackedFileHashCache stackedFileHashCache = createStackOfDefaultFileHashCache();
// Used for rule key computations.
StackedFileHashCache remoteStackedFileHashCache =
stackedFileHashCache.newDecoratedFileHashCache(
cache -> args.getState().createRemoteFileHashCache(cache));
// Used for the real build.
StackedFileHashCache materializingStackedFileHashCache =
stackedFileHashCache.newDecoratedFileHashCache(
cache -> {
try {
return args.getState().createMaterializerAndPreload(cache, args.getProvider());
} catch (IOException exception) {
throw new RuntimeException(
String.format(
"Failed to create the Materializer for file system [%s]",
cache.getFilesystem()),
exception);
}
});
return new StackedFileHashCaches(remoteStackedFileHashCache, materializingStackedFileHashCache);
}
}