// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.rules.cpp;

import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.baseArtifactNames;
import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.baseNamesOf;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.truth.IterableSubject;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.analysis.AnalysisUtils;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.OutputGroupInfo;
import com.google.devtools.build.lib.analysis.mock.BazelAnalysisMock;
import com.google.devtools.build.lib.analysis.util.AnalysisMock;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.bazel.rules.BazelRuleClassProvider;
import com.google.devtools.build.lib.bazel.rules.CcRules;
import com.google.devtools.build.lib.bazel.rules.GenericRules;
import com.google.devtools.build.lib.bazel.rules.ToolchainRules;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.rules.core.CoreRules;
import com.google.devtools.build.lib.rules.platform.PlatformRules;
import com.google.devtools.build.lib.rules.repository.CoreWorkspaceRules;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig;
import com.google.devtools.common.options.InvocationPolicyEnforcer;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** A test for {@link CcCommon}. */
@RunWith(JUnit4.class)
public class CcCommonTest extends BuildViewTestCase {

  private static final String STATIC_LIB = "statically/libstatically.a";

  @Before
  public final void createBuildFiles() throws Exception {
    // Having lots of setUp code leads to bad running time. Don't add anything here!
    scratch.file("empty/BUILD",
        "cc_library(name = 'emptylib')",
        "cc_binary(name = 'emptybinary')");

    scratch.file("foo/BUILD",
        "cc_library(name = 'foo',",
        "           srcs = ['foo.cc'])");

    scratch.file("bar/BUILD",
        "cc_library(name = 'bar',",
        "           srcs = ['bar.cc'])");
  }

  @Test
  public void testSameCcFileTwice() throws Exception {
    scratch.file(
        "a/BUILD",
        "cc_library(name='a', srcs=['a1', 'a2'])",
        "filegroup(name='a1', srcs=['a.cc'])",
        "filegroup(name='a2', srcs=['a.cc'])");
    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//a:a");
    assertContainsEvent("Artifact 'a/a.cc' is duplicated");
  }

  @Test
  public void testSameHeaderFileTwice() throws Exception {
    scratch.file(
        "a/BUILD",
        "package(features=['parse_headers'])",
        "cc_library(name='a', srcs=['a1', 'a2', 'a.cc'])",
        "filegroup(name='a1', srcs=['a.h'])",
        "filegroup(name='a2', srcs=['a.h'])");
    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//a:a");
    assertNoEvents();
  }

  @Test
  public void testEmptyLibrary() throws Exception {
    ConfiguredTarget emptylib = getConfiguredTarget("//empty:emptylib");
    // We create .a for empty libraries, for simplicity (in Blaze).
    // But we avoid creating .so files for empty libraries,
    // because those have a potentially significant run-time startup cost.
    if (emptyShouldOutputStaticLibrary()) {
      assertThat(baseNamesOf(getFilesToBuild(emptylib))).isEqualTo("libemptylib.a");
    } else {
      assertThat(getFilesToBuild(emptylib)).isEmpty();
    }
    assertThat(
            emptylib
                .get(CcInfo.PROVIDER)
                .getCcLinkingInfo()
                .getDynamicModeParamsForExecutable()
                .getDynamicLibrariesForRuntime()
                .isEmpty())
        .isTrue();
  }

  protected boolean emptyShouldOutputStaticLibrary() {
    return !getAnalysisMock().isThisBazel();
  }

  @Test
  public void testEmptyBinary() throws Exception {
    ConfiguredTarget emptybin = getConfiguredTarget("//empty:emptybinary");
    assertThat(baseNamesOf(getFilesToBuild(emptybin)))
        .isEqualTo("emptybinary");
  }

  private List<String> getCopts(String target) throws Exception {
    ConfiguredTarget cLib = getConfiguredTarget(target);
    Artifact object = getOnlyElement(getOutputGroup(cLib, OutputGroupInfo.FILES_TO_COMPILE));
    CppCompileAction compileAction = (CppCompileAction) getGeneratingAction(object);
    return compileAction.getCompilerOptions();
  }

  @Test
  public void testCopts() throws Exception {
    scratch.file(
        "copts/BUILD",
        "cc_library(name = 'c_lib',",
        "    srcs = ['foo.cc'],",
        "    copts = [ '-Wmy-warning', '-frun-faster' ])");
    assertThat(getCopts("//copts:c_lib")).containsAllOf("-Wmy-warning", "-frun-faster");
  }

  @Test
  public void testCoptsTokenization() throws Exception {
    scratch.file(
        "copts/BUILD",
        "cc_library(name = 'c_lib',",
        "    srcs = ['foo.cc'],",
        "    copts = ['-Wmy-warning -frun-faster'])");
    List<String> copts = getCopts("//copts:c_lib");
    assertThat(copts).containsAllOf("-Wmy-warning", "-frun-faster");
  }

