<commit_before>/*
 * 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.apple.xcode;

import com.facebook.buck.apple.AppleBuildRules;
import com.facebook.buck.apple.XcodeProjectConfigDescription;
import com.facebook.buck.apple.XcodeWorkspaceConfigDescription;
import com.facebook.buck.apple.xcode.xcodeproj.PBXTarget;
import com.facebook.buck.graph.TopologicalSort;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.HasBuildTarget;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.TargetNode;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
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.Multimap;
import com.google.common.collect.Sets;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Set;

public class WorkspaceAndProjectGenerator {
  private static final Logger LOG = Logger.get(WorkspaceAndProjectGenerator.class);

  private final ProjectFilesystem projectFilesystem;
  private final TargetGraph projectGraph;
  private final ExecutionContext executionContext;
  private final TargetNode<XcodeWorkspaceConfigDescription.Arg> workspaceTargetNode;
  private final ImmutableSet<ProjectGenerator.Option> projectGeneratorOptions;
  private final ImmutableMultimap<BuildTarget, TargetNode<?>> sourceTargetToTestNodes;
  private final ImmutableSet<TargetNode<?>> extraTestBundleTargetNodes;
  private final boolean combinedProject;
  private Optional<ProjectGenerator> combinedProjectGenerator;

  public WorkspaceAndProjectGenerator(
      ProjectFilesystem projectFilesystem,
      TargetGraph projectGraph,
      ExecutionContext executionContext,
      TargetNode<XcodeWorkspaceConfigDescription.Arg> workspaceTargetNode,
      Set<ProjectGenerator.Option> projectGeneratorOptions,
      Multimap<BuildTarget, TargetNode<?>> sourceTargetToTestNodes,
      boolean combinedProject) {
    this.projectFilesystem = projectFilesystem;
    this.projectGraph = projectGraph;
    this.executionContext = executionContext;
    this.workspaceTargetNode = workspaceTargetNode;
    this.projectGeneratorOptions = ImmutableSet.copyOf(projectGeneratorOptions);
    this.sourceTargetToTestNodes = ImmutableMultimap.copyOf(sourceTargetToTestNodes);
    this.combinedProject = combinedProject;
    this.combinedProjectGenerator = Optional.absent();
    extraTestBundleTargetNodes = ImmutableSet.copyOf(
        projectGraph.getAll(
            workspaceTargetNode.getConstructorArg().extraTests.get()));
  }

  @VisibleForTesting
  Optional<ProjectGenerator> getCombinedProjectGenerator() {
    return combinedProjectGenerator;
  }

  public Path generateWorkspaceAndDependentProjects(
        Map<TargetNode<?>, ProjectGenerator> projectGenerators)
      throws IOException {
    LOG.debug("Generating workspace for target %s", workspaceTargetNode);

    String workspaceName;
    Path outputDirectory;

    if (combinedProject) {
      workspaceName = "GeneratedProject";
      outputDirectory = Paths.get("_gen");
    } else {
      workspaceName = XcodeWorkspaceConfigDescription.getWorkspaceNameFromArg(
          workspaceTargetNode.getConstructorArg());
      outputDirectory = workspaceTargetNode.getBuildTarget().getBasePath();
    }

    WorkspaceGenerator workspaceGenerator = new WorkspaceGenerator(
        projectFilesystem,
        workspaceName,
        outputDirectory);

    ImmutableSet<TargetNode<?>> orderedTargetNodes;
    if (workspaceTargetNode.getConstructorArg().srcTarget.isPresent()) {
      orderedTargetNodes = AppleBuildRules.getSchemeBuildableTargetNodes(
          projectGraph,
          projectGraph.get(
              workspaceTargetNode.getConstructorArg().srcTarget.get().getBuildTarget()));
    } else {
      orderedTargetNodes = ImmutableSet.of();
    }

    ImmutableSet<TargetNode<?>> orderedTestTargetNodes;
    ImmutableSet<TargetNode<?>> orderedTestBundleTargetNodes;
    {
      ImmutableSet.Builder<TargetNode<?>> orderedTestTargetNodesBuilder =
          ImmutableSet.builder();
      ImmutableSet.Builder<TargetNode<?>> orderedTestBundleTargetNodesBuilder =
          ImmutableSet.builder();

      getOrderedTestNodes(
          projectGraph,
          sourceTargetToTestNodes,
          orderedTargetNodes,
          extraTestBundleTargetNodes,
          orderedTestTargetNodesBuilder,
          orderedTestBundleTargetNodesBuilder);

      orderedTestTargetNodes = orderedTestTargetNodesBuilder.build();
      orderedTestBundleTargetNodes = orderedTestBundleTargetNodesBuilder.build();
    }

    ImmutableSet<BuildTarget> targetsInRequiredProjects = FluentIterable
        .from(Sets.union(orderedTargetNodes, orderedTestTargetNodes))
        .transform(HasBuildTarget.TO_TARGET)
        .toSet();
    ImmutableMap.Builder<BuildTarget, PBXTarget> buildTargetToPbxTargetMapBuilder =
        ImmutableMap.builder();
    ImmutableMap.Builder<PBXTarget, Path> targetToProjectPathMapBuilder =
        ImmutableMap.builder();

    if (combinedProject) {
      ImmutableSet.Builder<BuildTarget> initialTargetsBuilder = ImmutableSet.builder();
      for (TargetNode<?> targetNode : projectGraph.getNodes()) {
        if (targetNode.getType() != XcodeProjectConfigDescription.TYPE) {
          continue;
        }
        XcodeProjectConfigDescription.Arg projectArg =
            (XcodeProjectConfigDescription.Arg) targetNode.getConstructorArg();
        if (Sets.intersection(targetsInRequiredProjects, projectArg.rules).isEmpty()) {
          continue;
        }
        initialTargetsBuilder.addAll(projectArg.rules);
      }

      LOG.debug("Generating a combined project");
      ProjectGenerator generator = new ProjectGenerator(
          projectGraph,
          initialTargetsBuilder.build(),
          projectFilesystem,
          executionContext,
          outputDirectory,
          "GeneratedProject",
          projectGeneratorOptions);
      combinedProjectGenerator = Optional.of(generator);
      generator.createXcodeProjects();

      workspaceGenerator.addFilePath(generator.getProjectPath());

      buildTargetToPbxTargetMapBuilder.putAll(generator.getBuildTargetToGeneratedTargetMap());
      for (PBXTarget target : generator.getBuildTargetToGeneratedTargetMap().values()) {
        targetToProjectPathMapBuilder.put(target, generator.getProjectPath());
      }
    } else {
      for (TargetNode<?> targetNode : projectGraph.getNodes()) {
        if (targetNode.getType() != XcodeProjectConfigDescription.TYPE) {
          continue;
        }
        XcodeProjectConfigDescription.Arg projectArg =
            (XcodeProjectConfigDescription.Arg) targetNode.getConstructorArg();
        if (Sets.intersection(targetsInRequiredProjects, projectArg.rules).isEmpty()) {
          continue;
        }

        ProjectGenerator generator = projectGenerators.get(targetNode);
        if (generator == null) {
          LOG.debug("Generating project for target %s", targetNode);
          generator = new ProjectGenerator(
              projectGraph,
              projectArg.rules,
              projectFilesystem,
              executionContext,
              targetNode.getBuildTarget().getBasePath(),
              projectArg.projectName,
              projectGeneratorOptions);
          generator.createXcodeProjects();
          projectGenerators.put(targetNode, generator);
        } else {
          LOG.debug("Already generated project for target %s, skipping", targetNode);
        }

        workspaceGenerator.addFilePath(generator.getProjectPath());

        buildTargetToPbxTargetMapBuilder.putAll(generator.getBuildTargetToGeneratedTargetMap());
        for (PBXTarget target : generator.getBuildTargetToGeneratedTargetMap().values()) {
          targetToProjectPathMapBuilder.put(target, generator.getProjectPath());
        }
      }
    }

    Path workspacePath = workspaceGenerator.writeWorkspace();

    SchemeGenerator schemeGenerator = new SchemeGenerator(
        projectFilesystem,
        workspaceTargetNode.getConstructorArg().srcTarget,
        Iterables.transform(
            orderedTargetNodes,
            HasBuildTarget.TO_TARGET),
        Iterables.transform(
            orderedTestTargetNodes,
            HasBuildTarget.TO_TARGET),
        Iterables.transform(
            orderedTestBundleTargetNodes,
            HasBuildTarget.TO_TARGET),
        workspaceName,
        outputDirectory.resolve(workspaceName + ".xcworkspace"),
        XcodeWorkspaceConfigDescription.getActionConfigNamesFromArg(
            workspaceTargetNode.getConstructorArg()),
        buildTargetToPbxTargetMapBuilder.build(),
        targetToProjectPathMapBuilder.build());
    schemeGenerator.writeScheme();

    return workspacePath;
  }

  private void getOrderedTestNodes(
      TargetGraph targetGraph,
      ImmutableMultimap<BuildTarget, TargetNode<?>> sourceTargetToTestNodes,
      ImmutableSet<TargetNode<?>> orderedTargetNodes,
      ImmutableSet<TargetNode<?>> extraTestBundleTargets,
      ImmutableSet.Builder<TargetNode<?>> orderedTestTargetNodeBuilder,
      ImmutableSet.Builder<TargetNode<?>> orderedTestBundleTargetNodeBuilder) {
    LOG.debug("Getting ordered test target nodes for %s", orderedTargetNodes);
    final ImmutableSet.Builder<TargetNode<?>> recursiveTestTargetNodesBuilder =
        ImmutableSet.builder();
    if (!sourceTargetToTestNodes.isEmpty()) {
      for (TargetNode<?> node : orderedTargetNodes) {
        LOG.verbose("Checking if target %s has any tests covering it..", node);
        for (TargetNode<?> testNode : sourceTargetToTestNodes.get(node.getBuildTarget())) {
          addTestNodeAndDependencies(
              testNode,
              recursiveTestTargetNodesBuilder,
              orderedTestBundleTargetNodeBuilder);
        }
      }
    }

    for (TargetNode<?> testBundleTarget : extraTestBundleTargets) {
      if (!AppleBuildRules.isXcodeTargetTestBundleTargetNode(testBundleTarget)) {
        throw new HumanReadableException(
            "Test target %s must be apple_bundle with a test extension!",
            testBundleTarget);
      }
      addTestNodeAndDependencies(
          testBundleTarget,
          recursiveTestTargetNodesBuilder,
          orderedTestBundleTargetNodeBuilder);
    }

    final Set<TargetNode<?>> includedTestNodes =
        Sets.difference(recursiveTestTargetNodesBuilder.build(), orderedTargetNodes);

    orderedTestTargetNodeBuilder.addAll(
        TopologicalSort.sort(
            targetGraph,
            new Predicate<TargetNode<?>>() {
              @Override
              public boolean apply(TargetNode<?> input) {
                return includedTestNodes.contains(input) &&
                    AppleBuildRules.isXcodeTargetBuildRuleType(input.getType());
              }
            }));
  }

  private void addTestNodeAndDependencies(
      TargetNode<?> testBundleBuildTarget,
      ImmutableSet.Builder<TargetNode<?>> recursiveTestTargetNodesBuilder,
      ImmutableSet.Builder<TargetNode<?>> orderedTestBundleTargetNodesBuilder) {
    Iterable<TargetNode<?>> testBundleTargetDependencies =
        AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(
            projectGraph,
            AppleBuildRules.RecursiveDependenciesMode.BUILDING,
            projectGraph.get(testBundleBuildTarget.getBuildTarget()),
            Optional.<ImmutableSet<BuildRuleType>>absent());
    recursiveTestTargetNodesBuilder.addAll(testBundleTargetDependencies);
    recursiveTestTargetNodesBuilder.add(testBundleBuildTarget);
    orderedTestBundleTargetNodesBuilder.add(testBundleBuildTarget);
  }
}<commit_msg>Remove an unnecessary target graph node lookup.<commit_after>/*
 * 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.apple.xcode;

import com.facebook.buck.apple.AppleBuildRules;
import com.facebook.buck.apple.XcodeProjectConfigDescription;
import com.facebook.buck.apple.XcodeWorkspaceConfigDescription;
import com.facebook.buck.apple.xcode.xcodeproj.PBXTarget;
import com.facebook.buck.graph.TopologicalSort;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.HasBuildTarget;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.TargetNode;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
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.Multimap;
import com.google.common.collect.Sets;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Set;

public class WorkspaceAndProjectGenerator {
  private static final Logger LOG = Logger.get(WorkspaceAndProjectGenerator.class);

  private final ProjectFilesystem projectFilesystem;
  private final TargetGraph projectGraph;
  private final ExecutionContext executionContext;
  private final TargetNode<XcodeWorkspaceConfigDescription.Arg> workspaceTargetNode;
  private final ImmutableSet<ProjectGenerator.Option> projectGeneratorOptions;
  private final ImmutableMultimap<BuildTarget, TargetNode<?>> sourceTargetToTestNodes;
  private final ImmutableSet<TargetNode<?>> extraTestBundleTargetNodes;
  private final boolean combinedProject;
  private Optional<ProjectGenerator> combinedProjectGenerator;

  public WorkspaceAndProjectGenerator(
      ProjectFilesystem projectFilesystem,
      TargetGraph projectGraph,
      ExecutionContext executionContext,
      TargetNode<XcodeWorkspaceConfigDescription.Arg> workspaceTargetNode,
      Set<ProjectGenerator.Option> projectGeneratorOptions,
      Multimap<BuildTarget, TargetNode<?>> sourceTargetToTestNodes,
      boolean combinedProject) {
    this.projectFilesystem = projectFilesystem;
    this.projectGraph = projectGraph;
    this.executionContext = executionContext;
    this.workspaceTargetNode = workspaceTargetNode;
    this.projectGeneratorOptions = ImmutableSet.copyOf(projectGeneratorOptions);
    this.sourceTargetToTestNodes = ImmutableMultimap.copyOf(sourceTargetToTestNodes);
    this.combinedProject = combinedProject;
    this.combinedProjectGenerator = Optional.absent();
    extraTestBundleTargetNodes = ImmutableSet.copyOf(
        projectGraph.getAll(
            workspaceTargetNode.getConstructorArg().extraTests.get()));
  }

  @VisibleForTesting
  Optional<ProjectGenerator> getCombinedProjectGenerator() {
    return combinedProjectGenerator;
  }

  public Path generateWorkspaceAndDependentProjects(
        Map<TargetNode<?>, ProjectGenerator> projectGenerators)
      throws IOException {
    LOG.debug("Generating workspace for target %s", workspaceTargetNode);

    String workspaceName;
    Path outputDirectory;

    if (combinedProject) {
      workspaceName = "GeneratedProject";
      outputDirectory = Paths.get("_gen");
    } else {
      workspaceName = XcodeWorkspaceConfigDescription.getWorkspaceNameFromArg(
          workspaceTargetNode.getConstructorArg());
      outputDirectory = workspaceTargetNode.getBuildTarget().getBasePath();
    }

    WorkspaceGenerator workspaceGenerator = new WorkspaceGenerator(
        projectFilesystem,
        workspaceName,
        outputDirectory);

    ImmutableSet<TargetNode<?>> orderedTargetNodes;
    if (workspaceTargetNode.getConstructorArg().srcTarget.isPresent()) {
      orderedTargetNodes = AppleBuildRules.getSchemeBuildableTargetNodes(
          projectGraph,
          projectGraph.get(
              workspaceTargetNode.getConstructorArg().srcTarget.get().getBuildTarget()));
    } else {
      orderedTargetNodes = ImmutableSet.of();
    }

    ImmutableSet<TargetNode<?>> orderedTestTargetNodes;
    ImmutableSet<TargetNode<?>> orderedTestBundleTargetNodes;
    {
      ImmutableSet.Builder<TargetNode<?>> orderedTestTargetNodesBuilder =
          ImmutableSet.builder();
      ImmutableSet.Builder<TargetNode<?>> orderedTestBundleTargetNodesBuilder =
          ImmutableSet.builder();

      getOrderedTestNodes(
          projectGraph,
          sourceTargetToTestNodes,
          orderedTargetNodes,
          extraTestBundleTargetNodes,
          orderedTestTargetNodesBuilder,
          orderedTestBundleTargetNodesBuilder);

      orderedTestTargetNodes = orderedTestTargetNodesBuilder.build();
      orderedTestBundleTargetNodes = orderedTestBundleTargetNodesBuilder.build();
    }

    ImmutableSet<BuildTarget> targetsInRequiredProjects = FluentIterable
        .from(Sets.union(orderedTargetNodes, orderedTestTargetNodes))
        .transform(HasBuildTarget.TO_TARGET)
        .toSet();
    ImmutableMap.Builder<BuildTarget, PBXTarget> buildTargetToPbxTargetMapBuilder =
        ImmutableMap.builder();
    ImmutableMap.Builder<PBXTarget, Path> targetToProjectPathMapBuilder =
        ImmutableMap.builder();

    if (combinedProject) {
      ImmutableSet.Builder<BuildTarget> initialTargetsBuilder = ImmutableSet.builder();
      for (TargetNode<?> targetNode : projectGraph.getNodes()) {
        if (targetNode.getType() != XcodeProjectConfigDescription.TYPE) {
          continue;
        }
        XcodeProjectConfigDescription.Arg projectArg =
            (XcodeProjectConfigDescription.Arg) targetNode.getConstructorArg();
        if (Sets.intersection(targetsInRequiredProjects, projectArg.rules).isEmpty()) {
          continue;
        }
        initialTargetsBuilder.addAll(projectArg.rules);
      }

      LOG.debug("Generating a combined project");
      ProjectGenerator generator = new ProjectGenerator(
          projectGraph,
          initialTargetsBuilder.build(),
          projectFilesystem,
          executionContext,
          outputDirectory,
          "GeneratedProject",
          projectGeneratorOptions);
      combinedProjectGenerator = Optional.of(generator);
      generator.createXcodeProjects();

      workspaceGenerator.addFilePath(generator.getProjectPath());

      buildTargetToPbxTargetMapBuilder.putAll(generator.getBuildTargetToGeneratedTargetMap());
      for (PBXTarget target : generator.getBuildTargetToGeneratedTargetMap().values()) {
        targetToProjectPathMapBuilder.put(target, generator.getProjectPath());
      }
    } else {
      for (TargetNode<?> targetNode : projectGraph.getNodes()) {
        if (targetNode.getType() != XcodeProjectConfigDescription.TYPE) {
          continue;
        }
        XcodeProjectConfigDescription.Arg projectArg =
            (XcodeProjectConfigDescription.Arg) targetNode.getConstructorArg();
        if (Sets.intersection(targetsInRequiredProjects, projectArg.rules).isEmpty()) {
          continue;
        }

        ProjectGenerator generator = projectGenerators.get(targetNode);
        if (generator == null) {
          LOG.debug("Generating project for target %s", targetNode);
          generator = new ProjectGenerator(
              projectGraph,
              projectArg.rules,
              projectFilesystem,
              executionContext,
              targetNode.getBuildTarget().getBasePath(),
              projectArg.projectName,
              projectGeneratorOptions);
          generator.createXcodeProjects();
          projectGenerators.put(targetNode, generator);
        } else {
          LOG.debug("Already generated project for target %s, skipping", targetNode);
        }

        workspaceGenerator.addFilePath(generator.getProjectPath());

        buildTargetToPbxTargetMapBuilder.putAll(generator.getBuildTargetToGeneratedTargetMap());
        for (PBXTarget target : generator.getBuildTargetToGeneratedTargetMap().values()) {
          targetToProjectPathMapBuilder.put(target, generator.getProjectPath());
        }
      }
    }

    Path workspacePath = workspaceGenerator.writeWorkspace();

    SchemeGenerator schemeGenerator = new SchemeGenerator(
        projectFilesystem,
        workspaceTargetNode.getConstructorArg().srcTarget,
        Iterables.transform(
            orderedTargetNodes,
            HasBuildTarget.TO_TARGET),
        Iterables.transform(
            orderedTestTargetNodes,
            HasBuildTarget.TO_TARGET),
        Iterables.transform(
            orderedTestBundleTargetNodes,
            HasBuildTarget.TO_TARGET),
        workspaceName,
        outputDirectory.resolve(workspaceName + ".xcworkspace"),
        XcodeWorkspaceConfigDescription.getActionConfigNamesFromArg(
            workspaceTargetNode.getConstructorArg()),
        buildTargetToPbxTargetMapBuilder.build(),
        targetToProjectPathMapBuilder.build());
    schemeGenerator.writeScheme();

    return workspacePath;
  }

  private void getOrderedTestNodes(
      TargetGraph targetGraph,
      ImmutableMultimap<BuildTarget, TargetNode<?>> sourceTargetToTestNodes,
      ImmutableSet<TargetNode<?>> orderedTargetNodes,
      ImmutableSet<TargetNode<?>> extraTestBundleTargets,
      ImmutableSet.Builder<TargetNode<?>> orderedTestTargetNodeBuilder,
      ImmutableSet.Builder<TargetNode<?>> orderedTestBundleTargetNodeBuilder) {
    LOG.debug("Getting ordered test target nodes for %s", orderedTargetNodes);
    final ImmutableSet.Builder<TargetNode<?>> recursiveTestTargetNodesBuilder =
        ImmutableSet.builder();
    if (!sourceTargetToTestNodes.isEmpty()) {
      for (TargetNode<?> node : orderedTargetNodes) {
        LOG.verbose("Checking if target %s has any tests covering it..", node);
        for (TargetNode<?> testNode : sourceTargetToTestNodes.get(node.getBuildTarget())) {
          addTestNodeAndDependencies(
              testNode,
              recursiveTestTargetNodesBuilder,
              orderedTestBundleTargetNodeBuilder);
        }
      }
    }

    for (TargetNode<?> testBundleTarget : extraTestBundleTargets) {
      if (!AppleBuildRules.isXcodeTargetTestBundleTargetNode(testBundleTarget)) {
        throw new HumanReadableException(
            "Test target %s must be apple_bundle with a test extension!",
            testBundleTarget);
      }
      addTestNodeAndDependencies(
          testBundleTarget,
          recursiveTestTargetNodesBuilder,
          orderedTestBundleTargetNodeBuilder);
    }

    final Set<TargetNode<?>> includedTestNodes =
        Sets.difference(recursiveTestTargetNodesBuilder.build(), orderedTargetNodes);

    orderedTestTargetNodeBuilder.addAll(
        TopologicalSort.sort(
            targetGraph,
            new Predicate<TargetNode<?>>() {
              @Override
              public boolean apply(TargetNode<?> input) {
                return includedTestNodes.contains(input) &&
                    AppleBuildRules.isXcodeTargetBuildRuleType(input.getType());
              }
            }));
  }

  private void addTestNodeAndDependencies(
      TargetNode<?> testBundleTargetNode,
      ImmutableSet.Builder<TargetNode<?>> recursiveTestTargetNodesBuilder,
      ImmutableSet.Builder<TargetNode<?>> orderedTestBundleTargetNodesBuilder) {
    Iterable<TargetNode<?>> testBundleTargetDependencies =
        AppleBuildRules.getRecursiveTargetNodeDependenciesOfTypes(
            projectGraph,
            AppleBuildRules.RecursiveDependenciesMode.BUILDING,
            testBundleTargetNode,
            Optional.<ImmutableSet<BuildRuleType>>absent());
    recursiveTestTargetNodesBuilder.addAll(testBundleTargetDependencies);
    recursiveTestTargetNodesBuilder.add(testBundleTargetNode);
    orderedTestBundleTargetNodesBuilder.add(testBundleTargetNode);
  }
}
{'fraction_non_alphanumeric': 0.08311392305576296, 'fraction_numerical': 0.0006287578103509254, 'mean_word_length': 3.737155621742368, 'pattern_counts': {'":': 0, '<': 121, '<?xml version=': 0, '>': 121, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3020650', 'n_tokens_mistral': 6362, 'n_tokens_neox': 6272, 'n_words': 1132}