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.apple.project_generator; import com.facebook.buck.apple.AppleBinaryDescription; import com.facebook.buck.apple.AppleBundleDescription; import com.facebook.buck.apple.AppleConfig; import com.facebook.buck.apple.AppleLibraryDescription; import com.facebook.buck.apple.XcodeWorkspaceConfigDescription; import com.facebook.buck.apple.XcodeWorkspaceConfigDescriptionArg; import com.facebook.buck.cli.BuckConfig; import com.facebook.buck.cli.ProjectTestsMode; import com.facebook.buck.cxx.CxxBuckConfig; import com.facebook.buck.cxx.CxxPlatform; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.event.ConsoleEvent; import com.facebook.buck.graph.AbstractBottomUpTraversal; import com.facebook.buck.halide.HalideBuckConfig; import com.facebook.buck.json.BuildFileParseException; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetException; import com.facebook.buck.model.UnflavoredBuildTarget; import com.facebook.buck.parser.BuildFileSpec; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.parser.Parser; import com.facebook.buck.parser.ParserConfig; import com.facebook.buck.parser.SpeculativeParsing; import com.facebook.buck.parser.TargetNodePredicateSpec; import com.facebook.buck.parser.TargetNodeSpec; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.Cell; import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.rules.Description; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.rules.TargetGraphAndTargets; import com.facebook.buck.rules.TargetNode; import com.facebook.buck.swift.SwiftBuckConfig; import com.facebook.buck.util.Console; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.MoreExceptions; import com.facebook.buck.util.ProcessManager; import com.facebook.buck.util.RichStream; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListeningExecutorService; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import javax.annotation.concurrent.ThreadSafe; public class XCodeProjectCommandHelper { private static final Logger LOG = Logger.get(XCodeProjectCommandHelper.class); private static final String XCODE_PROCESS_NAME = "Xcode"; private final BuckEventBus buckEventBus; private final Parser parser; private final BuckConfig buckConfig; private final Cell cell; private final Console console; private final Optional<ProcessManager> processManager; private final ImmutableMap<String, String> environment; private final ListeningExecutorService executorService; private final List<String> arguments; private final boolean enableParserProfiling; private final boolean withTests; private final boolean withoutTests; private final boolean withoutDependenciesTests; private final String modulesToFocusOn; private final boolean combinedProject; private final boolean dryRun; private final boolean readOnly; private final Function<Iterable<String>, ImmutableList<TargetNodeSpec>> argsParser; private final Function<ImmutableList<String>, Integer> buildRunner; public XCodeProjectCommandHelper(BuckEventBus buckEventBus, Parser parser, BuckConfig buckConfig, Cell cell, Console console, Optional<ProcessManager> processManager, ImmutableMap<String, String> environment, ListeningExecutorService executorService, List<String> arguments, boolean enableParserProfiling, boolean withTests, boolean withoutTests, boolean withoutDependenciesTests, String modulesToFocusOn, boolean combinedProject, boolean dryRun, boolean readOnly, Function<Iterable<String>, ImmutableList<TargetNodeSpec>> argsParser, Function<ImmutableList<String>, Integer> buildRunner) { this.buckEventBus = buckEventBus; this.parser = parser; this.buckConfig = buckConfig; this.cell = cell; this.console = console; this.processManager = processManager; this.environment = environment; this.executorService = executorService; this.arguments = arguments; this.enableParserProfiling = enableParserProfiling; this.withTests = withTests; this.withoutTests = withoutTests; this.withoutDependenciesTests = withoutDependenciesTests; this.modulesToFocusOn = modulesToFocusOn; this.combinedProject = combinedProject; this.dryRun = dryRun; this.readOnly = readOnly; this.argsParser = argsParser; this.buildRunner = buildRunner; } public int parseTargetsAndRunXCodeGenerator(ListeningExecutorService executor) throws IOException, InterruptedException { ImmutableSet<BuildTarget> passedInTargetsSet; TargetGraph projectGraph; try { ParserConfig parserConfig = buckConfig.getView(ParserConfig.class); passedInTargetsSet = ImmutableSet.copyOf(Iterables.concat(parser.resolveTargetSpecs(buckEventBus, cell, enableParserProfiling, executor, argsParser.apply(arguments), SpeculativeParsing.of(true), parserConfig.getDefaultFlavorsMode()))); projectGraph = getProjectGraphForIde(executor, passedInTargetsSet); } catch (BuildTargetException | BuildFileParseException | HumanReadableException e) { buckEventBus.post(ConsoleEvent.severe(MoreExceptions.getHumanReadableOrLocalizedMessage(e))); return 1; } checkForAndKillXcodeIfRunning(getIdePrompt(buckConfig)); ImmutableSet<BuildTarget> graphRoots; if (passedInTargetsSet.isEmpty()) { graphRoots = getRootsFromPredicate(projectGraph, node -> node.getDescription() instanceof XcodeWorkspaceConfigDescription); } else { graphRoots = passedInTargetsSet; } TargetGraphAndTargets targetGraphAndTargets; try { targetGraphAndTargets = createTargetGraph(projectGraph, graphRoots, isWithTests(buckConfig), isWithDependenciesTests(buckConfig), passedInTargetsSet.isEmpty(), executor); } catch (BuildFileParseException | TargetGraph.NoSuchNodeException | BuildTargetException | HumanReadableException e) { buckEventBus.post(ConsoleEvent.severe(MoreExceptions.getHumanReadableOrLocalizedMessage(e))); return 1; } if (dryRun) { for (TargetNode<?, ?> targetNode : targetGraphAndTargets.getTargetGraph().getNodes()) { console.getStdOut().println(targetNode.toString()); } return 0; } return runXcodeProjectGenerator(executor, targetGraphAndTargets, passedInTargetsSet); } /** * Returns true if Buck should prompt to kill a running IDE before changing its files, false * otherwise. */ private boolean getIdePrompt(BuckConfig buckConfig) { return buckConfig.getBooleanValue("project", "ide_prompt", true); } private ProjectTestsMode testsMode(BuckConfig buckConfig) { ProjectTestsMode parameterMode = buckConfig.xcodeProjectTestsMode(); if (withoutTests) { parameterMode = ProjectTestsMode.WITHOUT_TESTS; } else if (withoutDependenciesTests) { parameterMode = ProjectTestsMode.WITHOUT_DEPENDENCIES_TESTS; } else if (withTests) { parameterMode = ProjectTestsMode.WITH_TESTS; } return parameterMode; } private boolean isWithTests(BuckConfig buckConfig) { return testsMode(buckConfig) != ProjectTestsMode.WITHOUT_TESTS; } private boolean isWithDependenciesTests(BuckConfig buckConfig) { return testsMode(buckConfig) == ProjectTestsMode.WITH_TESTS; } /** Run xcode specific project generation actions. */ private int runXcodeProjectGenerator(ListeningExecutorService executor, final TargetGraphAndTargets targetGraphAndTargets, ImmutableSet<BuildTarget> passedInTargetsSet) throws IOException, InterruptedException { int exitCode = 0; AppleConfig appleConfig = buckConfig.getView(AppleConfig.class); ImmutableSet<ProjectGenerator.Option> options = buildWorkspaceGeneratorOptions(readOnly, isWithTests(buckConfig), isWithDependenciesTests(buckConfig), combinedProject, appleConfig.shouldUseHeaderMapsInXcodeProject(), appleConfig.shouldMergeHeaderMapsInXcodeProject(), appleConfig.shouldGenerateHeaderSymlinkTreesOnly()); ImmutableSet<BuildTarget> requiredBuildTargets = generateWorkspacesForTargets(buckEventBus, cell, buckConfig, executorService, targetGraphAndTargets, passedInTargetsSet, options, getFocusModules(executor), new HashMap<>(), combinedProject); if (!requiredBuildTargets.isEmpty()) { ImmutableMultimap<Path, String> cellPathToCellName = cell.getCellPathResolver().getCellPaths() .asMultimap().inverse(); ImmutableList<String> arguments = RichStream.from(requiredBuildTargets).map(target -> { if (!target.getCellPath().equals(cell.getRoot())) { Optional<String> cellName = cellPathToCellName.get(target.getCellPath()).stream().findAny(); if (cellName.isPresent()) { return target.withUnflavoredBuildTarget(UnflavoredBuildTarget.of(target.getCellPath(), cellName, target.getBaseName(), target.getShortName())); } else { throw new IllegalStateException( "Failed to find cell name for cell path while constructing parameters to " + "build dependencies for project generation. " + "Build target: " + target + " cell path: " + target.getCellPath()); } } else { return target; } }).map(Object::toString).toImmutableList(); exitCode = buildRunner.apply(arguments); } return exitCode; } @VisibleForTesting static ImmutableSet<BuildTarget> generateWorkspacesForTargets(BuckEventBus buckEventBus, Cell cell, BuckConfig buckConfig, ListeningExecutorService executorService, final TargetGraphAndTargets targetGraphAndTargets, ImmutableSet<BuildTarget> passedInTargetsSet, ImmutableSet<ProjectGenerator.Option> options, FocusedModuleTargetMatcher focusModules, Map<Path, ProjectGenerator> projectGenerators, boolean combinedProject) throws IOException, InterruptedException { ImmutableSet<BuildTarget> targets; if (passedInTargetsSet.isEmpty()) { targets = targetGraphAndTargets.getProjectRoots().stream().map(TargetNode::getBuildTarget) .collect(MoreCollectors.toImmutableSet()); } else { targets = passedInTargetsSet; } LazyActionGraph lazyActionGraph = new LazyActionGraph(targetGraphAndTargets.getTargetGraph(), buckEventBus); LOG.debug("Generating workspace for config targets %s", targets); ImmutableSet.Builder<BuildTarget> requiredBuildTargetsBuilder = ImmutableSet.builder(); for (final BuildTarget inputTarget : targets) { TargetNode<?, ?> inputNode = targetGraphAndTargets.getTargetGraph().get(inputTarget); XcodeWorkspaceConfigDescriptionArg workspaceArgs; if (inputNode.getDescription() instanceof XcodeWorkspaceConfigDescription) { TargetNode<XcodeWorkspaceConfigDescriptionArg, ?> castedWorkspaceNode = castToXcodeWorkspaceTargetNode( inputNode); workspaceArgs = castedWorkspaceNode.getConstructorArg(); } else if (canGenerateImplicitWorkspaceForDescription(inputNode.getDescription())) { workspaceArgs = createImplicitWorkspaceArgs(inputNode); } else { throw new HumanReadableException( "%s must be a xcode_workspace_config, apple_binary, apple_bundle, or apple_library", inputNode); } AppleConfig appleConfig = buckConfig.getView(AppleConfig.class); HalideBuckConfig halideBuckConfig = new HalideBuckConfig(buckConfig); CxxBuckConfig cxxBuckConfig = new CxxBuckConfig(buckConfig); SwiftBuckConfig swiftBuckConfig = new SwiftBuckConfig(buckConfig); CxxPlatform defaultCxxPlatform = cell.getKnownBuildRuleTypes().getDefaultCxxPlatforms(); WorkspaceAndProjectGenerator generator = new WorkspaceAndProjectGenerator(cell, targetGraphAndTargets.getTargetGraph(), workspaceArgs, inputTarget, options, combinedProject, focusModules, !appleConfig.getXcodeDisableParallelizeBuild(), defaultCxxPlatform, buckConfig.getView(ParserConfig.class).getBuildFileName(), lazyActionGraph::getBuildRuleResolverWhileRequiringSubgraph, buckEventBus, halideBuckConfig, cxxBuckConfig, swiftBuckConfig); Preconditions.checkNotNull(executorService, "CommandRunnerParams does not have executor for PROJECT pool"); generator.generateWorkspaceAndDependentProjects(projectGenerators, executorService); ImmutableSet<BuildTarget> requiredBuildTargetsForWorkspace = generator.getRequiredBuildTargets(); LOG.debug("Required build targets for workspace %s: %s", inputTarget, requiredBuildTargetsForWorkspace); requiredBuildTargetsBuilder.addAll(requiredBuildTargetsForWorkspace); } return requiredBuildTargetsBuilder.build(); } private FocusedModuleTargetMatcher getFocusModules(ListeningExecutorService executor) throws IOException, InterruptedException { if (modulesToFocusOn == null) { return FocusedModuleTargetMatcher.noFocus(); } Iterable<String> patterns = Splitter.onPattern("\\s+").split(modulesToFocusOn); // Parse patterns with the following syntax: // https://buckbuild.com/concept/build_target_pattern.html ImmutableList<TargetNodeSpec> specs = argsParser.apply(patterns); // Resolve the list of targets matching the patterns. ImmutableSet<BuildTarget> passedInTargetsSet; ParserConfig parserConfig = buckConfig.getView(ParserConfig.class); try { passedInTargetsSet = parser .resolveTargetSpecs(buckEventBus, cell, enableParserProfiling, executor, specs, SpeculativeParsing.of(false), parserConfig.getDefaultFlavorsMode()) .stream().flatMap(Collection::stream).map(target -> target.withoutCell()) .collect(MoreCollectors.toImmutableSet()); } catch (BuildTargetException | BuildFileParseException | HumanReadableException e) { buckEventBus.post(ConsoleEvent.severe(MoreExceptions.getHumanReadableOrLocalizedMessage(e))); return FocusedModuleTargetMatcher.noFocus(); } LOG.debug("Selected targets: %s", passedInTargetsSet.toString()); ImmutableSet<UnflavoredBuildTarget> passedInUnflavoredTargetsSet = RichStream.from(passedInTargetsSet) .map(BuildTarget::getUnflavoredBuildTarget).toImmutableSet(); LOG.debug("Selected unflavored targets: %s", passedInUnflavoredTargetsSet.toString()); return FocusedModuleTargetMatcher.focusedOn(passedInUnflavoredTargetsSet); } @VisibleForTesting static ImmutableSet<ProjectGenerator.Option> buildWorkspaceGeneratorOptions(boolean isReadonly, boolean isWithTests, boolean isWithDependenciesTests, boolean isProjectsCombined, boolean shouldUseHeaderMaps, boolean shouldMergeHeaderMaps, boolean shouldGenerateHeaderSymlinkTreesOnly) { ImmutableSet.Builder<ProjectGenerator.Option> optionsBuilder = ImmutableSet.builder(); if (isReadonly) { optionsBuilder.add(ProjectGenerator.Option.GENERATE_READ_ONLY_FILES); } if (isWithTests) { optionsBuilder.add(ProjectGenerator.Option.INCLUDE_TESTS); } if (isWithDependenciesTests) { optionsBuilder.add(ProjectGenerator.Option.INCLUDE_DEPENDENCIES_TESTS); } if (isProjectsCombined) { optionsBuilder.addAll(ProjectGenerator.COMBINED_PROJECT_OPTIONS); } else { optionsBuilder.addAll(ProjectGenerator.SEPARATED_PROJECT_OPTIONS); } if (!shouldUseHeaderMaps) { optionsBuilder.add(ProjectGenerator.Option.DISABLE_HEADER_MAPS); } if (shouldMergeHeaderMaps) { optionsBuilder.add(ProjectGenerator.Option.MERGE_HEADER_MAPS); } if (shouldGenerateHeaderSymlinkTreesOnly) { optionsBuilder.add(ProjectGenerator.Option.GENERATE_HEADERS_SYMLINK_TREES_ONLY); } return optionsBuilder.build(); } @SuppressWarnings(value = "unchecked") private static TargetNode<XcodeWorkspaceConfigDescriptionArg, ?> castToXcodeWorkspaceTargetNode( TargetNode<?, ?> targetNode) { Preconditions.checkArgument(targetNode.getDescription() instanceof XcodeWorkspaceConfigDescription); return (TargetNode<XcodeWorkspaceConfigDescriptionArg, ?>) targetNode; } private void checkForAndKillXcodeIfRunning(boolean enablePrompt) throws InterruptedException, IOException { if (!processManager.isPresent()) { LOG.warn("Could not check if Xcode is running (no process manager)"); return; } if (!processManager.get().isProcessRunning(XCODE_PROCESS_NAME)) { LOG.debug("Xcode is not running."); return; } boolean canPromptResult = canPrompt(environment); if (enablePrompt && canPromptResult) { if (prompt("Xcode is currently running. Buck will modify files Xcode currently has " + "open, which can cause it to become unstable.\n\n" + "Kill Xcode and continue?")) { processManager.get().killProcess(XCODE_PROCESS_NAME); } else { console.getStdOut() .println(console.getAnsi().asWarningText( "Xcode is running. Generated projects might be lost or corrupted if Xcode " + "currently has them open.")); } console.getStdOut() .format("To disable this prompt in the future, add the following to %s: \n\n" + "[project]\n" + " ide_prompt = false\n\n", cell.getFilesystem().getRootPath().resolve(BuckConfig.BUCK_CONFIG_OVERRIDE_FILE_NAME) .toAbsolutePath()); } else { LOG.debug("Xcode is running, but cannot prompt to kill it (enabled %s, can prompt %s)", enablePrompt, canPromptResult); } } private boolean canPrompt(ImmutableMap<String, String> environment) { String nailgunStdinTty = environment.get("NAILGUN_TTY_0"); if (nailgunStdinTty != null) { return nailgunStdinTty.equals("1"); } else { return System.console() != null; } } private boolean prompt(String prompt) throws IOException { Preconditions.checkState(canPrompt(environment)); LOG.debug("Displaying prompt %s..", prompt); console.getStdOut().print(console.getAnsi().asWarningText(prompt + " [Y/n] ")); Optional<String> result; try (InputStreamReader stdinReader = new InputStreamReader(System.in, Charsets.UTF_8); BufferedReader bufferedStdinReader = new BufferedReader(stdinReader)) { result = Optional.ofNullable(bufferedStdinReader.readLine()); } LOG.debug("Result of prompt: [%s]", result); return result.isPresent() && (result.get().isEmpty() || result.get().toLowerCase(Locale.US).startsWith("y")); } @VisibleForTesting static ImmutableSet<BuildTarget> getRootsFromPredicate(TargetGraph projectGraph, Predicate<TargetNode<?, ?>> rootsPredicate) { return projectGraph.getNodes().stream().filter(rootsPredicate).map(TargetNode::getBuildTarget) .collect(MoreCollectors.toImmutableSet()); } private TargetGraph getProjectGraphForIde(ListeningExecutorService executor, ImmutableSet<BuildTarget> passedInTargets) throws InterruptedException, BuildFileParseException, BuildTargetException, IOException { if (passedInTargets.isEmpty()) { return parser .buildTargetGraphForTargetNodeSpecs(buckEventBus, cell, enableParserProfiling, executor, ImmutableList.of(TargetNodePredicateSpec.of(x -> true, BuildFileSpec.fromRecursivePath(Paths.get(""), cell.getRoot())))) .getTargetGraph(); } Preconditions.checkState(!passedInTargets.isEmpty()); return parser.buildTargetGraph(buckEventBus, cell, enableParserProfiling, executor, passedInTargets); } private TargetGraphAndTargets createTargetGraph(TargetGraph projectGraph, ImmutableSet<BuildTarget> graphRoots, boolean isWithTests, boolean isWithDependenciesTests, boolean needsFullRecursiveParse, ListeningExecutorService executor) throws IOException, InterruptedException, BuildFileParseException, BuildTargetException { ImmutableSet<BuildTarget> explicitTestTargets = ImmutableSet.of(); ImmutableSet<BuildTarget> graphRootsOrSourceTargets = replaceWorkspacesWithSourceTargetsIfPossible( graphRoots, projectGraph); if (isWithTests) { FocusedModuleTargetMatcher focusedModules = getFocusModules(executor); explicitTestTargets = getExplicitTestTargets(graphRootsOrSourceTargets, projectGraph, isWithDependenciesTests, focusedModules); if (!needsFullRecursiveParse) { projectGraph = parser.buildTargetGraph(buckEventBus, cell, enableParserProfiling, executor, Sets.union(graphRoots, explicitTestTargets)); } else { projectGraph = parser.buildTargetGraph(buckEventBus, cell, enableParserProfiling, executor, Sets.union(projectGraph.getNodes().stream().map(TargetNode::getBuildTarget) .collect(MoreCollectors.toImmutableSet()), explicitTestTargets)); } } return TargetGraphAndTargets.create(graphRoots, projectGraph, isWithTests, explicitTestTargets); } @VisibleForTesting static ImmutableSet<BuildTarget> replaceWorkspacesWithSourceTargetsIfPossible( ImmutableSet<BuildTarget> buildTargets, TargetGraph projectGraph) { Iterable<TargetNode<?, ?>> targetNodes = projectGraph.getAll(buildTargets); ImmutableSet.Builder<BuildTarget> resultBuilder = ImmutableSet.builder(); for (TargetNode<?, ?> node : targetNodes) { if (node.getDescription() instanceof XcodeWorkspaceConfigDescription) { TargetNode<XcodeWorkspaceConfigDescriptionArg, ?> castedWorkspaceNode = castToXcodeWorkspaceTargetNode( node); Optional<BuildTarget> srcTarget = castedWorkspaceNode.getConstructorArg().getSrcTarget(); if (srcTarget.isPresent()) { resultBuilder.add(srcTarget.get()); } else { resultBuilder.add(node.getBuildTarget()); } } else { resultBuilder.add(node.getBuildTarget()); } } return resultBuilder.build(); } private static boolean canGenerateImplicitWorkspaceForDescription(Description<?> description) { // We weren't given a workspace target, but we may have been given something that could // still turn into a workspace (for example, a library or an actual app rule). If that's the // case we still want to generate a workspace. return description instanceof AppleBinaryDescription || description instanceof AppleBundleDescription || description instanceof AppleLibraryDescription; } /** * @param sourceTargetNode - The TargetNode which will act as our fake workspaces `src_target` * @return Workspace Args that describe a generic Xcode workspace containing `src_target` and its * tests */ private static XcodeWorkspaceConfigDescriptionArg createImplicitWorkspaceArgs( TargetNode<?, ?> sourceTargetNode) { return XcodeWorkspaceConfigDescriptionArg.builder().setName("dummy") .setSrcTarget(sourceTargetNode.getBuildTarget()).build(); } /** * @param buildTargets The set of targets for which we would like to find tests * @param projectGraph A TargetGraph containing all nodes and their tests. * @param shouldIncludeDependenciesTests Should or not include tests that test dependencies * @return A set of all test targets that test any of {@code buildTargets} or their dependencies. */ @VisibleForTesting static ImmutableSet<BuildTarget> getExplicitTestTargets(ImmutableSet<BuildTarget> buildTargets, TargetGraph projectGraph, boolean shouldIncludeDependenciesTests, FocusedModuleTargetMatcher focusedModules) { Iterable<TargetNode<?, ?>> projectRoots = projectGraph.getAll(buildTargets); Iterable<TargetNode<?, ?>> nodes; if (shouldIncludeDependenciesTests) { nodes = projectGraph.getSubgraph(projectRoots).getNodes(); } else { nodes = projectRoots; } return TargetGraphAndTargets.getExplicitTestTargets(RichStream.from(nodes) .filter(node -> focusedModules.isFocusedOn(node.getBuildTarget())).iterator()); } /** * An action graph where subtrees are populated as needed. * * <p>This is useful when only select sub-graphs of the action graph needs to be generated, but * the subgraph is not known at this point in time. The synchronization and bottom-up traversal is * necessary as this will be accessed from multiple threads during project generation, and * BuildRuleResolver is not 100% thread safe when it comes to mutations. */ @ThreadSafe private static class LazyActionGraph { private final TargetGraph targetGraph; private final BuildRuleResolver resolver; public LazyActionGraph(TargetGraph targetGraph, BuckEventBus buckEventBus) { this.targetGraph = targetGraph; this.resolver = new BuildRuleResolver(targetGraph, new DefaultTargetNodeToBuildRuleTransformer(), buckEventBus); } public BuildRuleResolver getBuildRuleResolverWhileRequiringSubgraph(TargetNode<?, ?> root) { TargetGraph subgraph = targetGraph.getSubgraph(ImmutableList.of(root)); try { synchronized (this) { new AbstractBottomUpTraversal<TargetNode<?, ?>, NoSuchBuildTargetException>(subgraph) { @Override public void visit(TargetNode<?, ?> node) throws NoSuchBuildTargetException { resolver.requireRule(node.getBuildTarget()); } }.traverse(); } } catch (NoSuchBuildTargetException e) { throw new HumanReadableException(e); } return resolver; } } }