  @Test
  public void testCoptsNoTokenization() throws Exception {
    scratch.file(
        "copts/BUILD",
        "package(features = ['no_copts_tokenization'])",
        "cc_library(name = 'c_lib',",
        "    srcs = ['foo.cc'],",
        "    copts = ['-Wmy-warning -frun-faster'])");
    List<String> copts = getCopts("//copts:c_lib");
    assertThat(copts).contains("-Wmy-warning -frun-faster");
  }

  /**
   * Test that we handle ".a" files in cc_library srcs correctly when linking dynamically. In
   * particular, if srcs contains only the ".a" file for a library, with no corresponding ".so",
   * then we need to link in the ".a" file even when we're linking dynamically. If srcs contains
   * both ".a" and ".so" then we should only link in the ".so".
   */
  @Test
  public void testArchiveInCcLibrarySrcs() throws Exception {
    useConfiguration("--cpu=k8");
    ConfiguredTarget archiveInSrcsTest =
        scratchConfiguredTarget(
            "archive_in_srcs",
            "archive_in_srcs_test",
            "cc_test(name = 'archive_in_srcs_test',",
            "           srcs = ['archive_in_srcs_test.cc'],",
            "           deps = [':archive_in_srcs_lib'],",
            "           linkstatic = 0,)",
            "cc_library(name = 'archive_in_srcs_lib',",
            "           srcs = ['libstatic.a', 'libboth.a', 'libboth.so'])");
    List<String> artifactNames = baseArtifactNames(getLinkerInputs(archiveInSrcsTest));
    assertThat(artifactNames).containsAllOf("libboth.so", "libstatic.a");
    assertThat(artifactNames).doesNotContain("libboth.a");
  }

  private Iterable<Artifact> getLinkerInputs(ConfiguredTarget target) {
    Artifact executable = getExecutable(target);
    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(executable);
    return linkAction.getLinkCommandLine().getLinkerInputArtifacts();
  }

  @Test
  public void testDylibLibrarySuffixIsStripped() throws Exception {
    ConfiguredTarget archiveInSrcsTest =
        scratchConfiguredTarget(
            "archive_in_src_darwin",
            "archive_in_srcs",
            "cc_binary(name = 'archive_in_srcs',",
            "    srcs = ['libarchive.34.dylib'])");

    Artifact executable = getExecutable(archiveInSrcsTest);
    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(executable);
    assertThat(linkAction.getLinkCommandLine().toString()).contains(" -larchive.34 ");
  }

  @Test
  public void testLinkStaticStatically() throws Exception {
    ConfiguredTarget statically =
        scratchConfiguredTarget(
            "statically",
            "statically",
            "cc_library(name = 'statically',",
            "           srcs = ['statically.cc'],",
            "           linkstatic=1)");
    assertThat(
            statically
                .get(CcInfo.PROVIDER)
                .getCcLinkingInfo()
                .getDynamicModeParamsForExecutable()
                .getDynamicLibrariesForRuntime()
                .isEmpty())
        .isTrue();
    Artifact staticallyDotA = getOnlyElement(getFilesToBuild(statically));
    assertThat(getGeneratingAction(staticallyDotA)).isInstanceOf(CppLinkAction.class);
    PathFragment dotAPath = staticallyDotA.getExecPath();
    assertThat(dotAPath.getPathString()).endsWith(STATIC_LIB);
  }

  @Test
  public void testIsolatedDefines() throws Exception {
    ConfiguredTarget isolatedDefines =
        scratchConfiguredTarget(
            "isolated_defines",
            "defineslib",
            "cc_library(name = 'defineslib',",
            "           srcs = ['defines.cc'],",
            "           defines = ['FOO', 'BAR'])");
    assertThat(isolatedDefines.get(CcInfo.PROVIDER).getCcCompilationContext().getDefines())
        .containsExactly("FOO", "BAR")
        .inOrder();
  }

  @Test
  public void testStartEndLib() throws Exception {
    getAnalysisMock().ccSupport().setupCrosstool(mockToolsConfig,
        CrosstoolConfig.CToolchain.newBuilder().setSupportsStartEndLib(true).buildPartial());
    useConfiguration(
        // Prevent Android from trying to setup ARM crosstool by forcing it on system cpu.
        "--fat_apk_cpu=" + CrosstoolConfigurationHelper.defaultCpu(), "--start_end_lib");
    scratch.file(
        "test/BUILD",
        "cc_library(name='lib',",
        "           srcs=['lib.c'])",
        "cc_binary(name='bin',",
        "          srcs=['bin.c'])");

    ConfiguredTarget target = getConfiguredTarget("//test:bin");
    CppLinkAction action = (CppLinkAction) getGeneratingAction(getExecutable(target));
    for (Artifact input : action.getInputs()) {
      String name = input.getFilename();
      assertThat(!CppFileTypes.ARCHIVE.matches(name) && !CppFileTypes.PIC_ARCHIVE.matches(name))
          .isTrue();
    }
  }

