Open-source cpp tests

Open-source:
- lib/rules/cpp/CcBadDependenciesTest.java
- lib/rules/cpp/CcBinaryThinLtoTest.java
- lib/rules/cpp/CcCompilationHelperTest.java
- lib/rules/cpp/CppConfigurationSkylarkTest.java
- lib/rules/cpp/CppSysrootTest.java
- lib/rules/cpp/LinkCommandLineTest.java
- lib/rules/cpp/SpawnGccStrategyTest.java

Some test cases of CcBinaryThinLtoTest don't work
on Windows. Those have been moved into
NonWindowsCcBinaryThinLtoTest.

RELNOTES: none
PiperOrigin-RevId: 293759606
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
index 397ec4c..d445555 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
@@ -19,8 +19,12 @@
         exclude = [
             "CcImportBaseConfiguredTargetTest.java",
             "SkylarkCcCommonTestHelper.java",
+            "NonWindowsCcBinaryThinLtoTest.java",
         ],
-    ) + ["proto/CcProtoLibraryTest.java"],
+    ) + ["proto/CcProtoLibraryTest.java"] + select({
+        "//src/conditions:windows": [],
+        "//conditions:default": ["NonWindowsCcBinaryThinLtoTest.java"],
+    }),
     resources = [
         "@rules_cc//cc:srcs",
     ],
@@ -46,6 +50,7 @@
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
         "//src/main/java/com/google/devtools/build/lib/concurrent",
+        "//src/main/java/com/google/devtools/build/lib/rules/apple",
         "//src/main/java/com/google/devtools/build/lib/rules/cpp",
         "//src/main/java/com/google/devtools/build/lib/rules/platform",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcBadDependenciesTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcBadDependenciesTest.java
