@Test
  public void testSimpleCxxBinaryWithHeader() throws IOException {
    ProjectWorkspace workspace =
        TestDataHelper.createProjectWorkspaceForScenario(this, "simple", tmp);
    workspace.setUp();

    CxxPlatform cxxPlatform = DefaultCxxPlatforms.build(new CxxBuckConfig(new FakeBuckConfig()));
    BuildTarget target = BuildTargetFactory.newInstance("//foo:simple_with_header");
    CxxSourceRuleFactory cxxSourceRuleFactory = CxxSourceRuleFactoryHelper.of(target, cxxPlatform);
    BuildTarget binaryTarget = CxxDescriptionEnhancer.createCxxLinkTarget(target);
    String sourceName = "simple_with_header.cpp";
    String headerName = "simple_with_header.h";
    String headerFull = "foo/" + headerName;
    BuildTarget preprocessTarget =
        cxxSourceRuleFactory.createPreprocessBuildTarget(
            sourceName, CxxSource.Type.CXX, CxxSourceRuleFactory.PicType.PDC);
    BuildTarget compileTarget =
        cxxSourceRuleFactory.createCompileBuildTarget(sourceName, CxxSourceRuleFactory.PicType.PDC);
    BuildTarget headerSymlinkTreeTarget =
        CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
            target, cxxPlatform.getFlavor(), HeaderVisibility.PRIVATE);

    // Do a clean build, verify that it succeeds, and check that all expected targets built
    // successfully.
    workspace.runBuckCommand("build", target.toString()).assertSuccess();
    BuckBuildLog buildLog = workspace.getBuildLog();
    assertEquals(
        ImmutableSet.of(
            headerSymlinkTreeTarget, preprocessTarget, compileTarget, binaryTarget, target),
        buildLog.getAllTargets());
    buildLog.assertTargetBuiltLocally(headerSymlinkTreeTarget.toString());
    buildLog.assertTargetBuiltLocally(preprocessTarget.toString());
    buildLog.assertTargetBuiltLocally(compileTarget.toString());
    buildLog.assertTargetBuiltLocally(binaryTarget.toString());
    buildLog.assertTargetBuiltLocally(target.toString());

    // Clear for new build.
    workspace.resetBuildLogFile();

    // Update the source file.
    workspace.replaceFileContents(headerFull, "blah = 5", "blah = 6");

    // Check that running a build again makes the source get recompiled and the binary
    // re-linked, but does not cause the header rules to re-run.
    workspace.runBuckCommand("build", target.toString()).assertSuccess();
    buildLog = workspace.getBuildLog();
    assertEquals(
        ImmutableSet.of(
            headerSymlinkTreeTarget, preprocessTarget, compileTarget, binaryTarget, target),
        buildLog.getAllTargets());
    buildLog.assertTargetHadMatchingRuleKey(headerSymlinkTreeTarget.toString());
    buildLog.assertTargetBuiltLocally(preprocessTarget.toString());
    buildLog.assertTargetBuiltLocally(compileTarget.toString());
    assertThat(
        buildLog.getLogEntry(binaryTarget).getSuccessType().get(),
        Matchers.not(Matchers.equalTo(BuildRuleSuccessType.MATCHING_RULE_KEY)));
    buildLog.assertTargetBuiltLocally(target.toString());
  }
  @Test
  public void testCreateBuildRule() throws Exception {
    // Set up a #halide-compiler rule, then set up a halide_library rule, and
    // check that the library rule depends on the compiler rule.
    BuildTarget compilerTarget =
        BuildTargetFactory.newInstance("//:rule")
            .withFlavors(HalideLibraryDescription.HALIDE_COMPILER_FLAVOR);
    BuildTarget libTarget = BuildTargetFactory.newInstance("//:rule");

    ProjectFilesystem filesystem = new FakeProjectFilesystem();
    HalideLibraryBuilder compilerBuilder = new HalideLibraryBuilder(compilerTarget);
    compilerBuilder.setSrcs(
        ImmutableSortedSet.of(SourceWithFlags.of(new FakeSourcePath("main.cpp"))));
    HalideLibraryBuilder libBuilder = new HalideLibraryBuilder(libTarget);
    TargetGraph targetGraph =
        TargetGraphFactory.newInstance(compilerBuilder.build(), libBuilder.build());
    BuildRuleResolver resolver =
        new BuildRuleResolver(targetGraph, new BuildTargetNodeToBuildRuleTransformer());
    CxxBinary compiler = (CxxBinary) compilerBuilder.build(resolver, filesystem, targetGraph);
    HalideLibrary lib = (HalideLibrary) libBuilder.build(resolver, filesystem, targetGraph);
    // Check that we picked up the implicit dependency on the #halide-compiler
    // version of the rule.
    assertEquals(lib.getDeps(), ImmutableSortedSet.<BuildRule>of(compiler));

    // Check that the library rule has the correct preprocessor input.
    CxxPlatform cxxPlatform = CxxLibraryBuilder.createDefaultPlatform();
    String headerName = "rule.h";
    Path headerPath = BuildTargets.getGenPath(libTarget, "%s/" + headerName);
    Path headerRoot =
        CxxDescriptionEnhancer.getHeaderSymlinkTreePath(
            libTarget, cxxPlatform.getFlavor(), HeaderVisibility.PUBLIC);
    assertEquals(
        CxxPreprocessorInput.builder()
            .addRules(
                CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
                    libTarget, cxxPlatform.getFlavor(), HeaderVisibility.PUBLIC))
            .setIncludes(
                CxxHeaders.builder()
                    .putNameToPathMap(
                        Paths.get(headerName), new BuildTargetSourcePath(libTarget, headerPath))
                    .putFullNameToPathMap(
                        headerRoot.resolve(headerName),
                        new BuildTargetSourcePath(libTarget, headerPath))
                    .build())
            .addSystemIncludeRoots(headerRoot)
            .build(),
        lib.getCxxPreprocessorInput(cxxPlatform, HeaderVisibility.PUBLIC));

    // Check that the library rule has the correct native linkable input.
    NativeLinkableInput input =
        lib.getNativeLinkableInput(cxxPlatform, Linker.LinkableDepType.STATIC);
    BuildRule buildRule =
        FluentIterable.from(input.getArgs())
            .transformAndConcat(Arg.getDepsFunction(new SourcePathResolver(resolver)))
            .get(0);
    assertTrue(buildRule instanceof HalideLibrary);
  }
  public static HeaderSymlinkTree createHeaderSymlinkTree(
      BuildRuleParams params,
      BuildRuleResolver ruleResolver,
      SourcePathResolver pathResolver,
      CxxPlatform cxxPlatform,
      boolean includeLexYaccHeaders,
      ImmutableMap<String, SourcePath> lexSources,
      ImmutableMap<String, SourcePath> yaccSources,
      ImmutableMap<Path, SourcePath> headers,
      HeaderVisibility headerVisibility) {

    BuildTarget headerSymlinkTreeTarget =
        CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
            params.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility);
    Path headerSymlinkTreeRoot =
        CxxDescriptionEnhancer.getHeaderSymlinkTreePath(
            params.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility);
    Optional<Path> headerMapLocation = Optional.absent();
    if (cxxPlatform.getCpp().supportsHeaderMaps() && cxxPlatform.getCxxpp().supportsHeaderMaps()) {
      headerMapLocation =
          Optional.of(
              getHeaderMapPath(params.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility));
    }

    CxxHeaderSourceSpec lexYaccSources;
    if (includeLexYaccHeaders) {
      lexYaccSources =
          requireLexYaccSources(
              params, ruleResolver, pathResolver, cxxPlatform, lexSources, yaccSources);
    } else {
      lexYaccSources = CxxHeaderSourceSpec.builder().build();
    }

    return CxxPreprocessables.createHeaderSymlinkTreeBuildRule(
        pathResolver,
        headerSymlinkTreeTarget,
        params,
        headerSymlinkTreeRoot,
        headerMapLocation,
        ImmutableMap.<Path, SourcePath>builder()
            .putAll(headers)
            .putAll(lexYaccSources.getCxxHeaders())
            .build());
  }
  public static HeaderSymlinkTree requireHeaderSymlinkTree(
      BuildRuleParams params,
      BuildRuleResolver ruleResolver,
      SourcePathResolver pathResolver,
      CxxPlatform cxxPlatform,
      boolean includeLexYaccHeaders,
      ImmutableMap<String, SourcePath> lexSources,
      ImmutableMap<String, SourcePath> yaccSources,
      ImmutableMap<Path, SourcePath> headers,
      HeaderVisibility headerVisibility) {
    BuildTarget headerSymlinkTreeTarget =
        CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
            params.getBuildTarget(), cxxPlatform.getFlavor(), headerVisibility);

    // Check the cache...
    Optional<BuildRule> rule = ruleResolver.getRuleOptional(headerSymlinkTreeTarget);
    if (rule.isPresent()) {
      Preconditions.checkState(rule.get() instanceof HeaderSymlinkTree);
      return (HeaderSymlinkTree) rule.get();
    }

    HeaderSymlinkTree symlinkTree =
        createHeaderSymlinkTree(
            params,
            ruleResolver,
            pathResolver,
            cxxPlatform,
            includeLexYaccHeaders,
            lexSources,
            yaccSources,
            headers,
            headerVisibility);

    ruleResolver.addToIndex(symlinkTree);

    return symlinkTree;
  }
  public void doTestSimpleCxxBinaryBuilds(String preprocessMode, boolean expectPreprocessorOutput)
      throws IOException {
    ProjectWorkspace workspace =
        TestDataHelper.createProjectWorkspaceForScenario(this, "simple", tmp);
    workspace.setUp();
    workspace.writeContentsToPath(
        String.format("[cxx]\npreprocess_mode = %s\n", preprocessMode), ".buckconfig");
    CxxPlatform cxxPlatform = DefaultCxxPlatforms.build(new CxxBuckConfig(new FakeBuckConfig()));
    BuildTarget target = BuildTargetFactory.newInstance("//foo:simple");
    CxxSourceRuleFactory cxxSourceRuleFactory = CxxSourceRuleFactoryHelper.of(target, cxxPlatform);
    BuildTarget binaryTarget = CxxDescriptionEnhancer.createCxxLinkTarget(target);
    String sourceName = "simple.cpp";
    String sourceFull = "foo/" + sourceName;
    BuildTarget preprocessTarget =
        cxxSourceRuleFactory.createPreprocessBuildTarget(
            sourceName, CxxSource.Type.CXX, CxxSourceRuleFactory.PicType.PDC);
    BuildTarget compileTarget =
        cxxSourceRuleFactory.createCompileBuildTarget(sourceName, CxxSourceRuleFactory.PicType.PDC);
    BuildTarget headerSymlinkTreeTarget =
        CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
            target, cxxPlatform.getFlavor(), HeaderVisibility.PRIVATE);

    // Do a clean build, verify that it succeeds, and check that all expected targets built
    // successfully.
    workspace.runBuckCommand("build", target.toString()).assertSuccess();
    BuckBuildLog buildLog = workspace.getBuildLog();
    ImmutableSet<BuildTarget> expectedTargets =
        ImmutableSet.<BuildTarget>builder()
            .addAll(ImmutableSet.of(headerSymlinkTreeTarget, compileTarget, binaryTarget, target))
            .addAll(
                (expectPreprocessorOutput
                    ? ImmutableSet.of(preprocessTarget)
                    : ImmutableSet.<BuildTarget>of()))
            .build();

    assertEquals(expectedTargets, buildLog.getAllTargets());
    buildLog.assertTargetBuiltLocally(headerSymlinkTreeTarget.toString());
    if (expectPreprocessorOutput) {
      buildLog.assertTargetBuiltLocally(preprocessTarget.toString());
    }
    buildLog.assertTargetBuiltLocally(compileTarget.toString());
    buildLog.assertTargetBuiltLocally(binaryTarget.toString());
    buildLog.assertTargetBuiltLocally(target.toString());

    // Clear for new build.
    workspace.resetBuildLogFile();

    // Check that running a build again results in no builds since everything is up to
    // date.
    workspace.runBuckCommand("build", target.toString()).assertSuccess();
    buildLog = workspace.getBuildLog();
    assertEquals(ImmutableSet.of(target, binaryTarget), buildLog.getAllTargets());
    buildLog.assertTargetHadMatchingRuleKey(binaryTarget.toString());
    buildLog.assertTargetHadMatchingRuleKey(target.toString());

    // Clear for new build.
    workspace.resetBuildLogFile();

    // Update the source file.
    workspace.replaceFileContents(sourceFull, "{}", "{ return 0; }");

    // Check that running a build again makes the source get recompiled and the binary
    // re-linked, but does not cause the header rules to re-run.
    workspace.runBuckCommand("build", target.toString()).assertSuccess();
    buildLog = workspace.getBuildLog();
    assertEquals(expectedTargets, buildLog.getAllTargets());
    buildLog.assertTargetHadMatchingRuleKey(headerSymlinkTreeTarget.toString());
    if (expectPreprocessorOutput) {
      buildLog.assertTargetBuiltLocally(preprocessTarget.toString());
    }
    buildLog.assertTargetBuiltLocally(compileTarget.toString());
    assertThat(
        buildLog.getLogEntry(binaryTarget).getSuccessType().get(),
        Matchers.not(Matchers.equalTo(BuildRuleSuccessType.MATCHING_RULE_KEY)));
    buildLog.assertTargetBuiltLocally(target.toString());

    // Clear for new build.
    workspace.resetBuildLogFile();

    // Update the source file.
    workspace.replaceFileContents(sourceFull, "{ return 0; }", "won't compile");

    // Check that running a build again makes the source get recompiled and the binary
    // re-linked, but does not cause the header rules to re-run.
    workspace.runBuckCommand("build", target.toString()).assertFailure();
    buildLog = workspace.getBuildLog();
    assertEquals(expectedTargets, buildLog.getAllTargets());
    buildLog.assertTargetHadMatchingRuleKey(headerSymlinkTreeTarget.toString());
    if (expectPreprocessorOutput) {
      buildLog.assertTargetBuiltLocally(preprocessTarget.toString());
    }
    assertThat(
        buildLog.getLogEntry(binaryTarget).getStatus(), Matchers.equalTo(BuildRuleStatus.CANCELED));
    assertThat(
        buildLog.getLogEntry(target).getStatus(), Matchers.equalTo(BuildRuleStatus.CANCELED));
  }
  @Test
  public void testInferCxxBinaryWithDeps() throws IOException {
    assumeTrue(Platform.detect() != Platform.WINDOWS);
    ProjectWorkspace workspace = InferHelper.setupCxxInferWorkspace(this, tmp);

    CxxPlatform cxxPlatform = DefaultCxxPlatforms.build(new CxxBuckConfig(new FakeBuckConfig()));
    BuildTarget inputBuildTarget = BuildTargetFactory.newInstance("//foo:binary_with_deps");
    String inputBuildTargetName =
        inputBuildTarget.withFlavors(CxxInferEnhancer.INFER).getFullyQualifiedName();

    /*
     * Build the given target and check that it succeeds.
     */
    workspace.runBuckCommand("build", inputBuildTargetName).assertSuccess();

    /*
     * Check that all the required build targets have been generated.
     */
    String sourceName = "src_with_deps.c";
    CxxSourceRuleFactory cxxSourceRuleFactory =
        CxxSourceRuleFactoryHelper.of(inputBuildTarget, cxxPlatform);
    // 1. create the targets of binary_with_deps
    // this is unflavored, but bounded to the InferCapture build rule
    BuildTarget topCaptureBuildTarget =
        cxxSourceRuleFactory.createInferCaptureBuildTarget(sourceName);

    // this is unflavored, but necessary to run the compiler successfully
    BuildTarget topHeaderSymlinkTreeTarget =
        CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
            inputBuildTarget, cxxPlatform.getFlavor(), HeaderVisibility.PRIVATE);

    // this is flavored, and denotes the analysis step (generates a local report)
    BuildTarget topInferAnalysisTarget =
        inputBuildTarget.withFlavors(CxxInferEnhancer.INFER_ANALYZE);

    // this is flavored and corresponds to the top level target (the one give in input to buck)
    BuildTarget topInferReportTarget = inputBuildTarget.withFlavors(CxxInferEnhancer.INFER);

    // 2. create the targets of dep_one
    BuildTarget depOneBuildTarget = BuildTargetFactory.newInstance("//foo:dep_one");
    String depOneSourceName = "dep_one.c";
    String depOneSourceFull = "foo/" + depOneSourceName;
    CxxSourceRuleFactory depOneSourceRuleFactory =
        CxxSourceRuleFactoryHelper.of(depOneBuildTarget, cxxPlatform);

    BuildTarget depOneCaptureBuildTarget =
        depOneSourceRuleFactory.createInferCaptureBuildTarget(depOneSourceName);

    BuildTarget depOneHeaderSymlinkTreeTarget =
        CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
            depOneBuildTarget, cxxPlatform.getFlavor(), HeaderVisibility.PRIVATE);

    BuildTarget depOneExportedHeaderSymlinkTreeTarget =
        CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
            depOneBuildTarget, cxxPlatform.getFlavor(), HeaderVisibility.PUBLIC);

    BuildTarget depOneInferAnalysisTarget =
        depOneCaptureBuildTarget.withFlavors(
            cxxPlatform.getFlavor(), CxxInferEnhancer.INFER_ANALYZE);

    // 3. create the targets of dep_two
    BuildTarget depTwoBuildTarget = BuildTargetFactory.newInstance("//foo:dep_two");
    CxxSourceRuleFactory depTwoSourceRuleFactory =
        CxxSourceRuleFactoryHelper.of(depTwoBuildTarget, cxxPlatform);

    BuildTarget depTwoCaptureBuildTarget =
        depTwoSourceRuleFactory.createInferCaptureBuildTarget("dep_two.c");

    BuildTarget depTwoHeaderSymlinkTreeTarget =
        CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
            depTwoBuildTarget, cxxPlatform.getFlavor(), HeaderVisibility.PRIVATE);

    BuildTarget depTwoExportedHeaderSymlinkTreeTarget =
        CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
            depTwoBuildTarget, cxxPlatform.getFlavor(), HeaderVisibility.PUBLIC);

    BuildTarget depTwoInferAnalysisTarget =
        depTwoCaptureBuildTarget.withFlavors(
            cxxPlatform.getFlavor(), CxxInferEnhancer.INFER_ANALYZE);

    // Check all the targets are in the buildLog
    assertEquals(
        ImmutableSet.of(
            topCaptureBuildTarget,
            topHeaderSymlinkTreeTarget,
            topInferAnalysisTarget,
            topInferReportTarget,
            depOneCaptureBuildTarget,
            depOneHeaderSymlinkTreeTarget,
            depOneExportedHeaderSymlinkTreeTarget,
            depOneInferAnalysisTarget,
            depTwoCaptureBuildTarget,
            depTwoHeaderSymlinkTreeTarget,
            depTwoExportedHeaderSymlinkTreeTarget,
            depTwoInferAnalysisTarget),
        workspace.getBuildLog().getAllTargets());

    /*
     * Check that running a build again results in no builds since nothing has changed.
     */
    workspace.resetBuildLogFile(); // clear for new build
    workspace.runBuckCommand("build", inputBuildTargetName).assertSuccess();
    BuckBuildLog buildLog = workspace.getBuildLog();
    assertEquals(ImmutableSet.of(topInferReportTarget), buildLog.getAllTargets());
    buildLog.assertTargetHadMatchingRuleKey(topInferReportTarget.toString());

    /*
     * Check that if a library source file changes then the capture/analysis rules run again on
     * the main target and on dep_one only.
     */
    workspace.resetBuildLogFile();
    workspace.replaceFileContents(depOneSourceFull, "flag > 0", "flag < 0");
    workspace.runBuckCommand("build", inputBuildTargetName).assertSuccess();
    buildLog = workspace.getBuildLog();
    assertEquals(
        ImmutableSet.of(
            topInferAnalysisTarget, // analysis runs again
            topInferReportTarget, // report runs again
            topCaptureBuildTarget, // cached
            depTwoInferAnalysisTarget, // cached
            depOneCaptureBuildTarget, // capture of the changed file runs again
            depOneExportedHeaderSymlinkTreeTarget, // cached
            depOneHeaderSymlinkTreeTarget, // cached
            depOneInferAnalysisTarget), // analysis of the library runs again
        buildLog.getAllTargets());
    buildLog.assertTargetBuiltLocally(topInferAnalysisTarget.toString());
    buildLog.assertTargetBuiltLocally(topInferReportTarget.toString());
    buildLog.assertTargetHadMatchingRuleKey(topCaptureBuildTarget.toString());
    buildLog.assertTargetHadMatchingRuleKey(depTwoInferAnalysisTarget.toString());
    buildLog.assertTargetBuiltLocally(depOneCaptureBuildTarget.toString());
    buildLog.assertTargetHadMatchingRuleKey(depOneExportedHeaderSymlinkTreeTarget.toString());
    buildLog.assertTargetHadMatchingRuleKey(depOneHeaderSymlinkTreeTarget.toString());
    buildLog.assertTargetBuiltLocally(depOneInferAnalysisTarget.toString());
  }
  @Test
  public void testInferCxxBinaryWithoutDeps() throws IOException {
    assumeTrue(Platform.detect() != Platform.WINDOWS);
    ProjectWorkspace workspace = InferHelper.setupCxxInferWorkspace(this, tmp);

    CxxPlatform cxxPlatform = DefaultCxxPlatforms.build(new CxxBuckConfig(new FakeBuckConfig()));
    BuildTarget inputBuildTarget = BuildTargetFactory.newInstance("//foo:simple");
    String inputBuildTargetName =
        inputBuildTarget.withFlavors(CxxInferEnhancer.INFER).getFullyQualifiedName();

    /*
     * Build the given target and check that it succeeds.
     */
    workspace.runBuckCommand("build", inputBuildTargetName).assertSuccess();

    /*
     * Check that all the required build targets have been generated.
     */
    String sourceName = "simple.cpp";
    String sourceFull = "foo/" + sourceName;

    CxxSourceRuleFactory cxxSourceRuleFactory =
        CxxSourceRuleFactoryHelper.of(inputBuildTarget, cxxPlatform);
    // this is unflavored, but bounded to the InferCapture build rule
    BuildTarget captureBuildTarget = cxxSourceRuleFactory.createInferCaptureBuildTarget(sourceName);
    // this is unflavored, but necessary to run the compiler successfully
    BuildTarget headerSymlinkTreeTarget =
        CxxDescriptionEnhancer.createHeaderSymlinkTreeTarget(
            inputBuildTarget, cxxPlatform.getFlavor(), HeaderVisibility.PRIVATE);
    // this is flavored, and denotes the analysis step (generates a local report)
    BuildTarget inferAnalysisTarget = inputBuildTarget.withFlavors(CxxInferEnhancer.INFER_ANALYZE);

    // this is flavored and corresponds to the top level target (the one give in input to buck)
    BuildTarget inferReportTarget = inputBuildTarget.withFlavors(CxxInferEnhancer.INFER);

    ImmutableSet<BuildTarget> expectedTargets =
        ImmutableSet.<BuildTarget>builder()
            .addAll(
                ImmutableSet.of(
                    headerSymlinkTreeTarget,
                    captureBuildTarget,
                    inferAnalysisTarget,
                    inferReportTarget))
            .build();

    BuckBuildLog buildLog = workspace.getBuildLog();
    assertEquals(expectedTargets, buildLog.getAllTargets());
    buildLog.assertTargetBuiltLocally(headerSymlinkTreeTarget.toString());
    buildLog.assertTargetBuiltLocally(captureBuildTarget.toString());
    buildLog.assertTargetBuiltLocally(inferAnalysisTarget.toString());
    buildLog.assertTargetBuiltLocally(inferReportTarget.toString());

    /*
     * Check that running a build again results in no builds since nothing has changed.
     */
    workspace.resetBuildLogFile(); // clear for new build
    workspace.runBuckCommand("build", inputBuildTargetName).assertSuccess();
    buildLog = workspace.getBuildLog();
    assertEquals(ImmutableSet.of(inferReportTarget), buildLog.getAllTargets());
    buildLog.assertTargetHadMatchingRuleKey(inferReportTarget.toString());

    /*
     * Check that changing the source file results in running the capture/analysis rules again.
     */
    workspace.resetBuildLogFile();
    workspace.replaceFileContents(sourceFull, "*s = 42;", "");
    workspace.runBuckCommand("build", inputBuildTargetName).assertSuccess();
    buildLog = workspace.getBuildLog();
    assertEquals(expectedTargets, buildLog.getAllTargets());
    buildLog.assertTargetBuiltLocally(captureBuildTarget.toString());
    buildLog.assertTargetBuiltLocally(inferAnalysisTarget.toString());
    buildLog.assertTargetHadMatchingRuleKey(headerSymlinkTreeTarget.toString());
  }