  @Test
  public void testTempsWithDifferentExtensions() throws Exception {
    useConfiguration("--cpu=k8", "--save_temps");
    scratch.file(
        "ananas/BUILD",
        "cc_library(name='ananas',",
        "           srcs=['1.c', '2.cc', '3.cpp', '4.S', '5.h', '6.hpp'])");

    ConfiguredTarget ananas = getConfiguredTarget("//ananas:ananas");
    Iterable<String> temps =
        ActionsTestUtil.baseArtifactNames(getOutputGroup(ananas, OutputGroupInfo.TEMP_FILES));
    assertThat(temps)
        .containsExactly(
            "1.pic.i", "1.pic.s",
            "2.pic.ii", "2.pic.s",
            "3.pic.ii", "3.pic.s");
  }

  /**
   * Returns the {@link IterableSubject} for the {@link OutputGroupInfo#TEMP_FILES} generated when
   * {@code testTarget} is built for {@code cpu}.
   */
  private IterableSubject assertTempsForTarget(String cpu, String testTarget) throws Exception {
    useConfiguration("--cpu=" + cpu, "--host_cpu=" + cpu, "--save_temps");
    ConfiguredTarget target = getConfiguredTarget(testTarget);
    assertThat(target).isNotNull();

    List<String> temps =
        ActionsTestUtil.baseArtifactNames(getOutputGroup(target, OutputGroupInfo.TEMP_FILES));

    // Return the IterableSubject for the temp files.
    return assertThat(temps).named(cpu);
  }

  @Test
  public void testTempsForCc_k8() throws Exception {
    assertTempsForTarget("k8", "//foo:foo").containsExactly("foo.pic.ii", "foo.pic.s");
  }

  @Test
  public void testTempsForCc_piii() throws Exception {
    // PIII does not use PIC.
    assertTempsForTarget("piii", "//foo:foo").containsExactly("foo.ii", "foo.s");
  }

  @Test
  public void testTempsForC_k8() throws Exception {
    scratch.file("csrc/BUILD", "cc_library(name='csrc', srcs=['foo.c'])");
    assertTempsForTarget("k8", "//csrc:csrc").containsExactly("foo.pic.i", "foo.pic.s");
  }

  @Test
  public void testTempsForC_piii() throws Exception {
    scratch.file("csrc/BUILD", "cc_library(name='csrc', srcs=['foo.c'])");
    // PIII does not use PIC.
    assertTempsForTarget("piii", "//csrc:csrc").containsExactly("foo.i", "foo.s");
  }

  @Test
  public void testAlwaysLinkYieldsLo() throws Exception {
    ConfiguredTarget alwaysLink =
        scratchConfiguredTarget(
            "always_link",
            "always_link",
            "cc_library(name = 'always_link',",
            "           alwayslink = 1,",
            "           srcs = ['always_link.cc'])");
    assertThat(baseNamesOf(getFilesToBuild(alwaysLink))).contains("libalways_link.lo");
  }

  /**
   * Tests that nocopts= "-fPIC" takes '-fPIC' out of a compile invocation even if the crosstool
   * requires fPIC compilation (i.e. nocopts overrides crosstool settings on a rule-specific
   * basis).
   */
  @Test
  public void testNoCoptfPicOverride() throws Exception {
    getAnalysisMock().ccSupport().setupCrosstool(mockToolsConfig,
        CrosstoolConfig.CToolchain.newBuilder().setNeedsPic(true).buildPartial());
    useConfiguration(
        // Prevent Android from trying to setup ARM crosstool by forcing it on system cpu.
        "--fat_apk_cpu=" + CrosstoolConfigurationHelper.defaultCpu());

    scratch.file(
        "a/BUILD",
        "cc_binary(name = 'pic',",
        "           srcs = [ 'binary.cc' ])",
        "cc_binary(name = 'libpic.so',",
        "           srcs = [ 'binary.cc' ])",
        "cc_library(name = 'piclib',",
        "           srcs = [ 'library.cc' ])",
        "cc_binary(name = 'nopic',",
        "           srcs = [ 'binary.cc' ],",
        "           nocopts = '-fPIC')",
        "cc_binary(name = 'libnopic.so',",
        "           srcs = [ 'binary.cc' ],",
        "           nocopts = '-fPIC')",
        "cc_library(name = 'nopiclib',",
        "           srcs = [ 'library.cc' ],",
        "           nocopts = '-fPIC')");

    assertThat(getCppCompileAction("//a:pic").getArguments()).contains("-fPIC");
    assertThat(getCppCompileAction("//a:libpic.so").getArguments()).contains("-fPIC");
    assertThat(getCppCompileAction("//a:piclib").getArguments()).contains("-fPIC");
    assertThat(getCppCompileAction("//a:nopic").getArguments()).doesNotContain("-fPIC");
    assertThat(getCppCompileAction("//a:libnopic.so").getArguments()).doesNotContain("-fPIC");
    assertThat(getCppCompileAction("//a:nopiclib").getArguments()).doesNotContain("-fPIC");
  }

  @Test
  public void testPicModeAssembly() throws Exception {
    useConfiguration("--cpu=k8");
    scratch.file("a/BUILD", "cc_library(name='preprocess', srcs=['preprocess.S'])");
    List<String> argv = getCppCompileAction("//a:preprocess").getArguments();
    assertThat(argv).contains("-fPIC");
  }