new file mode 100644
index 0000000..9f03e68
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcBadDependenciesTest.java
@@ -0,0 +1,67 @@
+// Copyright 2020 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 com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** A test for dependencies between C++ libraries. */
+@RunWith(JUnit4.class)
+public final class CcBadDependenciesTest extends BuildViewTestCase {
+  private ConfiguredTarget configure(String targetLabel) throws Exception {
+    return getConfiguredTarget(targetLabel);
+  }
+
+  @Test
+  public void testRejectsSingleUnknownSourceFile() throws Exception {
+    reporter.removeHandler(failFastHandler);
+    scratch.file("foo/BUILD",
+                "cc_library(name = 'foo', srcs = ['unknown.oops'])");
+    scratch.file("foo/unknown.oops", "foo");
+    configure("//foo:foo");
+    assertContainsEvent(getErrorMsgMisplacedFiles(
+        "srcs", "cc_library", "//foo:foo", "//foo:unknown.oops"));
+  }
+
+  @Test
+  public void testAcceptsDependencyWithAtLeastOneGoodSource() throws Exception {
+    scratch.file("dependency/BUILD",
+                "genrule(name = 'goodandbad_gen', ",
+                "        cmd = '/bin/true',",
+                "        outs = ['good.cc', 'bad.oops'])");
+    scratch.file("foo/BUILD",
+                "cc_library(name = 'foo',",
+                "           srcs = ['//dependency:goodandbad_gen'])");
+    configure("//foo:foo");
+  }
+
+  @Test
+  public void testRejectsBadGeneratedFile() throws Exception {
+    reporter.removeHandler(failFastHandler);
+    scratch.file("dependency/BUILD",
+        "genrule(name = 'generated', ",
+        "        cmd = '/bin/true',",
+        "        outs = ['bad.oops'])");
+    scratch.file("foo/BUILD",
+        "cc_library(name = 'foo',",
+        "           srcs = ['//dependency:generated'])");
+    configure("//foo:foo");
+    assertContainsEvent(
+        getErrorMsgNoGoodFiles("srcs", "cc_library", "//foo:foo", "//dependency:generated"));
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcBinaryThinLtoTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcBinaryThinLtoTest.java
new file mode 100644
index 0000000..0a8ab0d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcBinaryThinLtoTest.java
@@ -0,0 +1,1934 @@
+// Copyright 2020 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.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.util.AnalysisMock;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.devtools.build.lib.packages.util.Crosstool.CcToolchainConfig;
+import com.google.devtools.build.lib.packages.util.MockCcSupport;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetValue;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for cc_binary with ThinLTO. */
+@RunWith(JUnit4.class)
+public class CcBinaryThinLtoTest extends BuildViewTestCase {
+
+  @Before
+  public void createBasePkg() throws IOException {
+    scratch.overwriteFile(
+        "base/BUILD", "cc_library(name = 'system_malloc', visibility = ['//visibility:public'])");
+  }
+
+  public void createBuildFiles(String targetName, String... extraCcBinaryParameters)
+      throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['thin_lto'])",
+        "",
+        "cc_binary(name = '" + targetName + "',",
+        "          srcs = ['binfile.cc', ],",
+        "          deps = [ ':lib' ], ",
+        String.join("", extraCcBinaryParameters),
+        "          malloc = '//base:system_malloc')",
+        "cc_library(name = 'lib',",
+        "        srcs = ['libfile.cc'],",
+        "        hdrs = ['libfile.h'],",
+        "        linkstamp = 'linkstamp.cc',",
+        "       )");
+
+    scratch.file("pkg/binfile.cc", "#include \"pkg/libfile.h\"", "int main() { return pkg(); }");
+    scratch.file("pkg/libfile.cc", "int pkg() { return 42; }");
+    scratch.file("pkg/libfile.h", "int pkg();");
+    scratch.file("pkg/linkstamp.cc");
+  }
+
+  public void createTestFiles(String extraTestParameters, String extraLibraryParameters)
+      throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['thin_lto'])",
+        "cc_test(",
+        "    name = 'bin_test',",
+        "    srcs = ['bin_test.cc', ],",
+        "    deps = [ ':lib' ], ",
+        extraTestParameters,
+        "    malloc = '//base:system_malloc'",
+        ")",
+        "cc_test(",
+        "    name = 'bin_test2',",
+        "    srcs = ['bin_test2.cc', ],",
+        "    deps = [ ':lib' ], ",
+        extraTestParameters,
+        "    malloc = '//base:system_malloc'",
+        ")",
+        "cc_library(",
+        "    name = 'lib',",
+        "    srcs = ['libfile.cc'],",
+        "    hdrs = ['libfile.h'],",
+        extraLibraryParameters,
+        "    linkstamp = 'linkstamp.cc',",
+        ")");
+
+    scratch.file("pkg/bin_test.cc", "#include \"pkg/libfile.h\"", "int main() { return pkg(); }");
+    scratch.file("pkg/bin_test2.cc", "#include \"pkg/libfile.h\"", "int main() { return pkg(); }");
+    scratch.file("pkg/libfile.cc", "int pkg() { return 42; }");
+    scratch.file("pkg/libfile.h", "int pkg();");
+    scratch.file("pkg/linkstamp.cc");
+  }
+
+  @Test
+  public void testActionGraph() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration("--noincompatible_make_thinlto_command_lines_standalone");
+
+    /*
+    We follow the chain from the final product backwards.
+
+    binary <=[Link]=
+    .lto/...o <=[LTOBackend]=
+    {.o.thinlto.bc,.o.imports} <=[LTOIndexing]=
+    .o <= [CppCompile] .cc
+    */
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+    assertThat(ActionsTestUtil.getFirstArtifactEndingWith(linkAction.getInputs(), "linkstamp.o"))
+        .isNotNull();
+
+    List<String> commandLine = linkAction.getLinkCommandLine().getRawLinkArgv();
+    String prefix = getTargetConfiguration().getOutputDirectory(RepositoryName.MAIN)
+        .getExecPathString();
+    assertThat(commandLine)
+        .containsAtLeast(
+            prefix + "/bin/pkg/bin.lto.merged.o",
+            "thinlto_param_file=" + prefix + "/bin/pkg/bin-lto-final.params")
+        .inOrder();
+
+    // We have no bitcode files: all files have pkg/bin.lto/
+    for (String arg : commandLine) {
+      if (arg.contains("_objs") && !arg.contains("linkstamp.o")) {
+        assertThat(arg).contains("pkg/bin.lto");
+      }
+    }
+
+    assertThat(artifactsToStrings(linkAction.getInputs()))
+        .containsAtLeast(
+            "bin pkg/bin.lto/pkg/_objs/bin/binfile.pic.o",
+            "bin pkg/bin.lto/pkg/_objs/lib/libfile.pic.o",
+            "bin pkg/bin-2.params",
+            "bin pkg/bin-lto-final.params");
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+
+    assertThat(artifactsToStrings(backendAction.getInputs()))
+        .containsAtLeast(
+            "bin pkg/bin.lto/pkg/_objs/bin/binfile.pic.o.thinlto.bc",
+            "bin pkg/bin.lto/pkg/_objs/bin/binfile.pic.o.imports");
+
+    assertThat(backendAction.getArguments())
+        .containsAtLeast(
+            "thinlto_index=" + prefix + "/bin/pkg/bin.lto/pkg/_objs/bin/binfile.pic.o.thinlto.bc",
+            "thinlto_output_object_file=" + prefix + "/bin/pkg/bin.lto/pkg/_objs/bin/binfile.pic.o",
+            "thinlto_input_bitcode_file=" + prefix + "/bin/pkg/_objs/bin/binfile.pic.o");
+
+    CppLinkAction indexAction =
+        (CppLinkAction)
+            getPredecessorByInputName(
+                backendAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o.thinlto.bc");
+
+    ConfiguredTargetValue configuredTargetValue =
+        (ConfiguredTargetValue)
+            getSkyframeExecutor()
+                .getEvaluatorForTesting()
+                .getExistingEntryAtLatestVersion(
+                    ConfiguredTargetKey.of(pkg.getLabel(), getConfiguration(pkg)))
+                .getValue();
+    ImmutableList<ActionAnalysisMetadata> linkstampCompileActions =
+        configuredTargetValue
+            .getActions()
+            .stream()
+            .filter(a -> a.getMnemonic().equals("CppLinkstampCompile"))
+            .collect(ImmutableList.toImmutableList());
+    assertThat(linkstampCompileActions).hasSize(1);
+    ActionAnalysisMetadata linkstampCompileAction = linkstampCompileActions.get(0);
+    assertThat(indexAction.getInputs().toList())
+        .doesNotContain(linkstampCompileAction.getOutputs());
+
+    assertThat(indexAction.getArguments())
+        .containsAtLeast(
+            "param_file=" + prefix + "/bin/pkg/bin-lto-final.params",
+            "prefix_replace=" + prefix + "/bin;" + prefix + "/bin/pkg/bin.lto",
+            "thinlto_merged_object_file=" + prefix + "/bin/pkg/bin.lto.merged.o",
+            "object_suffix_replace=.indexing.o;.o");
+    assertThat(indexAction.getArguments())
+        .doesNotContain("thinlto_param_file=" + prefix + "/bin/pkg/bin-lto-final.params");
+
+    assertThat(artifactsToStrings(indexAction.getOutputs()))
+        .containsAtLeast(
+            "bin pkg/bin.lto/pkg/_objs/bin/binfile.pic.o.imports",
+            "bin pkg/bin.lto/pkg/_objs/bin/binfile.pic.o.thinlto.bc",
+            "bin pkg/bin.lto/pkg/_objs/lib/libfile.pic.o.imports",
+            "bin pkg/bin.lto/pkg/_objs/lib/libfile.pic.o.thinlto.bc",
+            "bin pkg/bin-lto-final.params");
+
+    assertThat(indexAction.getMnemonic()).isEqualTo("CppLTOIndexing");
+
+    assertThat(artifactsToStrings(indexAction.getInputs()))
+        .containsAtLeast(
+            "bin pkg/_objs/bin/binfile.pic.indexing.o", "bin pkg/_objs/lib/libfile.pic.indexing.o");
+
+    CppCompileAction bitcodeAction =
+        (CppCompileAction)
+            getPredecessorByInputName(indexAction, "pkg/_objs/bin/binfile.pic.indexing.o");
+    assertThat(bitcodeAction.getMnemonic()).isEqualTo("CppCompile");
+    assertThat(bitcodeAction.getArguments())
+        .contains("lto_indexing_bitcode=" + prefix + "/bin/pkg/_objs/bin/binfile.pic.indexing.o");
+  }
+
+  @Test
+  public void testLinkshared() throws Exception {
+    createBuildFiles("bin.so", "linkshared = 1,");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration();
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin.so");
+
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+
+    Action backendAction =
+        getPredecessorByInputName(linkAction, "pkg/bin.so.lto/pkg/_objs/bin.so/binfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+  }
+
+  @Test
+  public void testNoLinkstatic() throws Exception {
+    createBuildFiles("bin", "linkstatic = 0,");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_DYNAMIC_LINKER,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_INTERFACE_SHARED_LIBRARIES));
+    useConfiguration("--noincompatible_make_thinlto_command_lines_standalone");
+
+    /*
+    We follow the chain from the final product backwards to verify intermediate actions.
+
+    binary <=[Link]=
+    .ifso <=[SolibSymlink]=
+    _S...ifso <=[SolibSymlink]=
+    .ifso <=[Link]=
+    .lto/...o <=[LTOBackend]=
+    {.o.thinlto.bc,.o.imports} <=[LTOIndexing]=
+    .o <= [CppCompile] .cc
+    */
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+
+    List<String> commandLine = linkAction.getLinkCommandLine().getRawLinkArgv();
+    String prefix = getTargetConfiguration().getOutputDirectory(RepositoryName.MAIN)
+        .getExecPathString();
+
+    assertThat(commandLine).contains("-Wl,@" + prefix + "/bin/pkg/bin-lto-final.params");
+
+    // We have no bitcode files: all files have pkg/bin.lto/
+    for (String arg : commandLine) {
+      if (arg.contains("_objs") && !arg.contains("linkstamp.o")) {
+        assertThat(arg).contains("pkg/bin.lto");
+      }
+    }
+
+    assertThat(artifactsToStrings(linkAction.getInputs()))
+        .containsAtLeast(
+            "bin pkg/bin.lto/pkg/_objs/bin/binfile.pic.o",
+            "bin _solib_k8/libpkg_Sliblib.ifso",
+            "bin pkg/bin-2.params",
+            "bin pkg/bin-lto-final.params");
+
+    SolibSymlinkAction solibSymlinkAction =
+        (SolibSymlinkAction) getPredecessorByInputName(linkAction, "_solib_k8/libpkg_Sliblib.ifso");
+    assertThat(solibSymlinkAction.getMnemonic()).isEqualTo("SolibSymlink");
+
+    CppLinkAction libLinkAction =
+        (CppLinkAction) getPredecessorByInputName(solibSymlinkAction, "bin/pkg/liblib.ifso");
+    assertThat(libLinkAction.getMnemonic()).isEqualTo("CppLink");
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(
+                libLinkAction, "pkg/liblib.so.lto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+
+    assertThat(artifactsToStrings(backendAction.getInputs()))
+        .contains("bin pkg/liblib.so.lto/pkg/_objs/lib/libfile.pic.o.thinlto.bc");
+
+    assertThat(backendAction.getArguments())
+        .containsAtLeast(
+            "thinlto_index="
+                + prefix
+                + "/bin/pkg/liblib.so.lto/pkg/_objs/lib/libfile.pic.o.thinlto.bc",
+            "thinlto_output_object_file="
+                + prefix
+                + "/bin/pkg/liblib.so.lto/pkg/_objs/lib/libfile.pic.o",
+            "thinlto_input_bitcode_file=" + prefix + "/bin/pkg/_objs/lib/libfile.pic.o");
+
+    CppLinkAction indexAction =
+        (CppLinkAction)
+            getPredecessorByInputName(
+                backendAction, "pkg/liblib.so.lto/pkg/_objs/lib/libfile.pic.o.thinlto.bc");
+
+    assertThat(indexAction.getArguments())
+        .containsAtLeast(
+            "param_file=" + prefix + "/bin/pkg/liblib.so-lto-final.params",
+            "prefix_replace=" + prefix + "/bin;" + prefix + "/bin/pkg/liblib.so.lto",
+            "object_suffix_replace=.indexing.o;.o");
+
+    assertThat(artifactsToStrings(indexAction.getOutputs()))
+        .containsAtLeast(
+            "bin pkg/liblib.so.lto/pkg/_objs/lib/libfile.pic.o.imports",
+            "bin pkg/liblib.so.lto/pkg/_objs/lib/libfile.pic.o.thinlto.bc",
+            "bin pkg/liblib.so-lto-final.params");
+
+    assertThat(indexAction.getMnemonic()).isEqualTo("CppLTOIndexing");
+
+    assertThat(artifactsToStrings(indexAction.getInputs()))
+        .contains("bin pkg/_objs/lib/libfile.pic.indexing.o");
+
+    CppCompileAction bitcodeAction =
+        (CppCompileAction)
+            getPredecessorByInputName(indexAction, "pkg/_objs/lib/libfile.pic.indexing.o");
+    assertThat(bitcodeAction.getMnemonic()).isEqualTo("CppCompile");
+    assertThat(bitcodeAction.getArguments())
+        .contains("lto_indexing_bitcode=" + prefix + "/bin/pkg/_objs/lib/libfile.pic.indexing.o");
+  }
+
+  /** Helper method to get the root prefix from the given dwpFile. */
+  private static PathFragment dwpRootPrefix(Artifact dwpFile) throws Exception {
+    return dwpFile
+        .getExecPath()
+        .subFragment(
+            0, dwpFile.getExecPath().segmentCount() - dwpFile.getRootRelativePath().segmentCount());
+  }
+
+  /** Helper method that checks that a .dwp has the expected generating action structure. */
+  private void validateDwp(
+      RuleContext ruleContext,
+      Artifact dwpFile,
+      CcToolchainProvider toolchain,
+      List<String> expectedInputs)
+      throws Exception {
+    SpawnAction dwpAction = (SpawnAction) getGeneratingAction(dwpFile);
+    String dwpToolPath = toolchain.getToolPathFragment(Tool.DWP, ruleContext).getPathString();
+    assertThat(dwpAction.getMnemonic()).isEqualTo("CcGenerateDwp");
+    assertThat(dwpToolPath).isEqualTo(dwpAction.getCommandFilename());
+    List<String> commandArgs = dwpAction.getArguments();
+    // The first argument should be the command being executed.
+    assertThat(dwpToolPath).isEqualTo(commandArgs.get(0));
+    // The final two arguments should be "-o dwpOutputFile".
+    assertThat(commandArgs.subList(commandArgs.size() - 2, commandArgs.size()))
+        .containsExactly("-o", dwpFile.getExecPathString())
+        .inOrder();
+    // The remaining arguments should be the set of .dwo inputs (in any order).
+    assertThat(commandArgs.subList(1, commandArgs.size() - 2))
+        .containsExactlyElementsIn(expectedInputs);
+  }
+
+  @Test
+  public void testFission() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.PER_OBJECT_DEBUG_INFO));
+    useConfiguration("--fission=yes", "--copt=-g0");
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(artifactsToStrings(backendAction.getOutputs()))
+        .containsExactly(
+            "bin pkg/bin.lto/pkg/_objs/bin/binfile.pic.o",
+            "bin pkg/bin.lto/pkg/_objs/bin/binfile.pic.dwo");
+
+    assertThat(backendAction.getArguments()).containsAtLeast("-g0", "per_object_debug_info_option");
+
+    backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(artifactsToStrings(backendAction.getOutputs()))
+        .containsExactly(
+            "bin pkg/bin.lto/pkg/_objs/lib/libfile.pic.o",
+            "bin pkg/bin.lto/pkg/_objs/lib/libfile.pic.dwo");
+
+    assertThat(backendAction.getArguments()).contains("per_object_debug_info_option");
+
+    // Now check the dwp action.
+    Artifact dwpFile = getFileConfiguredTarget(pkg.getLabel() + ".dwp").getArtifact();
+    PathFragment rootPrefix = dwpRootPrefix(dwpFile);
+    RuleContext ruleContext = getRuleContext(pkg);
+    CcToolchainProvider toolchain =
+        CppHelper.getToolchainUsingDefaultCcToolchainAttribute(ruleContext);
+    validateDwp(
+        ruleContext,
+        dwpFile,
+        toolchain,
+        ImmutableList.of(
+            rootPrefix + "/pkg/bin.lto/pkg/_objs/lib/libfile.pic.dwo",
+            rootPrefix + "/pkg/bin.lto/pkg/_objs/bin/binfile.pic.dwo"));
+  }
+
+  @Test
+  public void testNoLinkstaticFission() throws Exception {
+    createBuildFiles("bin", "linkstatic = 0,");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.SUPPORTS_INTERFACE_SHARED_LIBRARIES,
+                    CppRuleClasses.SUPPORTS_DYNAMIC_LINKER,
+                    CppRuleClasses.PER_OBJECT_DEBUG_INFO));
+    useConfiguration("--fission=yes");
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+
+    SolibSymlinkAction solibSymlinkAction =
+        (SolibSymlinkAction) getPredecessorByInputName(linkAction, "_solib_k8/libpkg_Sliblib.ifso");
+    assertThat(solibSymlinkAction.getMnemonic()).isEqualTo("SolibSymlink");
+
+    CppLinkAction libLinkAction =
+        (CppLinkAction) getPredecessorByInputName(solibSymlinkAction, "bin/pkg/liblib.ifso");
+    assertThat(libLinkAction.getMnemonic()).isEqualTo("CppLink");
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(
+                libLinkAction, "pkg/liblib.so.lto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(artifactsToStrings(backendAction.getOutputs()))
+        .containsExactly(
+            "bin pkg/liblib.so.lto/pkg/_objs/lib/libfile.pic.o",
+            "bin pkg/liblib.so.lto/pkg/_objs/lib/libfile.pic.dwo");
+
+    assertThat(backendAction.getArguments()).contains("per_object_debug_info_option");
+
+    // Check the dwp action.
+    Artifact dwpFile = getFileConfiguredTarget(pkg.getLabel() + ".dwp").getArtifact();
+    PathFragment rootPrefix = dwpRootPrefix(dwpFile);
+    RuleContext ruleContext = getRuleContext(pkg);
+    CcToolchainProvider toolchain =
+        CppHelper.getToolchainUsingDefaultCcToolchainAttribute(ruleContext);
+    validateDwp(
+        ruleContext,
+        dwpFile,
+        toolchain,
+        ImmutableList.of(rootPrefix + "/pkg/bin.lto/pkg/_objs/bin/binfile.pic.dwo"));
+  }
+
+  @Test
+  public void testLinkstaticCcTestFission() throws Exception {
+    createTestFiles("linkstatic = 1,", "");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.THIN_LTO_LINKSTATIC_TESTS_USE_SHARED_NONLTO_BACKENDS,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.PER_OBJECT_DEBUG_INFO));
+    useConfiguration(
+        "--fission=yes", "--features=thin_lto_linkstatic_tests_use_shared_nonlto_backends");
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin_test");
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+
+    // All backends should be shared non-LTO in this case
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(
+                linkAction, "shared.nonlto/pkg/_objs/bin_test/bin_test.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(artifactsToStrings(backendAction.getOutputs()))
+        .containsExactly(
+            "bin shared.nonlto/pkg/_objs/bin_test/bin_test.pic.o",
+            "bin shared.nonlto/pkg/_objs/bin_test/bin_test.pic.dwo");
+
+    assertThat(backendAction.getArguments()).contains("per_object_debug_info_option");
+
+    backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "shared.nonlto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(backendAction.getArguments()).contains("-fPIC");
+    assertThat(artifactsToStrings(backendAction.getOutputs()))
+        .containsExactly(
+            "bin shared.nonlto/pkg/_objs/lib/libfile.pic.o",
+            "bin shared.nonlto/pkg/_objs/lib/libfile.pic.dwo");
+
+    assertThat(backendAction.getArguments()).contains("per_object_debug_info_option");
+
+    // Now check the dwp action.
+    Artifact dwpFile = getFileConfiguredTarget(pkg.getLabel() + ".dwp").getArtifact();
+    PathFragment rootPrefix = dwpRootPrefix(dwpFile);
+    RuleContext ruleContext = getRuleContext(pkg);
+    CcToolchainProvider toolchain =
+        CppHelper.getToolchainUsingDefaultCcToolchainAttribute(ruleContext);
+    validateDwp(
+        ruleContext,
+        dwpFile,
+        toolchain,
+        ImmutableList.of(
+            rootPrefix + "/shared.nonlto/pkg/_objs/lib/libfile.pic.dwo",
+            rootPrefix + "/shared.nonlto/pkg/_objs/bin_test/bin_test.pic.dwo"));
+  }
+
+  @Test
+  public void testLinkstaticCcTest() throws Exception {
+    createTestFiles("linkstatic = 1,", "");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.THIN_LTO_LINKSTATIC_TESTS_USE_SHARED_NONLTO_BACKENDS,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.PER_OBJECT_DEBUG_INFO));
+    useConfiguration("--features=thin_lto_linkstatic_tests_use_shared_nonlto_backends");
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin_test");
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+
+    ConfiguredTarget pkg2 = getConfiguredTarget("//pkg:bin_test2");
+    Artifact pkgArtifact2 = getFilesToBuild(pkg2).getSingleton();
+    CppLinkAction linkAction2 = (CppLinkAction) getGeneratingAction(pkgArtifact2);
+
+    // All backends should be shared non-LTO in this case
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(
+                linkAction, "shared.nonlto/pkg/_objs/bin_test/bin_test.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+
+    backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "shared.nonlto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(backendAction.getArguments()).contains("-fPIC");
+
+    LtoBackendAction backendAction2 =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction2, "shared.nonlto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction2.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+
+    assertThat(backendAction).isEqualTo(backendAction2);
+  }
+
+  @Test
+  public void testTestOnlyTarget() throws Exception {
+    createBuildFiles("bin", "testonly = 1,");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.THIN_LTO_LINKSTATIC_TESTS_USE_SHARED_NONLTO_BACKENDS,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES));
+    useConfiguration("--features=thin_lto_linkstatic_tests_use_shared_nonlto_backends");
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "shared.nonlto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+  }
+
+  @Test
+  public void testUseSharedAllLinkstatic() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.THIN_LTO_ALL_LINKSTATIC_USE_SHARED_NONLTO_BACKENDS,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES));
+    useConfiguration("--features=thin_lto_all_linkstatic_use_shared_nonlto_backends");
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "shared.nonlto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+  }
+
+  private Action getPredecessorByInputName(Action action, String str) {
+    for (Artifact a : action.getInputs().toList()) {
+      if (a.getExecPathString().contains(str)) {
+        return getGeneratingAction(a);
+      }
+    }
+    return null;
+  }
+
+  @Test
+  public void testAssemblerSource() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['thin_lto'])",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          deps = [ ':lib' ], ",
+        "          malloc = '//base:system_malloc')",
+        "cc_library(name = 'lib',",
+        "        srcs = ['tracing.cc', 'tracing_x86-64.S'],",
+        "       )");
+
+    scratch.file("pkg/binfile.cc", "int main() { return pkg(); }");
+    scratch.file("pkg/tracing.cc", "// hello");
+    scratch.file("pkg/tracing_x86-64.S", "NOP");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration();
+
+    ConfiguredTarget bin = getConfiguredTarget("//pkg:bin");
+
+    Artifact binArtifact = getFilesToBuild(bin).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    Action dataGen = getPredecessorByInputName(linkAction, "tracing_x86-64");
+    assertWithMessage(linkAction.getInputs().toString()).that(dataGen).isNotNull();
+    assertThat(dataGen.getMnemonic()).isEqualTo("CppCompile");
+  }
+
+  // Make sure we don't choke on a cc_library without sources and therefore, without bitcode files.
+  @Test
+  public void testNoSourceFiles() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['thin_lto'])",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          deps = [ ':lib' ], ",
+        "          malloc = '//base:system_malloc')",
+        "cc_library(name = 'lib',",
+        "        srcs = ['static.a'],",
+        "       )");
+
+    scratch.file("pkg/binfile.cc", "int main() { return 1; }");
+    scratch.file("pkg/static.a", "xyz");
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration();
+
+    getConfiguredTarget("//pkg:bin");
+  }
+
+  @Test
+  public void testFdoInstrument() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['thin_lto'])",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.FDO_INSTRUMENT));
+    useConfiguration("--fdo_instrument=profiles");
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+    // If the LtoBackendAction incorrectly tries to add the fdo_instrument
+    // feature, we will fail with an "unknown variable 'fdo_instrument_path'"
+    // error. But let's also explicitly confirm that the fdo_instrument
+    // option didn't end up here.
+    assertThat(backendAction.getArguments()).doesNotContain("fdo_instrument_option");
+  }
+
+  @Test
+  public void testLtoIndexOpt() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration(
+        "--ltoindexopt=anltoindexopt", "--noincompatible_make_thinlto_command_lines_standalone");
+
+    /*
+    We follow the chain from the final product backwards.
+
+    binary <=[Link]=
+    .lto/...o <=[LTOBackend]=
+    {.o.thinlto.bc,.o.imports} <=[LTOIndexing]=
+    .o <= [CppCompile] .cc
+    */
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+
+    CppLinkAction indexAction =
+        (CppLinkAction)
+            getPredecessorByInputName(
+                backendAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o.thinlto.bc");
+
+    assertThat(indexAction.getArguments()).contains("anltoindexopt");
+  }
+
+  @Test
+  public void testLtoStandaloneCommandLines() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration(
+        "--ltoindexopt=anltoindexopt",
+        "--incompatible_make_thinlto_command_lines_standalone",
+        "--features=thin_lto");
+
+    /*
+    We follow the chain from the final product backwards.
+
+    binary <=[Link]=
+    .lto/...o <=[LTOBackend]=
+    {.o.thinlto.bc,.o.imports} <=[LTOIndexing]=
+    .o <= [CppCompile] .cc
+    */
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+
+    CppLinkAction indexAction =
+        (CppLinkAction)
+            getPredecessorByInputName(
+                backendAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o.thinlto.bc");
+
+    assertThat(indexAction.getArguments())
+        .contains("--i_come_from_standalone_lto_index=anltoindexopt");
+  }
+
+  @Test
+  public void testCopt() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.SUPPORTS_PIC));
+    useConfiguration("--copt=acopt");
+
+    /*
+    We follow the chain from the final product backwards.
+
+    binary <=[Link]=
+    .lto/...o <=[LTOBackend]=
+    */
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(backendAction.getArguments()).contains("acopt");
+  }
+
+  @Test
+  public void testPerFileCopt() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration(
+        "--per_file_copt=binfile\\.cc@copt1",
+        "--per_file_copt=libfile\\.cc@copt2",
+        "--per_file_copt=.*\\.cc,-binfile\\.cc@copt2");
+
+    /*
+    We follow the chain from the final product backwards.
+
+    binary <=[Link]=
+    .lto/...o <=[LTOBackend]=
+    */
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getArguments()).contains("copt1");
+    assertThat(backendAction.getArguments()).doesNotContain("copt2");
+
+    backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction.getArguments()).doesNotContain("copt1");
+    assertThat(backendAction.getArguments()).contains("copt2");
+  }
+
+  @Test
+  public void testCoptNoCoptAttributes() throws Exception {
+    createBuildFiles("bin", "copts = ['acopt', 'nocopt1'], nocopts = 'nocopt1|nocopt2',");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration("--copt=nocopt2", "--noincompatible_disable_nocopts");
+
+    /*
+    We follow the chain from the final product backwards.
+
+    binary <=[Link]=
+    .lto/...o <=[LTOBackend]=
+    */
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(backendAction.getArguments()).contains("acopt");
+    // TODO(b/122303926): Remove when nocopts are removed, or uncomment and fix if not removing.
+    // assertThat(backendAction.getArguments()).doesNotContain("nocopt1");
+    // assertThat(backendAction.getArguments()).doesNotContain("nocopt2");
+  }
+
+  @Test
+  public void testLtoBackendOpt() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    MockCcSupport.USER_COMPILE_FLAGS));
+    useConfiguration("--ltobackendopt=anltobackendopt");
+
+    /*
+    We follow the chain from the final product backwards.
+
+    binary <=[Link]=
+    .lto/...o <=[LTOBackend]=
+    */
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(backendAction.getArguments())
+        .containsAtLeast("--default-compile-flag", "anltobackendopt");
+  }
+
+  @Test
+  public void testPerFileLtoBackendOpt() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration(
+        "--per_file_ltobackendopt=binfile\\.pic\\.o@ltobackendopt1",
+        "--per_file_ltobackendopt=.*\\.o,-binfile\\.pic\\.o@ltobackendopt2");
+
+    /*
+    We follow the chain from the final product backwards.
+
+    binary <=[Link]=
+    .lto/...o <=[LTOBackend]=
+    */
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+    assertThat(backendAction.getArguments()).contains("ltobackendopt1");
+    assertThat(backendAction.getArguments()).doesNotContain("ltobackendopt2");
+
+    backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction.getArguments()).doesNotContain("ltobackendopt1");
+    assertThat(backendAction.getArguments()).contains("ltobackendopt2");
+  }
+
+  @Test
+  public void testNoUseLtoIndexingBitcodeFile() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.NO_USE_LTO_INDEXING_BITCODE_FILE,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration("--features=no_use_lto_indexing_bitcode_file");
+
+    /*
+    We follow the chain from the final product backwards.
+
+    binary <=[Link]=
+    .lto/...o <=[LTOBackend]=
+    {.o.thinlto.bc,.o.imports} <=[LTOIndexing]=
+    .o <= [CppCompile] .cc
+    */
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o");
+
+    CppLinkAction indexAction =
+        (CppLinkAction)
+            getPredecessorByInputName(
+                backendAction, "pkg/bin.lto/pkg/_objs/bin/binfile.pic.o.thinlto.bc");
+
+    assertThat(indexAction.getArguments()).doesNotContain("object_suffix_replace");
+
+    assertThat(artifactsToStrings(indexAction.getInputs()))
+        .containsAtLeast("bin pkg/_objs/bin/binfile.pic.o", "bin pkg/_objs/lib/libfile.pic.o");
+
+    CppCompileAction bitcodeAction =
+        (CppCompileAction) getPredecessorByInputName(indexAction, "pkg/_objs/bin/binfile.pic.o");
+    assertThat(bitcodeAction.getArguments()).doesNotContain("lto_indexing_bitcode=");
+  }
+
+  @Test
+  public void testAutoFdo() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['thin_lto'])",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.afdo", "");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.AUTOFDO));
+    useConfiguration("--fdo_optimize=pkg/profile.afdo", "--compilation_mode=opt");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+
+    // Checks that -fauto-profile is added to the LtoBackendAction.
+    assertThat(Joiner.on(" ").join(backendAction.getArguments())).containsMatch(
+        "-fauto-profile=[^ ]*/profile.afdo");
+    assertThat(ActionsTestUtil.baseArtifactNames(backendAction.getInputs())).contains(
+        "profile.afdo");
+  }
+
+  private void setupAutoFdoThinLtoCrosstool() throws Exception {
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.AUTOFDO,
+                    CppRuleClasses.ENABLE_AFDO_THINLTO,
+                    CppRuleClasses.AUTOFDO_IMPLICIT_THINLTO));
+  }
+
+  /**
+   * Tests that ThinLTO is not enabled for AFDO with LLVM without
+   * --features=autofdo_implicit_thinlto.
+   */
+  @Test
+  public void testAutoFdoNoImplicitThinLto() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.afdo", "");
+
+    setupAutoFdoThinLtoCrosstool();
+    useConfiguration("--fdo_optimize=pkg/profile.afdo", "--compilation_mode=opt");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  /** Tests that --features=autofdo_implicit_thinlto enables ThinLTO for AFDO with LLVM. */
+  @Test
+  public void testAutoFdoImplicitThinLto() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.afdo", "");
+
+    setupAutoFdoThinLtoCrosstool();
+    useConfiguration(
+        "--fdo_optimize=pkg/profile.afdo",
+        "--compilation_mode=opt",
+        "--features=autofdo_implicit_thinlto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // For ThinLTO compilation we should have a non-null backend action
+    assertThat(backendAction).isNotNull();
+  }
+
+  /**
+   * Tests that --features=-thin_lto overrides --features=autofdo_implicit_thinlto and prevents
+   * enabling ThinLTO for AFDO with LLVM.
+   */
+  @Test
+  public void testAutoFdoImplicitThinLtoDisabledOption() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.afdo", "");
+
+    setupAutoFdoThinLtoCrosstool();
+    useConfiguration(
+        "--fdo_optimize=pkg/profile.afdo",
+        "--compilation_mode=opt",
+        "--features=autofdo_implicit_thinlto",
+        "--features=-thin_lto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  /**
+   * Tests that features=[-thin_lto] in the build rule overrides --features=autofdo_implicit_thinlto
+   * and prevents enabling ThinLTO for AFDO with LLVM.
+   */
+  @Test
+  public void testAutoFdoImplicitThinLtoDisabledRule() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          features = ['-thin_lto'],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.afdo", "");
+
+    setupAutoFdoThinLtoCrosstool();
+    useConfiguration(
+        "--fdo_optimize=pkg/profile.afdo",
+        "--compilation_mode=opt",
+        "--features=autofdo_implicit_thinlto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  /**
+   * Tests that features=[-thin_lto] in the package overrides --features=autofdo_implicit_thinlto
+   * and prevents enabling ThinLTO for AFDO with LLVM.
+   */
+  @Test
+  public void testAutoFdoImplicitThinLtoDisabledPackage() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['-thin_lto'])",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.afdo", "");
+
+    setupAutoFdoThinLtoCrosstool();
+    useConfiguration(
+        "--fdo_optimize=pkg/profile.afdo",
+        "--compilation_mode=opt",
+        "--features=autofdo_implicit_thinlto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  private void setupFdoThinLtoCrosstool() throws Exception {
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.FDO_OPTIMIZE,
+                    CppRuleClasses.ENABLE_FDO_THINLTO,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    MockCcSupport.FDO_IMPLICIT_THINLTO));
+  }
+
+  /**
+   * Tests that ThinLTO is not enabled for FDO with LLVM without --features=fdo_implicit_thinlto.
+   */
+  @Test
+  public void testFdoNoImplicitThinLto() throws Exception {
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(CppRuleClasses.THIN_LTO, CppRuleClasses.SUPPORTS_START_END_LIB));
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.zip", "");
+
+    setupFdoThinLtoCrosstool();
+    useConfiguration("--fdo_optimize=pkg/profile.zip", "--compilation_mode=opt");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  /** Tests that --features=fdo_implicit_thinlto enables ThinLTO for FDO with LLVM. */
+  @Test
+  public void testFdoImplicitThinLto() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.zip", "");
+
+    setupFdoThinLtoCrosstool();
+    useConfiguration(
+        "--fdo_optimize=pkg/profile.zip",
+        "--compilation_mode=opt",
+        "--features=fdo_implicit_thinlto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // For ThinLTO compilation we should have a non-null backend action
+    assertThat(backendAction).isNotNull();
+  }
+
+  /**
+   * Tests that --features=-thin_lto overrides --features=fdo_implicit_thinlto and prevents enabling
+   * ThinLTO for FDO with LLVM.
+   */
+  @Test
+  public void testFdoImplicitThinLtoDisabledOption() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.zip", "");
+
+    setupFdoThinLtoCrosstool();
+    useConfiguration(
+        "--fdo_optimize=pkg/profile.zip",
+        "--compilation_mode=opt",
+        "--features=fdo_implicit_thinlto",
+        "--features=-thin_lto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  /**
+   * Tests that features=[-thin_lto] in the build rule overrides --features=fdo_implicit_thinlto and
+   * prevents enabling ThinLTO for FDO with LLVM.
+   */
+  @Test
+  public void testFdoImplicitThinLtoDisabledRule() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          features = ['-thin_lto'],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.zip", "");
+
+    setupFdoThinLtoCrosstool();
+    useConfiguration(
+        "--fdo_optimize=pkg/profile.zip",
+        "--compilation_mode=opt",
+        "--features=fdo_implicit_thinlto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  /**
+   * Tests that features=[-thin_lto] in the package overrides --features=fdo_implicit_thinlto and
+   * prevents enabling ThinLTO for FDO with LLVM.
+   */
+  @Test
+  public void testFdoImplicitThinLtoDisabledPackage() throws Exception {
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(CppRuleClasses.THIN_LTO, CppRuleClasses.SUPPORTS_START_END_LIB));
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['-thin_lto'])",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+    scratch.file("pkg/profile.zip", "");
+
+    setupFdoThinLtoCrosstool();
+    useConfiguration(
+        "--fdo_optimize=pkg/profile.zip",
+        "--compilation_mode=opt",
+        "--features=fdo_implicit_thinlto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  private void setupXBinaryFdoThinLtoCrosstool() throws Exception {
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.XBINARYFDO,
+                    CppRuleClasses.ENABLE_XFDO_THINLTO,
+                    MockCcSupport.XFDO_IMPLICIT_THINLTO));
+  }
+
+  /**
+   * Tests that ThinLTO is not enabled for XFDO with LLVM without
+   * --features=xbinaryfdo_implicit_thinlto.
+   */
+  @Test
+  public void testXBinaryFdoNoImplicitThinLto() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ])",
+        "fdo_profile(name='out.xfdo', profile='profiles.xfdo')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+
+    setupXBinaryFdoThinLtoCrosstool();
+    useConfiguration("--xbinary_fdo=//pkg:out.xfdo", "--compilation_mode=opt");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  /** Tests that --features=xbinaryfdo_implicit_thinlto enables ThinLTO for XFDO with LLVM. */
+  @Test
+  public void testXBinaryFdoImplicitThinLto() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ])",
+        "fdo_profile(name='out.xfdo', profile='profiles.xfdo')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+
+    setupXBinaryFdoThinLtoCrosstool();
+    useConfiguration(
+        "--xbinary_fdo=//pkg:out.xfdo",
+        "--compilation_mode=opt",
+        "--features=xbinaryfdo_implicit_thinlto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // For ThinLTO compilation we should have a non-null backend action
+    assertThat(backendAction).isNotNull();
+  }
+
+  /**
+   * Tests that --features=-thin_lto overrides --features=xbinaryfdo_implicit_thinlto and prevents
+   * enabling ThinLTO for XFDO with LLVM.
+   */
+  @Test
+  public void testXBinaryFdoImplicitThinLtoDisabledOption() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ])",
+        "fdo_profile(name='out.xfdo', profile='profiles.xfdo')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+
+    setupXBinaryFdoThinLtoCrosstool();
+    useConfiguration(
+        "--xbinary_fdo=//pkg:out.xfdo",
+        "--compilation_mode=opt",
+        "--features=xbinaryfdo_implicit_thinlto",
+        "--features=-thin_lto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  /**
+   * Tests that features=[-thin_lto] in the build rule overrides
+   * --features=xbinaryfdo_implicit_thinlto and prevents enabling ThinLTO for XFDO with LLVM.
+   */
+  @Test
+  public void testXBinaryFdoImplicitThinLtoDisabledRule() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          features = ['-thin_lto'])",
+        "fdo_profile(name='out.xfdo', profile='profiles.xfdo')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+
+    setupXBinaryFdoThinLtoCrosstool();
+    useConfiguration(
+        "--xbinary_fdo=//pkg:out.xfdo",
+        "--compilation_mode=opt",
+        "--features=xbinaryfdo_implicit_thinlto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  /**
+   * Tests that features=[-thin_lto] in the package overrides --features=fdo_implicit_thinlto and
+   * prevents enabling ThinLTO for XFDO with LLVM.
+   */
+  @Test
+  public void testXBinaryFdoImplicitThinLtoDisabledPackage() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['-thin_lto'])",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ])",
+        "fdo_profile(name='out.xfdo', profile='profiles.xfdo')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+
+    setupXBinaryFdoThinLtoCrosstool();
+    useConfiguration(
+        "--xbinary_fdo=//pkg:out.xfdo",
+        "--compilation_mode=opt",
+        "--features=xbinaryfdo_implicit_thinlto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  @Test
+  public void testXBinaryFdo() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['thin_lto'])",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')",
+        "fdo_profile(name='out.xfdo', profile='profiles.xfdo')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.XBINARYFDO));
+    useConfiguration("--xbinary_fdo=//pkg:out.xfdo", "--compilation_mode=opt");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+
+    // Checks that -fauto-profile is added to the LtoBackendAction.
+    assertThat(Joiner.on(" ").join(backendAction.getArguments()))
+        .containsMatch("-fauto-profile=[^ ]*/profiles.xfdo");
+    assertThat(ActionsTestUtil.baseArtifactNames(backendAction.getInputs()))
+        .contains("profiles.xfdo");
+  }
+
+  /**
+   * Tests that ThinLTO is not enabled for XBINARYFDO with --features=autofdo_implicit_thinlto and
+   * --features=fdo_implicit_thinlto.
+   */
+  @Test
+  public void testXBinaryFdoNoAutoFdoOrFdoImplicitThinLto() throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ],",
+        "          malloc = '//base:system_malloc')",
+        "fdo_profile(name='out.xfdo', profile='profiles.xfdo')");
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.ENABLE_FDO_THINLTO,
+                    MockCcSupport.FDO_IMPLICIT_THINLTO,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.ENABLE_AFDO_THINLTO,
+                    MockCcSupport.AUTOFDO_IMPLICIT_THINLTO,
+                    CppRuleClasses.XBINARYFDO));
+    useConfiguration(
+        "--xbinary_fdo=//pkg:out.xfdo",
+        "--compilation_mode=opt",
+        "--features=autofdo_implicit_thinlto",
+        "--features=fdo_implicit_thinlto");
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/pkg/binfile.o");
+    // We should not have a ThinLTO backend action
+    assertThat(backendAction).isNull();
+  }
+
+  @Test
+  public void testPICBackendOrder() throws Exception {
+    createBuildFiles("bin");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB));
+    useConfiguration("--copt=-fno-PIE");
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin");
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+        getGeneratingAction(artifactByPath(getFilesToBuild(pkg), "bin", "binfile.pic.o"));
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(backendAction.getArguments()).containsAtLeast("-fno-PIE", "-fPIC").inOrder();
+  }
+
+  private void testLLVMCachePrefetchBackendOption(String extraOption, boolean asLabel)
+      throws Exception {
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['thin_lto'])",
+        "",
+        "cc_binary(name = 'bin',",
+        "          srcs = ['binfile.cc', ])");
+     if (asLabel) {
+      scratch.file(
+          "fdo/BUILD",
+          "fdo_prefetch_hints(name='test_profile', profile=':prefetch.afdo')");
+    } else {
+      scratch.file(
+          "fdo/BUILD",
+          "fdo_prefetch_hints(name='test_profile', absolute_path_profile='/tmp/prefetch.afdo')");
+    }
+
+    scratch.file("pkg/binfile.cc", "int main() {}");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.AUTOFDO));
+    useConfiguration(
+        "--fdo_prefetch_hints=//fdo:test_profile",
+        "--compilation_mode=opt",
+        extraOption);
+
+    Artifact binArtifact = getFilesToBuild(getConfiguredTarget("//pkg:bin")).getSingleton();
+
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(binArtifact);
+    assertThat(linkAction.getOutputs()).containsExactly(binArtifact);
+
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "pkg/bin.lto/pkg/_objs/bin/binfile.o");
+
+    String expectedCompilerFlag =
+        "-Xclang-only=-prefetch-hints-file="
+            + (asLabel ? ".*/prefetch.afdo" : "(blaze|bazel)-out/.*/fdo/.*/prefetch.afdo");
+    assertThat(Joiner.on(" ").join(backendAction.getArguments()))
+        .containsMatch(
+            "-Xclang-only=-mllvm " + expectedCompilerFlag);
+
+    assertThat(ActionsTestUtil.baseArtifactNames(backendAction.getInputs()))
+        .contains("prefetch.afdo");
+  }
+
+  @Test
+  public void testFdoCachePrefetchLLVMOptionsToBackendFromPath() throws Exception {
+    testLLVMCachePrefetchBackendOption("", false);
+  }
+
+  @Test
+  public void testFdoCachePrefetchAndFdoLLVMOptionsToBackendFromPath() throws Exception {
+    testLLVMCachePrefetchBackendOption("--fdo_optimize=./profile.zip", false);
+  }
+
+  @Test
+  public void testFdoCachePrefetchLLVMOptionsToBackendFromLabel() throws Exception {
+    testLLVMCachePrefetchBackendOption("", true);
+  }
+
+  @Test
+  public void testFdoCachePrefetchAndFdoLLVMOptionsToBackendFromLabel() throws Exception {
+    testLLVMCachePrefetchBackendOption("--fdo_optimize=./profile.zip", true);
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelperTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelperTest.java
new file mode 100644
index 0000000..bda155d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelperTest.java
@@ -0,0 +1,142 @@
+// Copyright 2020 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.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.testing.NullPointerTester;
+import com.google.common.testing.NullPointerTester.Visibility;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.rules.cpp.CcCompilationHelper.SourceCategory;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link CcCompilationHelper}. */
+@RunWith(JUnit4.class)
+public final class CcCompilationHelperTest extends BuildViewTestCase {
+
+  @Test
+  public void testConstructorThrowsNPE() throws Exception {
+    ConfiguredTarget target =
+        scratchConfiguredTarget("a", "b", "cc_library(name = 'b', srcs = [],)");
+    RuleContext ruleContext = getRuleContext(target);
+    CcToolchainProvider ccToolchain =
+        CppHelper.getToolchainUsingDefaultCcToolchainAttribute(ruleContext);
+    FdoContext fdoContext = ccToolchain.getFdoContext();
+    Artifact grepIncludes = getBinArtifact("grep_includes", target);
+    NullPointerTester tester =
+        new NullPointerTester()
+            .setDefault(RuleContext.class, ruleContext)
+            .setDefault(CcCommon.class, new CcCommon(ruleContext))
+            .setDefault(CppSemantics.class, MockCppSemantics.INSTANCE)
+            .setDefault(CcToolchainProvider.class, ccToolchain)
+            .setDefault(BuildConfiguration.class, ruleContext.getConfiguration())
+            .setDefault(FdoContext.class, fdoContext)
+            .setDefault(Label.class, ruleContext.getLabel())
+            .setDefault(Artifact.class, grepIncludes)
+            .setDefault(CcCompilationOutputs.class, CcCompilationOutputs.builder().build());
+    tester.testConstructors(CcCompilationHelper.class, Visibility.PACKAGE);
+    tester.testAllPublicInstanceMethods(
+        new CcCompilationHelper(
+            ruleContext,
+            ruleContext,
+            target.getLabel(),
+            /* grepIncludes= */ null,
+            MockCppSemantics.INSTANCE,
+            FeatureConfiguration.EMPTY,
+            ccToolchain,
+            fdoContext,
+            /* executionInfo= */ ImmutableMap.of()));
+  }
+
+  @Test
+  public void testCanIgnoreObjcSource() throws Exception {
+    ConfiguredTarget target =
+        scratchConfiguredTarget("a", "b", "cc_library(name = 'b', srcs = ['cpp.cc'])");
+    Artifact objcSrc = getSourceArtifact("objc.m");
+    RuleContext ruleContext = getRuleContext(target);
+    CcToolchainProvider ccToolchain =
+        CppHelper.getToolchainUsingDefaultCcToolchainAttribute(getRuleContext(target));
+    FdoContext fdoContext = ccToolchain.getFdoContext();
+    CcCompilationHelper helper =
+        new CcCompilationHelper(
+                ruleContext,
+                ruleContext,
+                ruleContext.getLabel(),
+                /* grepIncludes= */ null,
+                MockCppSemantics.INSTANCE,
+                FeatureConfiguration.EMPTY,
+                ccToolchain,
+                fdoContext,
+                /* executionInfo= */ ImmutableMap.of())
+            .addSources(objcSrc);
+
+    ImmutableList.Builder<Artifact> helperArtifacts = ImmutableList.builder();
+    for (CppSource source : helper.getCompilationUnitSources()) {
+      helperArtifacts.add(source.getSource());
+    }
+
+    assertThat(Artifact.toRootRelativePaths(helperArtifacts.build())).isEmpty();
+  }
+
+  @Test
+  public void testCanConsumeObjcSource() throws Exception {
+    ConfiguredTarget target =
+        scratchConfiguredTarget("a", "b", "cc_library(name = 'b', srcs = ['cpp.cc'])");
+    Artifact objcSrc = getSourceArtifact("objc.m");
+    RuleContext ruleContext = getRuleContext(target);
+    CcToolchainProvider ccToolchain =
+        CppHelper.getToolchainUsingDefaultCcToolchainAttribute(getRuleContext(target));
+    FdoContext fdoContext = ccToolchain.getFdoContext();
+    CcCompilationHelper helper =
+        new CcCompilationHelper(
+                ruleContext,
+                ruleContext,
+                ruleContext.getLabel(),
+                /* grepIncludes= */ null,
+                MockCppSemantics.INSTANCE,
+                FeatureConfiguration.EMPTY,
+                SourceCategory.CC_AND_OBJC,
+                ccToolchain,
+                fdoContext,
+                ruleContext.getConfiguration(),
+                ImmutableMap.of())
+            .addSources(objcSrc);
+
+    ImmutableList.Builder<Artifact> helperArtifacts = ImmutableList.builder();
+    for (CppSource source : helper.getCompilationUnitSources()) {
+      helperArtifacts.add(source.getSource());
+    }
+
+    assertThat(Artifact.toRootRelativePaths(helperArtifacts.build()))
+        .contains(objcSrc.getRootRelativePath().toString());
+  }
+
+  @Test
+  public void testSetAllowCodeCoverage() throws Exception {
+    useConfiguration("--collect_code_coverage", "--instrumentation_filter=.");
+    ConfiguredTarget target =
+        scratchConfiguredTarget("a", "b", "cc_library(name = 'b', srcs = ['cpp.cc'])");
+    assertThat(CcCompilationHelper.isCodeCoverageEnabled(getRuleContext(target))).isTrue();
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationSkylarkTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationSkylarkTest.java
new file mode 100644
index 0000000..dbd2e63
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationSkylarkTest.java
@@ -0,0 +1,79 @@
+// Copyright 2020 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.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.syntax.Sequence;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for C++ fragments in Starlark. */
+@RunWith(JUnit4.class)
+public final class CppConfigurationSkylarkTest extends BuildViewTestCase {
+
+  @Test
+  public void testCopts() throws Exception {
+    writeRuleReturning("ctx.fragments.cpp.copts");
+    useConfiguration("--copt=-wololoo");
+
+    @SuppressWarnings("unchecked")
+    Sequence<String> result = (Sequence<String>) getConfiguredTarget("//foo:bar").get("result");
+    assertThat(result).containsExactly("-wololoo");
+  }
+
+  @Test
+  public void testCxxopts() throws Exception {
+    writeRuleReturning("ctx.fragments.cpp.cxxopts");
+    useConfiguration("--cxxopt=-wololoo");
+
+    @SuppressWarnings("unchecked")
+    Sequence<String> result = (Sequence<String>) getConfiguredTarget("//foo:bar").get("result");
+    assertThat(result).containsExactly("-wololoo");
+  }
+
+  @Test
+  public void testConlyopts() throws Exception {
+    writeRuleReturning("ctx.fragments.cpp.conlyopts");
+    useConfiguration("--conlyopt=-wololoo");
+
+    @SuppressWarnings("unchecked")
+    Sequence<String> result = (Sequence<String>) getConfiguredTarget("//foo:bar").get("result");
+    assertThat(result).containsExactly("-wololoo");
+  }
+
+  @Test
+  public void testLinkopts() throws Exception {
+    writeRuleReturning("ctx.fragments.cpp.linkopts");
+    useConfiguration("--linkopt=-wololoo");
+
+    @SuppressWarnings("unchecked")
+    Sequence<String> result = (Sequence<String>) getConfiguredTarget("//foo:bar").get("result");
+    assertThat(result).containsExactly("-wololoo");
+  }
+
+  private void writeRuleReturning(String s) throws IOException {
+    scratch.file(
+        "foo/lib.bzl",
+        "def _impl(ctx):",
+        "  return struct(",
+        "    result = " + s,
+        "  )",
+        "foo = rule(implementation=_impl, fragments = ['cpp'])");
+    scratch.file("foo/BUILD", "load(':lib.bzl', 'foo')", "foo(name='bar')");
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppSysrootTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppSysrootTest.java
new file mode 100644
index 0000000..300c1ff
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppSysrootTest.java
@@ -0,0 +1,211 @@
+// Copyright 2020 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.truth.Truth.assertThat;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.ConfigurationMakeVariableContext;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.platform.ToolchainInfo;
+import com.google.devtools.build.lib.analysis.util.AnalysisMock;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.util.Crosstool.CcToolchainConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for calculating the sysroot that require building configured targets. */
+@RunWith(JUnit4.class)
+public final class CppSysrootTest extends BuildViewTestCase {
+
+  @Before
+  public void writeDummyLibrary() throws Exception {
+    scratch.file("dummy/BUILD", "cc_library(name='library')");
+  }
+
+  void testCCFlagsContainsSysroot(BuildConfiguration config, String sysroot, boolean shouldContain)
+      throws Exception {
+
+    RuleContext ruleContext =
+        getRuleContext(
+            getConfiguredTarget(Label.parseAbsolute("//dummy:library", ImmutableMap.of()), config));
+    ConfigurationMakeVariableContext context =
+        new ConfigurationMakeVariableContext(
+            ruleContext,
+            ruleContext.getTarget().getPackage(),
+            config,
+            ImmutableList.of(new CcCommon.CcFlagsSupplier(ruleContext)));
+    if (shouldContain) {
+      assertThat(context.lookupVariable("CC_FLAGS")).contains("--sysroot=" + sysroot);
+    } else {
+      assertThat(context.lookupVariable("CC_FLAGS")).doesNotContain("--sysroot=" + sysroot);
+    }
+  }
+
+  CcToolchainProvider getCcToolchainProvider(BuildConfiguration configuration) throws Exception {
+    CppConfiguration cppConfiguration = configuration.getFragment(CppConfiguration.class);
+    return Preconditions.checkNotNull(
+        (CcToolchainProvider)
+            getConfiguredTarget(
+                    cppConfiguration.getRuleProvidingCcToolchainProvider(), configuration)
+                .get(ToolchainInfo.PROVIDER));
+  }
+
+  @Test
+  public void testHostGrteTop() throws Exception {
+    scratch.file("a/grte/top/BUILD", "filegroup(name='everything')", "cc_library(name='library')");
+    useConfiguration("--host_grte_top=//a/grte/top");
+    BuildConfiguration target = getTargetConfiguration();
+    CcToolchainProvider targetCcProvider = getCcToolchainProvider(target);
+    BuildConfiguration host = getHostConfiguration();
+    CcToolchainProvider hostCcProvider = getCcToolchainProvider(host);
+
+    testCCFlagsContainsSysroot(host, "a/grte/top", true);
+    assertThat(hostCcProvider.getSysroot().equals(targetCcProvider.getSysroot())).isFalse();
+  }
+
+  @Test
+  public void testOverrideHostGrteTop() throws Exception {
+    scratch.file("a/grte/top/BUILD", "filegroup(name='everything')");
+    scratch.file("b/grte/top/BUILD", "filegroup(name='everything')");
+    useConfiguration("--grte_top=//a/grte/top", "--host_grte_top=//b/grte/top");
+    BuildConfiguration target = getTargetConfiguration();
+    CcToolchainProvider targetCcProvider = getCcToolchainProvider(target);
+    BuildConfiguration host = getHostConfiguration();
+    CcToolchainProvider hostCcProvider = getCcToolchainProvider(host);
+
+    assertThat(targetCcProvider.getSysroot()).isEqualTo("a/grte/top");
+    assertThat(hostCcProvider.getSysroot()).isEqualTo("b/grte/top");
+
+    testCCFlagsContainsSysroot(target, "a/grte/top", true);
+    testCCFlagsContainsSysroot(target, "b/grte/top", false);
+    testCCFlagsContainsSysroot(host, "b/grte/top", true);
+    testCCFlagsContainsSysroot(host, "a/grte/top", false);
+  }
+
+  @Test
+  public void testGrteTopAlias() throws Exception {
+    scratch.file("a/grte/top/BUILD", "filegroup(name='everything')");
+    scratch.file("b/grte/top/BUILD", "alias(name='everything', actual='//a/grte/top:everything')");
+    useConfiguration("--grte_top=//b/grte/top");
+    BuildConfiguration target = getTargetConfiguration();
+    CcToolchainProvider targetCcProvider = getCcToolchainProvider(target);
+
+    assertThat(targetCcProvider.getSysroot()).isEqualTo("a/grte/top");
+
+    testCCFlagsContainsSysroot(target, "a/grte/top", true);
+    testCCFlagsContainsSysroot(target, "b/grte/top", false);
+  }
+
+  @Test
+  public void testSysroot() throws Exception {
+    // BuildConfiguration shouldn't provide a sysroot option by default.
+    useConfiguration("--cpu=k8");
+    BuildConfiguration config = getTargetConfiguration();
+    testCCFlagsContainsSysroot(config, "/usr/grte/v1", true);
+
+    scratch.file("a/grte/top/BUILD", "filegroup(name='everything')");
+    // BuildConfiguration should work with label grte_top options.
+    useConfiguration("--cpu=k8", "--grte_top=//a/grte/top:everything");
+    config = getTargetConfiguration();
+    testCCFlagsContainsSysroot(config, "a/grte/top", true);
+  }
+
+  @Test
+  public void testSysrootInFeatureConfigBlocksLegacySysroot() throws Exception {
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder().withActionConfigs("sysroot_in_action_config"));
+    scratch.overwriteFile("a/grte/top/BUILD", "filegroup(name='everything')");
+    useConfiguration("--grte_top=//a/grte/top:everything");
+    RuleContext ruleContext =
+        getRuleContext(
+            getConfiguredTarget(
+                Label.parseAbsolute("//dummy:library", ImmutableMap.of()), targetConfig));
+    ConfigurationMakeVariableContext context =
+        new ConfigurationMakeVariableContext(
+            ruleContext,
+            ruleContext.getTarget().getPackage(),
+            targetConfig,
+            ImmutableList.of(new CcCommon.CcFlagsSupplier(ruleContext)));
+    assertThat(context.lookupVariable("CC_FLAGS"))
+        .contains("fc-start --sysroot=a/grte/top-from-feature fc-end");
+    assertThat(context.lookupVariable("CC_FLAGS")).doesNotContain("--sysroot=a/grte/top fc");
+  }
+
+  @Test
+  public void testSysrootWithHostConfig() throws Exception {
+    // The host BuildConfiguration shouldn't provide a sysroot option by default.
+    for (String cpu : new String[] {"piii", "k8"}) {
+      useConfiguration("--cpu=" + cpu);
+      BuildConfiguration config = getHostConfiguration();
+      testCCFlagsContainsSysroot(config, "/usr/grte/v1", true);
+    }
+    // The host BuildConfiguration should work with label grte_top options.
+    scratch.file("a/grte/top/BUILD", "filegroup(name='everything')");
+    for (String cpu : new String[] {"piii", "k8"}) {
+      useConfiguration("--cpu=" + cpu, "--host_grte_top=//a/grte/top");
+      BuildConfiguration config = getHostConfiguration();
+      testCCFlagsContainsSysroot(config, "a/grte/top", true);
+
+      // "--grte_top" does *not* set the host grte_top,
+      // so we don't get "a/grte/top" here, but instead the default "/usr/grte/v1"
+      useConfiguration("--cpu=" + cpu, "--grte_top=//a/grte/top");
+      config = getHostConfiguration();
+      testCCFlagsContainsSysroot(config, "/usr/grte/v1", true);
+
+      // If a host_crosstool_top is set, we shouldn't see the grte_top option in the host config.
+      // (Assuming there was not also a --host_grte_top specified)
+      useConfiguration(
+          "--cpu=" + cpu,
+          "--grte_top=//a/grte/top",
+          "--host_crosstool_top=" + analysisMock.ccSupport().getMockCrosstoolLabel());
+      config = getHostConfiguration();
+      testCCFlagsContainsSysroot(config, "/usr/grte/v1", true);
+    }
+  }
+
+  @Test
+  public void testConfigurableSysroot() throws Exception {
+    scratch.file(
+        "test/config_setting/BUILD",
+        "config_setting(name='defines', values={'define': 'override_grte_top=1'})");
+    scratch.file("a/grte/top/BUILD", "filegroup(name='everything')");
+    scratch.file("b/grte/top/BUILD", "filegroup(name='everything')");
+    scratch.file(
+        "c/grte/top/BUILD",
+        "alias(",
+        "  name = 'everything',",
+        "  actual=select(",
+        "      {'//test/config_setting:defines' : '//a/grte/top:everything',",
+        "       '//conditions:default' : '//b/grte/top:everything'}",
+        "  )",
+        ")");
+    useConfiguration("--grte_top=//c/grte/top:everything");
+    CcToolchainProvider ccProvider = getCcToolchainProvider(getTargetConfiguration());
+    assertThat(ccProvider.getSysroot()).isEqualTo("b/grte/top");
+
+    useConfiguration("--grte_top=//c/grte/top:everything", "--define=override_grte_top=1");
+    ccProvider = getCcToolchainProvider(getTargetConfiguration());
+    assertThat(ccProvider.getSysroot()).isEqualTo("a/grte/top");
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLineTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLineTest.java
new file mode 100644
index 0000000..fbe9cfb
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLineTest.java
@@ -0,0 +1,468 @@
+// Copyright 2020 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.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
+import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
+import com.google.devtools.build.lib.actions.Artifact.SpecialArtifactType;
+import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
+import com.google.devtools.build.lib.actions.ArtifactRoot;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.LibraryToLinkValue;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.SequenceBuilder;
+import com.google.devtools.build.lib.rules.cpp.CppActionConfigs.CppPlatform;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
+import com.google.devtools.build.lib.rules.cpp.Link.LinkingMode;
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link LinkCommandLine}. In particular, tests command line emitted subject to the
+ * presence of certain build variables.
+ */
+@RunWith(JUnit4.class)
+public final class LinkCommandLineTest extends BuildViewTestCase {
+
+  private Artifact scratchArtifact(String s) {
+    Path execRoot = outputBase.getRelative("exec");
+    Path outputRoot = execRoot.getRelative("root");
+    ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, outputRoot);
+    try {
+      return ActionsTestUtil.createArtifact(
+          root, scratch.overwriteFile(outputRoot.getRelative(s).toString()));
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private CcToolchainVariables.Builder getMockBuildVariables() {
+    return getMockBuildVariables(ImmutableList.<String>of());
+  }
+
+  private static CcToolchainVariables.Builder getMockBuildVariables(
+      ImmutableList<String> linkstampOutputs) {
+    CcToolchainVariables.Builder result = CcToolchainVariables.builder();
+
+    result.addStringVariable(LinkBuildVariables.GENERATE_INTERFACE_LIBRARY.getVariableName(), "no");
+    result.addStringVariable(
+        LinkBuildVariables.INTERFACE_LIBRARY_INPUT.getVariableName(), "ignored");
+    result.addStringVariable(
+        LinkBuildVariables.INTERFACE_LIBRARY_OUTPUT.getVariableName(), "ignored");
+    result.addStringVariable(
+        LinkBuildVariables.INTERFACE_LIBRARY_BUILDER.getVariableName(), "ignored");
+    result.addStringSequenceVariable(
+        LinkBuildVariables.LINKSTAMP_PATHS.getVariableName(), linkstampOutputs);
+
+    return result;
+  }
+
+  private FeatureConfiguration getMockFeatureConfiguration() throws Exception {
+    ImmutableList<CToolchain.Feature> features =
+        new ImmutableList.Builder<CToolchain.Feature>()
+            .addAll(
+                CppActionConfigs.getLegacyFeatures(
+                    CppPlatform.LINUX,
+                    ImmutableSet.of(),
+                    "MOCK_LINKER_TOOL",
+                    /* supportsEmbeddedRuntimes= */ true,
+                    /* supportsInterfaceSharedLibraries= */ false,
+                    /* doNotSplitLinkingCmdline= */ true))
+            .addAll(
+                CppActionConfigs.getFeaturesToAppearLastInFeaturesList(
+                    ImmutableSet.of(), /* doNotSplitLinkingCmdline= */ true))
+            .build();
+
+    ImmutableList<CToolchain.ActionConfig> actionConfigs =
+        CppActionConfigs.getLegacyActionConfigs(
+            CppPlatform.LINUX,
+            "MOCK_GCC_TOOL",
+            "MOCK_AR_TOOL",
+            "MOCK_STRIP_TOOL",
+            /* supportsInterfaceSharedLibraries= */ false,
+            /* existingActionConfigNames= */ ImmutableSet.of());
+
+    return CcToolchainFeaturesTest.buildFeatures(features, actionConfigs)
+        .getFeatureConfiguration(
+            ImmutableSet.of(
+                Link.LinkTargetType.EXECUTABLE.getActionName(),
+                Link.LinkTargetType.NODEPS_DYNAMIC_LIBRARY.getActionName(),
+                Link.LinkTargetType.STATIC_LIBRARY.getActionName(),
+                CppActionNames.CPP_COMPILE,
+                CppActionNames.LINKSTAMP_COMPILE,
+                CppRuleClasses.INCLUDES,
+                CppRuleClasses.PREPROCESSOR_DEFINES,
+                CppRuleClasses.INCLUDE_PATHS,
+                CppRuleClasses.PIC));
+  }
+
+  private LinkCommandLine.Builder minimalConfiguration(CcToolchainVariables.Builder variables)
+      throws Exception {
+    return new LinkCommandLine.Builder()
+        .setBuildVariables(variables.build())
+        .setFeatureConfiguration(getMockFeatureConfiguration());
+  }
+
+  private LinkCommandLine.Builder minimalConfiguration() throws Exception {
+    return minimalConfiguration(getMockBuildVariables());
+  }
+
+  private void assertError(String expectedSubstring, LinkCommandLine.Builder builder) {
+    RuntimeException e = assertThrows(RuntimeException.class, () -> builder.build());
+    assertThat(e).hasMessageThat().contains(expectedSubstring);
+  }
+
+  @Test
+  public void testStaticLinkWithBuildInfoHeadersIsError() throws Exception {
+    assertError(
+        "build info headers may only be present",
+        minimalConfiguration()
+            .setLinkTargetType(LinkTargetType.STATIC_LIBRARY)
+            .setLinkingMode(LinkingMode.STATIC)
+            .setBuildInfoHeaderArtifacts(
+                ImmutableList.of(scratchArtifact("FakeBuildInfoHeaderArtifact1"))));
+  }
+
+  /**
+   * Tests that when linking without linkstamps, the exec command is the same as the link command.
+   */
+  @Test
+  public void testLinkCommandIsExecCommandWhenNoLinkstamps() throws Exception {
+    LinkCommandLine linkConfig =
+        minimalConfiguration()
+            .setActionName(LinkTargetType.EXECUTABLE.getActionName())
+            .setLinkTargetType(LinkTargetType.EXECUTABLE)
+            .build();
+    List<String> rawLinkArgv = linkConfig.getRawLinkArgv();
+    assertThat(linkConfig.arguments()).isEqualTo(rawLinkArgv);
+  }
+
+  /** Tests that symbol count output does not appear in argv when it should not. */
+  @Test
+  public void testSymbolCountsDisabled() throws Exception {
+    LinkCommandLine linkConfig =
+        minimalConfiguration()
+            .forceToolPath("foo/bar/gcc")
+            .setLinkTargetType(LinkTargetType.NODEPS_DYNAMIC_LIBRARY)
+            .setLinkingMode(LinkingMode.STATIC)
+            .build();
+    List<String> argv = linkConfig.getRawLinkArgv();
+    for (String arg : argv) {
+      assertThat(arg).doesNotContain("print-symbol-counts");
+    }
+  }
+
+  @Test
+  public void testLibrariesToLink() throws Exception {
+    CcToolchainVariables.Builder variables =
+        getMockBuildVariables()
+            .addCustomBuiltVariable(
+                LinkBuildVariables.LIBRARIES_TO_LINK.getVariableName(),
+                new SequenceBuilder()
+                    .addValue(LibraryToLinkValue.forStaticLibrary("foo", false))
+                    .addValue(LibraryToLinkValue.forStaticLibrary("bar", true)));
+
+    LinkCommandLine linkConfig =
+        minimalConfiguration(variables)
+            .forceToolPath("foo/bar/gcc")
+            .setActionName(LinkTargetType.NODEPS_DYNAMIC_LIBRARY.getActionName())
+            .setLinkTargetType(LinkTargetType.NODEPS_DYNAMIC_LIBRARY)
+            .setLinkingMode(LinkingMode.STATIC)
+            .build();
+    String commandLine = Joiner.on(" ").join(linkConfig.getRawLinkArgv());
+    assertThat(commandLine).matches(".*foo -Wl,-whole-archive bar -Wl,-no-whole-archive.*");
+  }
+
+  @Test
+  public void testLibrarySearchDirectories() throws Exception {
+    CcToolchainVariables.Builder variables =
+        getMockBuildVariables()
+            .addStringSequenceVariable(
+                LinkBuildVariables.LIBRARY_SEARCH_DIRECTORIES.getVariableName(),
+                ImmutableList.of("foo", "bar"));
+
+    LinkCommandLine linkConfig =
+        minimalConfiguration(variables)
+            .setActionName(LinkTargetType.NODEPS_DYNAMIC_LIBRARY.getActionName())
+            .setLinkTargetType(LinkTargetType.NODEPS_DYNAMIC_LIBRARY)
+            .setLinkingMode(LinkingMode.STATIC)
+            .build();
+    assertThat(linkConfig.getRawLinkArgv()).containsAtLeast("-Lfoo", "-Lbar").inOrder();
+  }
+
+  @Test
+  public void testLinkerParamFileForStaticLibrary() throws Exception {
+    CcToolchainVariables.Builder variables =
+        getMockBuildVariables()
+            .addStringVariable(
+                LinkBuildVariables.LINKER_PARAM_FILE.getVariableName(), "foo/bar.param");
+
+    LinkCommandLine linkConfig =
+        minimalConfiguration(variables)
+            .setActionName(LinkTargetType.STATIC_LIBRARY.getActionName())
+            .setLinkTargetType(LinkTargetType.STATIC_LIBRARY)
+            .setLinkingMode(Link.LinkingMode.STATIC)
+            .build();
+    assertThat(linkConfig.getRawLinkArgv()).contains("@foo/bar.param");
+  }
+
+  @Test
+  public void testLinkerParamFileForDynamicLibrary() throws Exception {
+    CcToolchainVariables.Builder variables =
+        getMockBuildVariables()
+            .addStringVariable(
+                LinkBuildVariables.LINKER_PARAM_FILE.getVariableName(), "foo/bar.param");
+
+    LinkCommandLine linkConfig =
+        minimalConfiguration(variables)
+            .setActionName(LinkTargetType.NODEPS_DYNAMIC_LIBRARY.getActionName())
+            .setLinkTargetType(LinkTargetType.NODEPS_DYNAMIC_LIBRARY)
+            .setLinkingMode(Link.LinkingMode.STATIC)
+            .doNotSplitLinkingCmdLine()
+            .build();
+    assertThat(linkConfig.getRawLinkArgv()).contains("@foo/bar.param");
+  }
+
+  private List<String> basicArgv(LinkTargetType targetType) throws Exception {
+    return basicArgv(targetType, getMockBuildVariables());
+  }
+
+  private List<String> basicArgv(LinkTargetType targetType, CcToolchainVariables.Builder variables)
+      throws Exception {
+    LinkCommandLine linkConfig =
+        minimalConfiguration(variables)
+            .setActionName(targetType.getActionName())
+            .setLinkTargetType(targetType)
+            .setLinkingMode(LinkingMode.STATIC)
+            .build();
+    return linkConfig.arguments();
+  }
+
+  /** Tests that a "--force_pic" configuration applies "-pie" to executable links. */
+  @Test
+  public void testPicMode() throws Exception {
+    String pieArg = "-pie";
+
+    // Disabled:
+    assertThat(basicArgv(LinkTargetType.EXECUTABLE)).doesNotContain(pieArg);
+    assertThat(basicArgv(LinkTargetType.NODEPS_DYNAMIC_LIBRARY)).doesNotContain(pieArg);
+    assertThat(basicArgv(LinkTargetType.STATIC_LIBRARY)).doesNotContain(pieArg);
+    assertThat(basicArgv(LinkTargetType.PIC_STATIC_LIBRARY)).doesNotContain(pieArg);
+    assertThat(basicArgv(LinkTargetType.ALWAYS_LINK_STATIC_LIBRARY)).doesNotContain(pieArg);
+    assertThat(basicArgv(LinkTargetType.ALWAYS_LINK_PIC_STATIC_LIBRARY)).doesNotContain(pieArg);
+
+    CcToolchainVariables.Builder picVariables =
+        getMockBuildVariables()
+            .addStringVariable(LinkBuildVariables.FORCE_PIC.getVariableName(), "");
+    // Enabled:
+    useConfiguration("--force_pic");
+    assertThat(basicArgv(LinkTargetType.EXECUTABLE, picVariables)).contains(pieArg);
+    assertThat(basicArgv(LinkTargetType.NODEPS_DYNAMIC_LIBRARY, picVariables))
+        .doesNotContain(pieArg);
+    assertThat(basicArgv(LinkTargetType.STATIC_LIBRARY, picVariables)).doesNotContain(pieArg);
+    assertThat(basicArgv(LinkTargetType.PIC_STATIC_LIBRARY, picVariables)).doesNotContain(pieArg);
+    assertThat(basicArgv(LinkTargetType.ALWAYS_LINK_STATIC_LIBRARY, picVariables))
+        .doesNotContain(pieArg);
+    assertThat(basicArgv(LinkTargetType.ALWAYS_LINK_PIC_STATIC_LIBRARY, picVariables))
+        .doesNotContain(pieArg);
+  }
+
+  @Test
+  public void testSplitStaticLinkCommand() throws Exception {
+    useConfiguration("--nostart_end_lib");
+    Artifact paramFile = scratchArtifact("some/file.params");
+    LinkCommandLine linkConfig =
+        minimalConfiguration(
+                getMockBuildVariables()
+                    .addStringVariable(
+                        LinkBuildVariables.OUTPUT_EXECPATH.getVariableName(), "a/FakeOutput")
+                    .addStringVariable(
+                        LinkBuildVariables.LINKER_PARAM_FILE.getVariableName(), "some/file.params"))
+            .setActionName(LinkTargetType.STATIC_LIBRARY.getActionName())
+            .setLinkTargetType(LinkTargetType.STATIC_LIBRARY)
+            .forceToolPath("foo/bar/ar")
+            .setParamFile(paramFile)
+            .build();
+    Pair<List<String>, List<String>> result = linkConfig.splitCommandline();
+    assertThat(result.first).isEqualTo(Arrays.asList("foo/bar/ar", "@some/file.params"));
+    assertThat(result.second).isEqualTo(Arrays.asList("rcsD", "a/FakeOutput"));
+  }
+
+  @Test
+  public void testSplitDynamicLinkCommand() throws Exception {
+    useConfiguration("--nostart_end_lib");
+    Artifact paramFile = scratchArtifact("some/file.params");
+    LinkCommandLine linkConfig =
+        minimalConfiguration(
+                getMockBuildVariables()
+                    .addStringVariable(
+                        LinkBuildVariables.OUTPUT_EXECPATH.getVariableName(), "a/FakeOutput")
+                    .addStringVariable(
+                        LinkBuildVariables.LINKER_PARAM_FILE.getVariableName(), "some/file.params")
+                    .addStringSequenceVariable(
+                        LinkBuildVariables.USER_LINK_FLAGS.getVariableName(), ImmutableList.of("")))
+            .setActionName(LinkTargetType.DYNAMIC_LIBRARY.getActionName())
+            .setLinkTargetType(LinkTargetType.DYNAMIC_LIBRARY)
+            .forceToolPath("foo/bar/linker")
+            .setParamFile(paramFile)
+            .doNotSplitLinkingCmdLine()
+            .build();
+    Pair<List<String>, List<String>> result = linkConfig.splitCommandline();
+    assertThat(result.first).containsExactly("foo/bar/linker", "@some/file.params").inOrder();
+    assertThat(result.second).containsExactly("-shared", "-o", "a/FakeOutput", "").inOrder();
+  }
+
+  @Test
+  public void testStaticLinkCommand() throws Exception {
+    useConfiguration("--nostart_end_lib");
+    LinkCommandLine linkConfig =
+        minimalConfiguration(
+                getMockBuildVariables()
+                    .addStringVariable(
+                        LinkBuildVariables.OUTPUT_EXECPATH.getVariableName(), "a/FakeOutput"))
+            .forceToolPath("foo/bar/ar")
+            .setActionName(LinkTargetType.STATIC_LIBRARY.getActionName())
+            .setLinkTargetType(LinkTargetType.STATIC_LIBRARY)
+            .build();
+    List<String> result = linkConfig.getRawLinkArgv();
+    assertThat(result).isEqualTo(Arrays.asList("foo/bar/ar", "rcsD", "a/FakeOutput"));
+  }
+
+  @Test
+  public void testSplitAlwaysLinkLinkCommand() throws Exception {
+    CcToolchainVariables.Builder variables =
+        CcToolchainVariables.builder()
+            .addStringVariable(CcCommon.SYSROOT_VARIABLE_NAME, "/usr/grte/v1")
+            .addStringVariable(LinkBuildVariables.OUTPUT_EXECPATH.getVariableName(), "a/FakeOutput")
+            .addStringVariable(
+                LinkBuildVariables.LINKER_PARAM_FILE.getVariableName(), "some/file.params")
+            .addCustomBuiltVariable(
+                LinkBuildVariables.LIBRARIES_TO_LINK.getVariableName(),
+                new CcToolchainVariables.SequenceBuilder()
+                    .addValue(LibraryToLinkValue.forObjectFile("foo.o", false))
+                    .addValue(LibraryToLinkValue.forObjectFile("bar.o", false)));
+
+    Artifact paramFile = scratchArtifact("some/file.params");
+    LinkCommandLine linkConfig =
+        minimalConfiguration(variables)
+            .setActionName(LinkTargetType.ALWAYS_LINK_STATIC_LIBRARY.getActionName())
+            .setLinkTargetType(LinkTargetType.ALWAYS_LINK_STATIC_LIBRARY)
+            .forceToolPath("foo/bar/ar")
+            .setParamFile(paramFile)
+            .build();
+    Pair<List<String>, List<String>> result = linkConfig.splitCommandline();
+
+    assertThat(result.first).isEqualTo(Arrays.asList("foo/bar/ar", "@some/file.params"));
+    assertThat(result.second).isEqualTo(Arrays.asList("rcsD", "a/FakeOutput", "foo.o", "bar.o"));
+  }
+
+  private SpecialArtifact createTreeArtifact(String name) {
+    FileSystem fs = scratch.getFileSystem();
+    Path execRoot = fs.getPath(TestUtils.tmpDir());
+    PathFragment execPath = PathFragment.create("out").getRelative(name);
+    return new SpecialArtifact(
+        ArtifactRoot.asDerivedRoot(execRoot, execRoot.getRelative("out")),
+        execPath,
+        ActionsTestUtil.NULL_ARTIFACT_OWNER,
+        SpecialArtifactType.TREE);
+  }
+
+  private void verifyArguments(
+      Iterable<String> arguments,
+      Iterable<String> allowedArguments,
+      Iterable<String> disallowedArguments) {
+    assertThat(arguments).containsAtLeastElementsIn(allowedArguments);
+    assertThat(arguments).containsNoneIn(disallowedArguments);
+  }
+
+  @Test
+  public void testTreeArtifactLink() throws Exception {
+    SpecialArtifact testTreeArtifact = createTreeArtifact("library_directory");
+
+    TreeFileArtifact library0 =
+        ActionsTestUtil.createTreeFileArtifactWithNoGeneratingAction(
+            testTreeArtifact, "library0.o");
+    TreeFileArtifact library1 =
+        ActionsTestUtil.createTreeFileArtifactWithNoGeneratingAction(
+            testTreeArtifact, "library1.o");
+
+    ArtifactExpander expander =
+        new ArtifactExpander() {
+          @Override
+          public void expand(Artifact artifact, Collection<? super Artifact> output) {
+            if (artifact.equals(testTreeArtifact)) {
+              output.add(library0);
+              output.add(library1);
+            }
+          };
+        };
+
+    Iterable<String> treeArtifactsPaths = ImmutableList.of(testTreeArtifact.getExecPathString());
+    Iterable<String> treeFileArtifactsPaths =
+        ImmutableList.of(library0.getExecPathString(), library1.getExecPathString());
+
+    Artifact paramFile = scratchArtifact("some/file.params");
+
+    LinkCommandLine linkConfig =
+        minimalConfiguration(
+                getMockBuildVariables()
+                    .addStringVariable(
+                        LinkBuildVariables.LINKER_PARAM_FILE.getVariableName(), "some/file.params")
+                    .addCustomBuiltVariable(
+                        LinkBuildVariables.LIBRARIES_TO_LINK.getVariableName(),
+                        new CcToolchainVariables.SequenceBuilder()
+                            .addValue(
+                                LibraryToLinkValue.forObjectFileGroup(
+                                    ImmutableList.of(testTreeArtifact), false))))
+            .forceToolPath("foo/bar/gcc")
+            .setActionName(LinkTargetType.STATIC_LIBRARY.getActionName())
+            .setLinkTargetType(LinkTargetType.STATIC_LIBRARY)
+            .setLinkingMode(Link.LinkingMode.STATIC)
+            .setParamFile(paramFile)
+            .build();
+
+    // Should only reference the tree artifact.
+    verifyArguments(linkConfig.arguments(null), treeArtifactsPaths, treeFileArtifactsPaths);
+    verifyArguments(linkConfig.getRawLinkArgv(null), treeArtifactsPaths, treeFileArtifactsPaths);
+    verifyArguments(
+        linkConfig.paramCmdLine().arguments(null), treeArtifactsPaths, treeFileArtifactsPaths);
+
+    // Should only reference tree file artifacts.
+    verifyArguments(linkConfig.arguments(expander), treeFileArtifactsPaths, treeArtifactsPaths);
+    verifyArguments(
+        linkConfig.getRawLinkArgv(expander), treeFileArtifactsPaths, treeArtifactsPaths);
+    verifyArguments(
+        linkConfig.paramCmdLine().arguments(expander), treeFileArtifactsPaths, treeArtifactsPaths);
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/NonWindowsCcBinaryThinLtoTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/NonWindowsCcBinaryThinLtoTest.java
new file mode 100644
index 0000000..a893d6c
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/NonWindowsCcBinaryThinLtoTest.java
@@ -0,0 +1,230 @@
+// Copyright 2020 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.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.util.AnalysisMock;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.packages.util.Crosstool.CcToolchainConfig;
+import com.google.devtools.build.lib.packages.util.MockCcSupport;
+import com.google.devtools.build.lib.rules.cpp.CppConfiguration.Tool;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for cc_binary with ThinLTO.
+ *
+ * <p>As of 2020-02-06, these tests do not work on Windows, hence the "NonWindows" part of the class
+ * name.
+ */
+@RunWith(JUnit4.class)
+public final class NonWindowsCcBinaryThinLtoTest extends BuildViewTestCase {
+
+  public void createTestFiles(String extraTestParameters, String extraLibraryParameters)
+      throws Exception {
+    scratch.overwriteFile(
+        "base/BUILD", "cc_library(name = 'system_malloc', visibility = ['//visibility:public'])");
+    scratch.file(
+        "pkg/BUILD",
+        "package(features = ['thin_lto'])",
+        "cc_test(",
+        "    name = 'bin_test',",
+        "    srcs = ['bin_test.cc', ],",
+        "    deps = [ ':lib' ], ",
+        extraTestParameters,
+        "    malloc = '//base:system_malloc'",
+        ")",
+        "cc_test(",
+        "    name = 'bin_test2',",
+        "    srcs = ['bin_test2.cc', ],",
+        "    deps = [ ':lib' ], ",
+        extraTestParameters,
+        "    malloc = '//base:system_malloc'",
+        ")",
+        "cc_library(",
+        "    name = 'lib',",
+        "    srcs = ['libfile.cc'],",
+        "    hdrs = ['libfile.h'],",
+        extraLibraryParameters,
+        "    linkstamp = 'linkstamp.cc',",
+        ")");
+
+    scratch.file("pkg/bin_test.cc", "#include \"pkg/libfile.h\"", "int main() { return pkg(); }");
+    scratch.file("pkg/bin_test2.cc", "#include \"pkg/libfile.h\"", "int main() { return pkg(); }");
+    scratch.file("pkg/libfile.cc", "int pkg() { return 42; }");
+    scratch.file("pkg/libfile.h", "int pkg();");
+    scratch.file("pkg/linkstamp.cc");
+  }
+
+  /** Helper method to get the root prefix from the given dwpFile. */
+  private static PathFragment dwpRootPrefix(Artifact dwpFile) throws Exception {
+    return dwpFile
+        .getExecPath()
+        .subFragment(
+            0, dwpFile.getExecPath().segmentCount() - dwpFile.getRootRelativePath().segmentCount());
+  }
+
+  /** Helper method that checks that a .dwp has the expected generating action structure. */
+  private void validateDwp(
+      RuleContext ruleContext,
+      Artifact dwpFile,
+      CcToolchainProvider toolchain,
+      List<String> expectedInputs)
+      throws Exception {
+    SpawnAction dwpAction = (SpawnAction) getGeneratingAction(dwpFile);
+    String dwpToolPath = toolchain.getToolPathFragment(Tool.DWP, ruleContext).getPathString();
+    assertThat(dwpAction.getMnemonic()).isEqualTo("CcGenerateDwp");
+    assertThat(dwpToolPath).isEqualTo(dwpAction.getCommandFilename());
+    List<String> commandArgs = dwpAction.getArguments();
+    // The first argument should be the command being executed.
+    assertThat(dwpToolPath).isEqualTo(commandArgs.get(0));
+    // The final two arguments should be "-o dwpOutputFile".
+    assertThat(commandArgs.subList(commandArgs.size() - 2, commandArgs.size()))
+        .containsExactly("-o", dwpFile.getExecPathString())
+        .inOrder();
+    // The remaining arguments should be the set of .dwo inputs (in any order).
+    assertThat(commandArgs.subList(1, commandArgs.size() - 2))
+        .containsExactlyElementsIn(expectedInputs);
+  }
+
+  @Test
+  public void testLinkstaticCcLibraryOnTestFission() throws Exception {
+    createTestFiles("", "linkstatic = 1,");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.THIN_LTO_LINKSTATIC_TESTS_USE_SHARED_NONLTO_BACKENDS,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.PER_OBJECT_DEBUG_INFO));
+    useConfiguration(
+        "--fission=yes", "--features=thin_lto_linkstatic_tests_use_shared_nonlto_backends");
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin_test");
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+
+    // The cc_test source should still get LTO in this case
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "bin_test.lto/pkg/_objs/bin_test/bin_test.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(artifactsToStrings(backendAction.getOutputs()))
+        .containsExactly(
+            "bin pkg/bin_test.lto/pkg/_objs/bin_test/bin_test.pic.o",
+            "bin pkg/bin_test.lto/pkg/_objs/bin_test/bin_test.pic.dwo");
+
+    assertThat(backendAction.getArguments()).contains("per_object_debug_info_option");
+
+    // The linkstatic cc_library source should get shared non-LTO
+    backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "shared.nonlto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(backendAction.getArguments()).contains("-fPIC");
+    assertThat(artifactsToStrings(backendAction.getOutputs()))
+        .containsExactly(
+            "bin shared.nonlto/pkg/_objs/lib/libfile.pic.o",
+            "bin shared.nonlto/pkg/_objs/lib/libfile.pic.dwo");
+
+    assertThat(backendAction.getArguments()).contains("per_object_debug_info_option");
+
+    // Now check the dwp action.
+    Artifact dwpFile = getFileConfiguredTarget(pkg.getLabel() + ".dwp").getArtifact();
+    PathFragment rootPrefix = dwpRootPrefix(dwpFile);
+    RuleContext ruleContext = getRuleContext(pkg);
+    CcToolchainProvider toolchain =
+        CppHelper.getToolchainUsingDefaultCcToolchainAttribute(ruleContext);
+    validateDwp(
+        ruleContext,
+        dwpFile,
+        toolchain,
+        ImmutableList.of(
+            rootPrefix + "/shared.nonlto/pkg/_objs/lib/libfile.pic.dwo",
+            rootPrefix + "/pkg/bin_test.lto/pkg/_objs/bin_test/bin_test.pic.dwo"));
+  }
+
+  @Test
+  public void testLinkstaticCcLibraryOnTest() throws Exception {
+    createTestFiles("", "linkstatic = 1,");
+
+    AnalysisMock.get()
+        .ccSupport()
+        .setupCcToolchainConfig(
+            mockToolsConfig,
+            CcToolchainConfig.builder()
+                .withFeatures(
+                    CppRuleClasses.THIN_LTO,
+                    CppRuleClasses.SUPPORTS_START_END_LIB,
+                    CppRuleClasses.THIN_LTO_LINKSTATIC_TESTS_USE_SHARED_NONLTO_BACKENDS,
+                    MockCcSupport.HOST_AND_NONHOST_CONFIGURATION_FEATURES,
+                    CppRuleClasses.SUPPORTS_PIC,
+                    CppRuleClasses.PER_OBJECT_DEBUG_INFO));
+    useConfiguration("--features=thin_lto_linkstatic_tests_use_shared_nonlto_backends");
+
+    ConfiguredTarget pkg = getConfiguredTarget("//pkg:bin_test");
+    Artifact pkgArtifact = getFilesToBuild(pkg).getSingleton();
+    CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(pkgArtifact);
+
+    ConfiguredTarget pkg2 = getConfiguredTarget("//pkg:bin_test2");
+    Artifact pkgArtifact2 = getFilesToBuild(pkg2).getSingleton();
+    CppLinkAction linkAction2 = (CppLinkAction) getGeneratingAction(pkgArtifact2);
+
+    // The cc_test source should still get LTO in this case
+    LtoBackendAction backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "bin_test.lto/pkg/_objs/bin_test/bin_test.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+
+    // The linkstatic cc_library sources should get shared non-LTO
+
+    backendAction =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction, "shared.nonlto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+    assertThat(backendAction.getArguments()).contains("-fPIC");
+
+    LtoBackendAction backendAction2 =
+        (LtoBackendAction)
+            getPredecessorByInputName(linkAction2, "shared.nonlto/pkg/_objs/lib/libfile.pic.o");
+    assertThat(backendAction2.getMnemonic()).isEqualTo("CcLtoBackendCompile");
+
+    assertThat(backendAction).isEqualTo(backendAction2);
+  }
+
+  private Action getPredecessorByInputName(Action action, String str) {
+    for (Artifact a : action.getInputs().toList()) {
+      if (a.getExecPathString().contains(str)) {
+        return getGeneratingAction(a);
+      }
+    }
+    return null;
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/SpawnGccStrategyTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/SpawnGccStrategyTest.java
new file mode 100644
index 0000000..86df9eb
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/SpawnGccStrategyTest.java
@@ -0,0 +1,83 @@
+// Copyright 2020 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.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactRoot;
+import com.google.devtools.build.lib.actions.ExecutionRequirements;
+import com.google.devtools.build.lib.actions.Spawn;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.clock.JavaClock;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.vfs.DigestHashFunction;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+/** Tests for {@link CppCompileAction#createSpawn}. */
+@RunWith(JUnit4.class)
+public final class SpawnGccStrategyTest {
+  private FileSystem fs;
+  private ArtifactRoot ar;
+  private Path execRoot;
+
+  @Before
+  public void setup() {
+    fs = new InMemoryFileSystem(new JavaClock(), DigestHashFunction.SHA256);
+    execRoot = fs.getPath("/exec/root");
+    ar = ArtifactRoot.asDerivedRoot(execRoot, execRoot.getChild("out"));
+  }
+
+  @Test
+  public void testInMemoryDotdFileAndExecutionRequirement() throws Exception {
+    // Test that when in memory dotd files are enabled the execution requirement to inline the dotd
+    // file is added to the spawn.
+
+    // arrange
+    Artifact dotdFile = ActionsTestUtil.createArtifact(ar, ar.getRoot().asPath().getChild("dot.d"));
+    CppCompileAction action = Mockito.mock(CppCompileAction.class);
+    when(action.getOutputs()).thenReturn(ImmutableSet.of());
+    when(action.getMandatoryInputs()).thenReturn(NestedSetBuilder.emptySet(Order.STABLE_ORDER));
+    when(action.getAdditionalInputs()).thenReturn(NestedSetBuilder.emptySet(Order.STABLE_ORDER));
+    when(action.getExecutionInfo()).thenReturn(ImmutableMap.of());
+    when(action.getArguments()).thenReturn(ImmutableList.of());
+    when(action.getEnvironment(ArgumentMatchers.any())).thenReturn(ImmutableMap.of());
+    when(action.getDotdFile()).thenReturn(dotdFile);
+    when(action.useInMemoryDotdFiles()).thenReturn(true);
+    when(action.estimateResourceConsumptionLocal()).thenReturn(AbstractAction.DEFAULT_RESOURCE_SET);
+    when(action.createSpawn(any())).thenCallRealMethod();
+
+    // act
+    Spawn spawn = action.createSpawn(ImmutableMap.of());
+
+    ImmutableMap<String, String> execInfo = spawn.getExecutionInfo();
+    assertThat(execInfo.get(ExecutionRequirements.REMOTE_EXECUTION_INLINE_OUTPUTS))
+        .isEqualTo(action.getDotdFile().getExecPathString());
+  }
+}