  private CppCompileAction getCppCompileAction(String label) throws Exception {
    ConfiguredTarget target = getConfiguredTarget(label);
    List<CppCompileAction> compilationSteps =
        actionsTestUtil()
            .findTransitivePrerequisitesOf(
                getFilesToBuild(target).iterator().next(), CppCompileAction.class);
    return compilationSteps.get(0);
  }

  @Test
  public void testIsolatedIncludes() throws Exception {
    // Tests the (immediate) effect of declaring the includes attribute on a
    // cc_library.

    scratch.file(
        "bang/BUILD",
        "cc_library(name = 'bang',",
        "           srcs = ['bang.cc'],",
        "           includes = ['bang_includes'])");

    ConfiguredTarget foo = getConfiguredTarget("//bang:bang");

    String includesRoot = "bang/bang_includes";
    assertThat(foo.get(CcInfo.PROVIDER).getCcCompilationContext().getSystemIncludeDirs())
        .containsAllOf(
            PathFragment.create(includesRoot),
            targetConfig.getGenfilesFragment().getRelative(includesRoot));
  }

  @Test
  public void testDisabledGenfilesDontShowUpInSystemIncludePaths() throws Exception {
    scratch.file(
        "bang/BUILD",
        "cc_library(name = 'bang',",
        "           srcs = ['bang.cc'],",
        "           includes = ['bang_includes'])");
    String includesRoot = "bang/bang_includes";

    useConfiguration("--noincompatible_merge_genfiles_directory");
    ConfiguredTarget foo = getConfiguredTarget("//bang:bang");
    PathFragment genfilesDir = targetConfig.getGenfilesFragment().getRelative(includesRoot);
    assertThat(foo.get(CcInfo.PROVIDER).getCcCompilationContext().getSystemIncludeDirs())
        .contains(genfilesDir);

    useConfiguration("--incompatible_merge_genfiles_directory");
    foo = getConfiguredTarget("//bang:bang");
    assertThat(foo.get(CcInfo.PROVIDER).getCcCompilationContext().getSystemIncludeDirs())
        .doesNotContain(genfilesDir);
  }

  @Test
  public void testUseIsystemForIncludes() throws Exception {
    // Tests the effect of --use_isystem_for_includes.

    scratch.file(
        "no_includes/BUILD",
        "cc_library(name = 'no_includes',",
        "           srcs = ['no_includes.cc'])");
    ConfiguredTarget noIncludes = getConfiguredTarget("//no_includes:no_includes");

    scratch.file(
        "bang/BUILD",
        "cc_library(name = 'bang',",
        "           srcs = ['bang.cc'],",
        "           includes = ['bang_includes'])");

    ConfiguredTarget foo = getConfiguredTarget("//bang:bang");

    String includesRoot = "bang/bang_includes";
    List<PathFragment> expected =
        new ImmutableList.Builder<PathFragment>()
            .addAll(
                noIncludes.get(CcInfo.PROVIDER).getCcCompilationContext().getSystemIncludeDirs())
            .add(PathFragment.create(includesRoot))
            .add(targetConfig.getGenfilesFragment().getRelative(includesRoot))
            .add(targetConfig.getBinFragment().getRelative(includesRoot))
            .build();
    assertThat(foo.get(CcInfo.PROVIDER).getCcCompilationContext().getSystemIncludeDirs())
        .containsExactlyElementsIn(expected);
  }

  @Test
  public void testCcTestDisallowsAlwaysLink() throws Exception {
    scratch.file(
        "cc/common/BUILD",
        "cc_library(name = 'lib1',",
        "           srcs = ['foo1.cc'],",
        "           deps = ['//left'])",
        "",
        "cc_test(name = 'testlib',",
        "       deps = [':lib1'],",
        "       alwayslink=1)");
    reporter.removeHandler(failFastHandler);
    getPackageManager().getPackage(reporter, PackageIdentifier.createInMainRepo("cc/common"));
    assertContainsEvent(
        "//cc/common:testlib: no such attribute 'alwayslink'" + " in 'cc_test' rule");
  }

  @Test
  public void testCcTestBuiltWithFissionHasDwp() throws Exception {
    // Tests that cc_tests built statically and with Fission will have the .dwp file
    // in their runfiles.

    useConfiguration("--cpu=k8", "--build_test_dwp", "--dynamic_mode=off", "--fission=yes");
    ConfiguredTarget target =
        scratchConfiguredTarget(
            "mypackage", "mytest", "cc_test(name = 'mytest', srcs = ['mytest.cc'])");

    Iterable<Artifact> runfiles = collectRunfiles(target);
    assertThat(baseArtifactNames(runfiles)).contains("mytest.dwp");
  }

  @Test
  public void testCcLibraryBadIncludesWarnedAndIgnored() throws Exception {
    checkWarning(
        "badincludes",
        "flaky_lib",
        // message:
        "in includes attribute of cc_library rule //badincludes:flaky_lib: "
            + "ignoring invalid absolute path '//third_party/procps/proc'",
        // build file:
        "cc_library(name = 'flaky_lib',",
        "   srcs = [ 'ok.cc' ],",
        "   includes = [ '//third_party/procps/proc' ])");
  }

  @Test
  public void testCcLibraryUplevelIncludesWarned() throws Exception {
    checkWarning(
        "third_party/uplevel",
        "lib",
        // message:
        "in includes attribute of cc_library rule //third_party/uplevel:lib: '../bar' resolves to "
            + "'third_party/bar' not below the relative path of its package 'third_party/uplevel'. "
            + "This will be an error in the future",
        // build file:
        "licenses(['unencumbered'])",
        "cc_library(name = 'lib',",
        "           srcs = ['foo.cc'],",
        "           includes = ['../bar'])");
  }

  @Test
  public void testCcLibraryNonThirdPartyIncludesWarned() throws Exception {
    if (getAnalysisMock().isThisBazel()) {
      return;
    }

    checkWarning(
        "topdir",
        "lib",
        // message:
        "in includes attribute of cc_library rule //topdir:lib: './' resolves to 'topdir' not "
            + "in 'third_party'. This will be an error in the future",
        // build file:
        "cc_library(name = 'lib',",
        "           srcs = ['foo.cc'],",
        "           includes = ['./'])");
  }

  @Test
  public void testCcLibraryThirdPartyIncludesNotWarned() throws Exception {
    eventCollector.clear();
    ConfiguredTarget target =
        scratchConfiguredTarget(
            "third_party/pkg",
            "lib",
            "licenses(['unencumbered'])",
            "cc_library(name = 'lib',",
            "           srcs = ['foo.cc'],",
            "           includes = ['./'])");
    assertThat(view.hasErrors(target)).isFalse();
    assertNoEvents();
  }

  @Test
  public void testCcLibraryExternalIncludesNotWarned() throws Exception {
    eventCollector.clear();
    FileSystemUtils.appendIsoLatin1(
        scratch.resolve("WORKSPACE"),
        "local_repository(",
        "    name = 'pkg',",
        "    path = '/foo')");
    getSkyframeExecutor()
        .invalidateFilesUnderPathForTesting(
            reporter,
            new ModifiedFileSet.Builder().modify(PathFragment.create("WORKSPACE")).build(),
            Root.fromPath(rootDirectory));
    FileSystemUtils.createDirectoryAndParents(scratch.resolve("/foo/bar"));
    scratch.file("/foo/WORKSPACE", "workspace(name = 'pkg')");
    scratch.file(
        "/foo/bar/BUILD",
        "cc_library(name = 'lib',",
        "           srcs = ['foo.cc'],",
        "           includes = ['./'])");
    Label label = Label.parseAbsolute("@pkg//bar:lib", ImmutableMap.of());
    ConfiguredTarget target = view.getConfiguredTargetForTesting(reporter, label, targetConfig);
    assertThat(view.hasErrors(target)).isFalse();
    assertNoEvents();
  }

  @Test
  public void testCcLibraryRootIncludesError() throws Exception {
    checkError(
        "third_party/root",
        "lib",
        // message:
        "in includes attribute of cc_library rule //third_party/root:lib: '../..' resolves to the "
            + "workspace root, which would allow this rule and all of its transitive dependents to "
            + "include any file in your workspace. Please include only what you need",
        // build file:
        "licenses(['unencumbered'])",
        "cc_library(name = 'lib',",
        "           srcs = ['foo.cc'],",
        "           includes = ['../..'])");
  }

  @Test
  public void testStaticallyLinkedBinaryNeedsSharedObject() throws Exception {
    scratch.file(
        "third_party/sophos_av_pua/BUILD",
        "licenses(['notice'])",
        "cc_library(name = 'savi',",
        "           srcs = [ 'lib/libsavi.so' ])");
    ConfiguredTarget wrapsophos =
        scratchConfiguredTarget(
            "quality/malware/support",
            "wrapsophos",
            "cc_library(name = 'sophosengine',",
            "           srcs = [ 'sophosengine.cc' ],",
            "           deps = [ '//third_party/sophos_av_pua:savi' ])",
            "cc_binary(name = 'wrapsophos',",
            "          srcs = [ 'wrapsophos.cc' ],",
            "          deps = [ ':sophosengine' ],",
            "          linkstatic=1)");

    List<String> artifactNames = baseArtifactNames(getLinkerInputs(wrapsophos));
    assertThat(artifactNames).contains("libsavi.so");
  }

  @Test
  public void testExpandLabelInLinkoptsAgainstSrc() throws Exception {
    scratch.file(
        "coolthing/BUILD",
        "genrule(name = 'build-that',",
        "  srcs = [ 'foo' ],",
        "  outs = [ 'nicelib.a' ],",
        "  cmd = 'cat  $< > $@')");
    // In reality the linkopts might contain several externally-provided
    // '.a' files with cyclic dependencies amongst them, but in this test
    // it suffices to show that one label in linkopts was resolved.
    scratch.file(
        "myapp/BUILD",
        "cc_binary(name = 'myapp',",
        "    srcs = [ '//coolthing:nicelib.a' ],",
        "    linkopts = [ '//coolthing:nicelib.a' ])");
    ConfiguredTarget theLib = getConfiguredTarget("//coolthing:build-that");
    ConfiguredTarget theApp = getConfiguredTarget("//myapp:myapp");
    // make sure we did not print warnings about the linkopt
    assertNoEvents();
    // make sure the binary is dependent on the static lib
    Action linkAction = getGeneratingAction(getOnlyElement(getFilesToBuild(theApp)));
    ImmutableList<Artifact> filesToBuild = ImmutableList.copyOf(getFilesToBuild(theLib));
    assertThat(ImmutableSet.copyOf(linkAction.getInputs()).containsAll(filesToBuild)).isTrue();
  }

  @Test
  public void testCcLibraryWithDashStatic() throws Exception {
    checkWarning(
        "badlib",
        "lib_with_dash_static",
        // message:
        "in linkopts attribute of cc_library rule //badlib:lib_with_dash_static: "
            + "Using '-static' here won't work. Did you mean to use 'linkstatic=1' instead?",
        // build file:
        "cc_library(name = 'lib_with_dash_static',",
        "   srcs = [ 'ok.cc' ],",
        "   linkopts = [ '-static' ])");
  }

  @Test
  public void testStampTests() throws Exception {
    scratch.file(
        "test/BUILD",
        "cc_test(name ='a', srcs = ['a.cc'])",
        "cc_test(name ='b', srcs = ['b.cc'], stamp = 0)",
        "cc_test(name ='c', srcs = ['c.cc'], stamp = 1)",
        "cc_binary(name ='d', srcs = ['d.cc'])",
        "cc_binary(name ='e', srcs = ['e.cc'], stamp = 0)",
        "cc_binary(name ='f', srcs = ['f.cc'], stamp = 1)");

    assertStamping(false, "//test:a");
    assertStamping(false, "//test:b");
    assertStamping(true, "//test:c");
    assertStamping(true, "//test:d");
    assertStamping(false, "//test:e");
    assertStamping(true, "//test:f");

    useConfiguration("--stamp");
    assertStamping(false, "//test:a");
    assertStamping(false, "//test:b");
    assertStamping(true, "//test:c");
    assertStamping(true, "//test:d");
    assertStamping(false, "//test:e");
    assertStamping(true, "//test:f");

    useConfiguration("--nostamp");
    assertStamping(false, "//test:a");
    assertStamping(false, "//test:b");
    assertStamping(true, "//test:c");
    assertStamping(false, "//test:d");
    assertStamping(false, "//test:e");
    assertStamping(true, "//test:f");
  }

  private void assertStamping(boolean enabled, String label) throws Exception {
    assertThat(AnalysisUtils.isStampingEnabled(getRuleContext(getConfiguredTarget(label))))
        .isEqualTo(enabled);
  }

  @Test
  public void testIncludeRelativeHeadersAboveExecRoot() throws Exception {
    checkError(
        "test",
        "bad_relative_include",
        "Path references a path above the execution root.",
        "cc_library(name='bad_relative_include', srcs=[], includes=['../..'])");
  }

  @Test
  public void testIncludeAbsoluteHeaders() throws Exception {
    checkWarning(
        "test",
        "bad_absolute_include",
        "ignoring invalid absolute path",
        "cc_library(name='bad_absolute_include', srcs=[], includes=['/usr/include/'])");
  }

  @Test
  public void testSelectPreferredLibrariesInvariant() {
    // All combinations of libraries:
    // a - static+pic+shared
    // b - static+pic
    // c - static+shared
    // d - static
    // e - pic+shared
    // f - pic
    // g - shared
    CcLinkingOutputs linkingOutputs =
        CcLinkingOutputs.builder()
            .addStaticLibraries(
                ImmutableList.copyOf(
                    LinkerInputs.opaqueLibrariesToLink(ArtifactCategory.STATIC_LIBRARY,
                        Arrays.asList(
                            getSourceArtifact("liba.a"),
                            getSourceArtifact("libb.a"),
                            getSourceArtifact("libc.a"),
                            getSourceArtifact("libd.a")))))
            .addPicStaticLibraries(
                ImmutableList.copyOf(
                    LinkerInputs.opaqueLibrariesToLink(ArtifactCategory.STATIC_LIBRARY,
                        Arrays.asList(
                            getSourceArtifact("liba.pic.a"),
                            getSourceArtifact("libb.pic.a"),
                            getSourceArtifact("libe.pic.a"),
                            getSourceArtifact("libf.pic.a")))))
            .addDynamicLibraries(
                ImmutableList.copyOf(
                    LinkerInputs.opaqueLibrariesToLink(ArtifactCategory.DYNAMIC_LIBRARY,
                        Arrays.asList(
                            getSourceArtifact("liba.so"),
                            getSourceArtifact("libc.so"),
                            getSourceArtifact("libe.so"),
                            getSourceArtifact("libg.so")))))
            .build();

    // Whether linkShared is true or false, this should return the identical results.
    List<Artifact> sharedLibraries1 =
        FileType.filterList(
            LinkerInputs.toLibraryArtifacts(linkingOutputs.getPreferredLibraries(true, false)),
            CppFileTypes.SHARED_LIBRARY);
    List<Artifact> sharedLibraries2 =
        FileType.filterList(
            LinkerInputs.toLibraryArtifacts(linkingOutputs.getPreferredLibraries(true, true)),
            CppFileTypes.SHARED_LIBRARY);
    assertThat(sharedLibraries2).isEqualTo(sharedLibraries1);
  }

  /** Tests that shared libraries of the form "libfoo.so.1.2" are permitted within "srcs". */
  @Test
  public void testVersionedSharedLibrarySupport() throws Exception {
    ConfiguredTarget target =
        scratchConfiguredTarget(
            "mypackage",
            "mybinary",
            "cc_binary(name = 'mybinary',",
            "           srcs = ['mybinary.cc'],",
            "           deps = [':mylib'])",
            "cc_library(name = 'mylib',",
            "           srcs = ['libshared.so', 'libshared.so.1.1', 'foo.cc'])");
    List<String> artifactNames = baseArtifactNames(getLinkerInputs(target));
    assertThat(artifactNames).containsAllOf("libshared.so", "libshared.so.1.1");
  }

  @Test
  public void testNoHeaderInHdrsWarning() throws Exception {
    checkWarning(
        "hdrs_filetypes",
        "foo",
        "in hdrs attribute of cc_library rule //hdrs_filetypes:foo: file 'foo.a' "
            + "from target '//hdrs_filetypes:foo.a' is not allowed in hdrs",
        "cc_library(name = 'foo',",
        "    srcs = [],",
        "    hdrs = ['foo.a'])");
  }

  @Test
  public void testLibraryInHdrs() throws Exception {
    scratchConfiguredTarget("a", "a",
        "cc_library(name='a', srcs=['a.cc'], hdrs=[':b'])",
        "cc_library(name='b', srcs=['b.cc'])");
  }

  @Test
  public void testExpandedLinkopts() throws Exception {
    scratch.file(
        "a/BUILD",
        "genrule(name = 'linker', cmd='generate', outs=['a.lds'])",
        "cc_binary(",
        "    name='bin',",
        "    srcs=['b.cc'],",
        "    linkopts=['-Wl,@$(location a.lds)'],",
        "    deps=['a.lds'])");
    ConfiguredTarget target = getConfiguredTarget("//a:bin");
    CppLinkAction action =
        (CppLinkAction) getGeneratingAction(getOnlyElement(getFilesToBuild(target)));
    assertThat(action.getLinkCommandLine().getLinkopts()).containsExactly(
        String.format("-Wl,@%s/a/a.lds", getTargetConfiguration().getGenfilesDirectory(
            RepositoryName.MAIN).getExecPath().getPathString()));
  }

  @Test
  public void testProvidesLinkerScriptToLinkAction() throws Exception {
    scratch.file(
        "a/BUILD",
        "cc_binary(",
        "    name='bin',",
        "    srcs=['b.cc'],",
        "    linkopts=['-Wl,@$(location a.lds)'],",
        "    deps=['a.lds'])");
    ConfiguredTarget target = getConfiguredTarget("//a:bin");
    CppLinkAction action =
        (CppLinkAction) getGeneratingAction(getOnlyElement(getFilesToBuild(target)));
    Iterable<Artifact> linkInputs = action.getInputs();
    assertThat(ActionsTestUtil.baseArtifactNames(linkInputs)).contains("a.lds");
  }

  @Test
  public void testIncludeManglingSmoke() throws Exception {
    scratch.file(
        "third_party/a/BUILD",
        "licenses(['notice'])",
        "cc_library(name='a', hdrs=['v1/b/c.h'], strip_include_prefix='v1', include_prefix='lib')");

    ConfiguredTarget lib = getConfiguredTarget("//third_party/a");
    CcCompilationContext ccCompilationContext = lib.get(CcInfo.PROVIDER).getCcCompilationContext();
    assertThat(ActionsTestUtil.prettyArtifactNames(ccCompilationContext.getDeclaredIncludeSrcs()))
        .containsExactly("third_party/a/_virtual_includes/a/lib/b/c.h");
    assertThat(ccCompilationContext.getIncludeDirs())
        .containsExactly(
            getTargetConfiguration()
                .getBinFragment()
                .getRelative("third_party/a/_virtual_includes/a"));
  }

  @Test
  public void testUpLevelReferencesInIncludeMangling() throws Exception {
    scratch.file(
        "third_party/a/BUILD",
        "licenses(['notice'])",
        "cc_library(name='sip', srcs=['a.h'], strip_include_prefix='a/../b')",
        "cc_library(name='ip', srcs=['a.h'], include_prefix='a/../b')",
        "cc_library(name='ipa', srcs=['a.h'], include_prefix='/foo')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//third_party/a:sip");
    assertContainsEvent("should not contain uplevel references");

    eventCollector.clear();
    getConfiguredTarget("//third_party/a:ip");
    assertContainsEvent("should not contain uplevel references");

    eventCollector.clear();
    getConfiguredTarget("//third_party/a:ipa");
    assertContainsEvent("should be a relative path");
  }

  @Test
  public void testAbsoluteAndRelativeStripPrefix() throws Exception {
    scratch.file("third_party/a/BUILD",
        "licenses(['notice'])",
        "cc_library(name='relative', hdrs=['v1/b.h'], strip_include_prefix='v1')",
        "cc_library(name='absolute', hdrs=['v1/b.h'], strip_include_prefix='/third_party')");

    CcCompilationContext relative =
        getConfiguredTarget("//third_party/a:relative")
            .get(CcInfo.PROVIDER)
            .getCcCompilationContext();
    CcCompilationContext absolute =
        getConfiguredTarget("//third_party/a:absolute")
            .get(CcInfo.PROVIDER)
            .getCcCompilationContext();

    assertThat(ActionsTestUtil.prettyArtifactNames(relative.getDeclaredIncludeSrcs()))
        .containsExactly("third_party/a/_virtual_includes/relative/b.h");
    assertThat(ActionsTestUtil.prettyArtifactNames(absolute.getDeclaredIncludeSrcs()))
        .containsExactly("third_party/a/_virtual_includes/absolute/a/v1/b.h");
  }

  @Test
  public void testArtifactNotUnderStripPrefix() throws Exception {
    scratch.file("third_party/a/BUILD",
        "licenses(['notice'])",
        "cc_library(name='a', hdrs=['v1/b.h'], strip_include_prefix='v2')");

    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//third_party/a:a");
    assertContainsEvent(
        "header 'third_party/a/v1/b.h' is not under the specified strip prefix 'third_party/a/v2'");
  }

  @Test
  public void testSymlinkActionIsNotRegisteredWhenIncludePrefixDoesntChangePath() throws Exception {
    scratch.file(
        "third_party/BUILD",
        "licenses(['notice'])",
        "cc_library(name='a', hdrs=['a.h'], include_prefix='third_party')");

    CcCompilationContext ccCompilationContext =
        getConfiguredTarget("//third_party:a").get(CcInfo.PROVIDER).getCcCompilationContext();
    assertThat(ActionsTestUtil.prettyArtifactNames(ccCompilationContext.getDeclaredIncludeSrcs()))
        .doesNotContain("third_party/_virtual_includes/a/third_party/a.h");
  }

  @Test
  public void
  testConfigureFeaturesDoesntCrashOnCollidingFeaturesExceptionButReportsRuleErrorCleanly()
      throws Exception {
    reporter.removeHandler(failFastHandler);
    getAnalysisMock()
        .ccSupport()
        .setupCrosstool(
            mockToolsConfig,
            "feature { name: 'a1' provides: 'a' }",
            "feature { name: 'a2' provides: 'a' }");
    useConfiguration("--features=a1", "--features=a2");

    scratch.file("x/BUILD", "cc_library(name = 'foo', srcs = ['a.cc'])");
    scratch.file("x/a.cc");

    getConfiguredTarget("//x:foo");
    assertContainsEvent("Symbol a is provided by all of the following features: a1 a2");
  }

  /**
   * Tests for the case where there are only C++ rules defined.
   */
  @RunWith(JUnit4.class)
  public static class OnlyCppRules extends CcCommonTest {

    @Override
    protected AnalysisMock getAnalysisMock() {
      final AnalysisMock original = BazelAnalysisMock.INSTANCE;
      return new AnalysisMock.Delegate(original) {
        @Override
        public ConfiguredRuleClassProvider createRuleClassProvider() {
          ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
          builder.setToolsRepository("@bazel_tools");
          BazelRuleClassProvider.BAZEL_SETUP.init(builder);
          CoreRules.INSTANCE.init(builder);
          CoreWorkspaceRules.INSTANCE.init(builder);
          PlatformRules.INSTANCE.init(builder);
          ToolchainRules.INSTANCE.init(builder);
          GenericRules.INSTANCE.init(builder);
          CcRules.INSTANCE.init(builder);
          return builder.build();
        }

        @Override
        public InvocationPolicyEnforcer getInvocationPolicyEnforcer() {
          return new InvocationPolicyEnforcer(null);
        }

        @Override
        public boolean isThisBazel() {
          return true;
        }
      };
    }

    @Override
    public void testNoCoptfPicOverride() throws Exception {
      // Test sets --fat_apk_cpu, which doesn't exist.
    }

    @Override
    public void testStartEndLib() throws Exception {
      // Test sets --fat_apk_cpu, which doesn't exist.
    }

    @Override
    public void testExpandLabelInLinkoptsAgainstSrc() throws Exception {
      // genrule now requires JavaConfiguration, so isn't appropriate for OnlyCppRules.
    }

    @Override
    public void testExpandedLinkopts() throws Exception {
      // genrule now requires JavaConfiguration, so isn't appropriate for OnlyCppRules.
    }
  }
}
