// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.cpp; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; 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.ResourceSet; import com.google.devtools.build.lib.actions.Root; 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.RunfilesProvider; import com.google.devtools.build.lib.analysis.util.ActionTester; import com.google.devtools.build.lib.analysis.util.ActionTester.ActionCombinationFactory; import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariableValue; import com.google.devtools.build.lib.rules.cpp.CppLinkActionConfigs.CppLinkPlatform; import com.google.devtools.build.lib.rules.cpp.Link.LinkStaticness; import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType; import com.google.devtools.build.lib.rules.cpp.Link.Staticness; import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink; import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.util.OsUtils; import com.google.devtools.build.lib.vfs.PathFragment; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link CppLinkAction}. */ @RunWith(JUnit4.class) public class CppLinkActionTest extends BuildViewTestCase { private RuleContext createDummyRuleContext() throws Exception { return view.getRuleContextForTesting( reporter, scratchConfiguredTarget( "dummyRuleContext", "dummyRuleContext", // CppLinkAction creation requires a CcToolchainProvider. "cc_library(name = 'dummyRuleContext')"), new StubAnalysisEnvironment() { @Override public void registerAction(ActionAnalysisMetadata... action) { // No-op. } @Override public Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root) { return CppLinkActionTest.this.getDerivedArtifact( rootRelativePath, root, ActionsTestUtil.NULL_ARTIFACT_OWNER); } }, masterConfig); } private final FeatureConfiguration getMockFeatureConfiguration() throws Exception { return CcToolchainFeaturesTest.buildFeatures( CppLinkActionConfigs.getCppLinkActionConfigs( CppLinkPlatform.LINUX, ImmutableSet.<String>of(), "dynamic_library_linker_tool", true)) .getFeatureConfiguration( Link.LinkTargetType.EXECUTABLE.getActionName(), Link.LinkTargetType.DYNAMIC_LIBRARY.getActionName(), Link.LinkTargetType.STATIC_LIBRARY.getActionName(), Link.LinkTargetType.PIC_STATIC_LIBRARY.getActionName(), Link.LinkTargetType.ALWAYS_LINK_STATIC_LIBRARY.getActionName(), Link.LinkTargetType.ALWAYS_LINK_PIC_STATIC_LIBRARY.getActionName()); } @Test public void testToolchainFeatureFlags() throws Exception { FeatureConfiguration featureConfiguration = CcToolchainFeaturesTest.buildFeatures( "feature {", " name: 'a'", " flag_set {", " action: '" + Link.LinkTargetType.EXECUTABLE.getActionName() + "'", " flag_group { flag: 'some_flag' }", " }", "}", "action_config {", " config_name: '" + Link.LinkTargetType.EXECUTABLE.getActionName() + "'", " action_name: '" + Link.LinkTargetType.EXECUTABLE.getActionName() + "'", " tool {", " tool_path: 'toolchain/mock_tool'", " }", "}") .getFeatureConfiguration("a", Link.LinkTargetType.EXECUTABLE.getActionName()); CppLinkAction linkAction = createLinkBuilder( Link.LinkTargetType.EXECUTABLE, "dummyRuleContext/out", ImmutableList.<Artifact>of(), ImmutableList.<LibraryToLink>of(), featureConfiguration) .build(); assertThat(linkAction.getArgv()).contains("some_flag"); } @Test public void testLibOptsAndLibSrcsAreInCorrectOrder() throws Exception { scratch.file( "x/BUILD", "cc_binary(", " name = 'foo',", " srcs = ['some-dir/bar.so', 'some-other-dir/qux.so'],", " linkopts = [", " '-ldl',", " '-lutil',", " ],", ")"); scratch.file("x/some-dir/bar.so"); scratch.file("x/some-other-dir/qux.so"); ConfiguredTarget configuredTarget = getConfiguredTarget("//x:foo"); CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(configuredTarget, "x/foo" + OsUtils.executableExtension()); List<String> arguments = linkAction.getLinkCommandLine().arguments(); assertThat(Joiner.on(" ").join(arguments)) .matches( ".* -L[^ ]*some-dir(?= ).* -L[^ ]*some-other-dir(?= ).* " + "-lbar -lqux(?= ).* -ldl -lutil .*"); assertThat(Joiner.on(" ").join(arguments)) .matches(".* -Wl,-rpath[^ ]*some-dir(?= ).* -Wl,-rpath[^ ]*some-other-dir .*"); } @Test public void testExposesRuntimeLibrarySearchDirectoriesVariable() throws Exception { scratch.file( "x/BUILD", "cc_binary(", " name = 'foo',", " srcs = ['some-dir/bar.so', 'some-other-dir/qux.so'],", ")"); scratch.file("x/some-dir/bar.so"); scratch.file("x/some-other-dir/qux.so"); ConfiguredTarget configuredTarget = getConfiguredTarget("//x:foo"); CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(configuredTarget, "x/foo" + OsUtils.executableExtension()); Iterable<? extends VariableValue> runtimeLibrarySearchDirectories = linkAction .getLinkCommandLine() .getBuildVariables() .getSequenceVariable(CppLinkActionBuilder.RUNTIME_LIBRARY_SEARCH_DIRECTORIES_VARIABLE); List<String> directories = new ArrayList<>(); for (VariableValue value : runtimeLibrarySearchDirectories) { directories.add(value.getStringValue("runtime_library_search_directory")); } assertThat(Joiner.on(" ").join(directories)).matches(".*some-dir .*some-other-dir"); } @Test public void testCompilesTestSourcesIntoDynamicLibrary() throws Exception { if (OS.getCurrent() == OS.WINDOWS) { // Skip the test on Windows. // TODO(bazel-team): maybe we should move that test that doesn't work with MSVC toolchain to // its own suite with a TestSpec? return; } scratch.file( "x/BUILD", "cc_test(name = 'a', srcs = ['a.cc'])", "cc_binary(name = 'b', srcs = ['a.cc'], linkstatic = 0)"); scratch.file("x/a.cc", "int main() {}"); useConfiguration("--experimental_link_compile_output_separately", "--force_pic"); ConfiguredTarget configuredTarget = getConfiguredTarget("//x:a"); String cpu = CrosstoolConfigurationHelper.defaultCpu(); String extension = OsUtils.executableExtension(); CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(configuredTarget, "x/a" + extension); assertThat(artifactsToStrings(linkAction.getInputs())) .contains("bin _solib_" + cpu + "/libx_Sliba.ifso"); assertThat(linkAction.getArguments()) .contains( getBinArtifactWithNoOwner("_solib_" + cpu + "/libx_Sliba.ifso").getExecPathString()); RunfilesProvider runfilesProvider = configuredTarget.getProvider(RunfilesProvider.class); assertThat(artifactsToStrings(runfilesProvider.getDefaultRunfiles().getArtifacts())) .contains("bin _solib_" + cpu + "/libx_Sliba.so"); configuredTarget = getConfiguredTarget("//x:b"); linkAction = (CppLinkAction) getGeneratingAction(configuredTarget, "x/b" + extension); assertThat(artifactsToStrings(linkAction.getInputs())).contains("bin x/_objs/b/x/a.pic.o"); runfilesProvider = configuredTarget.getProvider(RunfilesProvider.class); assertThat(artifactsToStrings(runfilesProvider.getDefaultRunfiles().getArtifacts())) .containsExactly("bin x/b"); } @Test public void testToolchainFeatureEnv() throws Exception { FeatureConfiguration featureConfiguration = CcToolchainFeaturesTest.buildFeatures( "feature {", " name: 'a'", " env_set {", " action: '" + Link.LinkTargetType.EXECUTABLE.getActionName() + "'", " env_entry { key: 'foo', value: 'bar' }", " }", "}") .getFeatureConfiguration("a"); CppLinkAction linkAction = createLinkBuilder( Link.LinkTargetType.EXECUTABLE, "dummyRuleContext/out", ImmutableList.<Artifact>of(), ImmutableList.<LibraryToLink>of(), featureConfiguration) .build(); assertThat(linkAction.getEnvironment()).containsEntry("foo", "bar"); } private enum NonStaticAttributes { OUTPUT_FILE, COMPILATION_INPUTS, NATIVE_DEPS, USE_TEST_ONLY_FLAGS, FAKE, RUNTIME_SOLIB_DIR } /** * This mainly checks that non-static links don't have identical keys. Many options are only * allowed on non-static links, and we test several of them here. */ @Test public void testComputeKeyNonStatic() throws Exception { final RuleContext ruleContext = createDummyRuleContext(); final PathFragment exeOutputPath = PathFragment.create("dummyRuleContext/output/path"); final PathFragment dynamicOutputPath = PathFragment.create("dummyRuleContext/output/path.so"); final Artifact staticOutputFile = getBinArtifactWithNoOwner(exeOutputPath.getPathString()); final Artifact dynamicOutputFile = getBinArtifactWithNoOwner(dynamicOutputPath.getPathString()); final Artifact oFile = getSourceArtifact("cc/a.o"); final Artifact oFile2 = getSourceArtifact("cc/a2.o"); final FeatureConfiguration featureConfiguration = getMockFeatureConfiguration(); ActionTester.runTest( NonStaticAttributes.class, new ActionCombinationFactory<NonStaticAttributes>() { @Override public Action generate(ImmutableSet<NonStaticAttributes> attributesToFlip) throws InterruptedException { CppLinkActionBuilder builder = new CppLinkActionBuilder( ruleContext, attributesToFlip.contains(NonStaticAttributes.OUTPUT_FILE) ? dynamicOutputFile : staticOutputFile, CppHelper.getToolchain(ruleContext, ":cc_toolchain"), CppHelper.getFdoSupport(ruleContext, ":cc_toolchain")) {}; builder.addCompilationInputs( attributesToFlip.contains(NonStaticAttributes.COMPILATION_INPUTS) ? ImmutableList.of(oFile) : ImmutableList.of(oFile2)); if (attributesToFlip.contains(NonStaticAttributes.OUTPUT_FILE)) { builder.setLinkType(LinkTargetType.DYNAMIC_LIBRARY); builder.setLibraryIdentifier("foo"); } else { builder.setLinkType(LinkTargetType.EXECUTABLE); } builder.setLinkStaticness(LinkStaticness.DYNAMIC); builder.setNativeDeps(attributesToFlip.contains(NonStaticAttributes.NATIVE_DEPS)); builder.setUseTestOnlyFlags( attributesToFlip.contains(NonStaticAttributes.USE_TEST_ONLY_FLAGS)); builder.setFake(attributesToFlip.contains(NonStaticAttributes.FAKE)); builder.setRuntimeSolibDir( attributesToFlip.contains(NonStaticAttributes.RUNTIME_SOLIB_DIR) ? null : PathFragment.create("so1")); builder.setFeatureConfiguration(featureConfiguration); return builder.build(); } }); } private enum StaticKeyAttributes { OUTPUT_FILE, COMPILATION_INPUTS } /** * This mainly checks that static library links don't have identical keys, and it also compares * them with simple dynamic library links. */ @Test public void testComputeKeyStatic() throws Exception { final RuleContext ruleContext = createDummyRuleContext(); final PathFragment staticOutputPath = PathFragment.create("dummyRuleContext/output/path.a"); final PathFragment dynamicOutputPath = PathFragment.create("dummyRuleContext/output/path.so"); final Artifact staticOutputFile = getBinArtifactWithNoOwner(staticOutputPath.getPathString()); final Artifact dynamicOutputFile = getBinArtifactWithNoOwner(dynamicOutputPath.getPathString()); final Artifact oFile = getSourceArtifact("cc/a.o"); final Artifact oFile2 = getSourceArtifact("cc/a2.o"); final FeatureConfiguration featureConfiguration = getMockFeatureConfiguration(); ActionTester.runTest( StaticKeyAttributes.class, new ActionCombinationFactory<StaticKeyAttributes>() { @Override public Action generate(ImmutableSet<StaticKeyAttributes> attributes) throws InterruptedException { CppLinkActionBuilder builder = new CppLinkActionBuilder( ruleContext, attributes.contains(StaticKeyAttributes.OUTPUT_FILE) ? staticOutputFile : dynamicOutputFile, CppHelper.getToolchain(ruleContext, ":cc_toolchain"), CppHelper.getFdoSupport(ruleContext, ":cc_toolchain")) {}; builder.addCompilationInputs( attributes.contains(StaticKeyAttributes.COMPILATION_INPUTS) ? ImmutableList.of(oFile) : ImmutableList.of(oFile2)); builder.setLinkType( attributes.contains(StaticKeyAttributes.OUTPUT_FILE) ? LinkTargetType.STATIC_LIBRARY : LinkTargetType.DYNAMIC_LIBRARY); builder.setLibraryIdentifier("foo"); builder.setFeatureConfiguration(featureConfiguration); return builder.build(); } }); } @Test public void testCommandLineSplitting() throws Exception { RuleContext ruleContext = createDummyRuleContext(); Artifact output = getDerivedArtifact( PathFragment.create("output/path.xyz"), getTargetConfiguration().getBinDirectory( RepositoryName.MAIN), ActionsTestUtil.NULL_ARTIFACT_OWNER); final Artifact outputIfso = getDerivedArtifact( PathFragment.create("output/path.ifso"), getTargetConfiguration().getBinDirectory( RepositoryName.MAIN), ActionsTestUtil.NULL_ARTIFACT_OWNER); CppLinkActionBuilder builder = new CppLinkActionBuilder( ruleContext, output, CppHelper.getToolchain(ruleContext, ":cc_toolchain"), CppHelper.getFdoSupport(ruleContext, ":cc_toolchain")); builder.setLinkType(LinkTargetType.STATIC_LIBRARY); assertTrue(builder.canSplitCommandLine()); builder.setLinkType(LinkTargetType.DYNAMIC_LIBRARY); assertTrue(builder.canSplitCommandLine()); builder.setInterfaceOutput(outputIfso); assertFalse(builder.canSplitCommandLine()); builder.setInterfaceOutput(null); builder.setLinkType(LinkTargetType.INTERFACE_DYNAMIC_LIBRARY); assertFalse(builder.canSplitCommandLine()); } /** * Links a small target. * Checks that resource estimates are above the minimum and scale correctly. */ @Test public void testSmallLocalLinkResourceEstimate() throws Exception { assertLinkSizeAccuracy(3); } /** * Fake links a large target. * Checks that resource estimates are above the minimum and scale correctly. * The actual link action is irrelevant; we are just checking the estimate. */ @Test public void testLargeLocalLinkResourceEstimate() throws Exception { assertLinkSizeAccuracy(7000); } private void assertLinkSizeAccuracy(int inputs) throws Exception { ImmutableList.Builder<Artifact> objects = ImmutableList.builder(); for (int i = 0; i < inputs; i++) { objects.add(getOutputArtifact("object" + i + ".o")); } CppLinkAction linkAction = createLinkBuilder( Link.LinkTargetType.EXECUTABLE, "dummyRuleContext/binary2", objects.build(), ImmutableList.<LibraryToLink>of(), new FeatureConfiguration()) .setFake(true) .build(); // Ensure that minima are enforced. ResourceSet resources = linkAction.estimateResourceConsumptionLocal(); assertTrue(resources.getMemoryMb() >= CppLinkAction.MIN_STATIC_LINK_RESOURCES.getMemoryMb()); assertTrue(resources.getCpuUsage() >= CppLinkAction.MIN_STATIC_LINK_RESOURCES.getCpuUsage()); assertTrue(resources.getIoUsage() >= CppLinkAction.MIN_STATIC_LINK_RESOURCES.getIoUsage()); final int linkSize = Iterables.size(linkAction.getLinkCommandLine().getLinkerInputs()); ResourceSet scaledSet = ResourceSet.createWithRamCpuIo( CppLinkAction.LINK_RESOURCES_PER_INPUT.getMemoryMb() * linkSize, CppLinkAction.LINK_RESOURCES_PER_INPUT.getCpuUsage() * linkSize, CppLinkAction.LINK_RESOURCES_PER_INPUT.getIoUsage() * linkSize ); // Ensure that anything above the minimum is properly scaled. assertTrue(resources.getMemoryMb() == CppLinkAction.MIN_STATIC_LINK_RESOURCES.getMemoryMb() || resources.getMemoryMb() == scaledSet.getMemoryMb()); assertTrue(resources.getCpuUsage() == CppLinkAction.MIN_STATIC_LINK_RESOURCES.getCpuUsage() || resources.getCpuUsage() == scaledSet.getCpuUsage()); assertTrue(resources.getIoUsage() == CppLinkAction.MIN_STATIC_LINK_RESOURCES.getIoUsage() || resources.getIoUsage() == scaledSet.getIoUsage()); } private CppLinkActionBuilder createLinkBuilder( Link.LinkTargetType type, String outputPath, Iterable<Artifact> nonLibraryInputs, ImmutableList<LibraryToLink> libraryInputs, FeatureConfiguration featureConfiguration) throws Exception { RuleContext ruleContext = createDummyRuleContext(); CppLinkActionBuilder builder = new CppLinkActionBuilder( ruleContext, new Artifact( PathFragment.create(outputPath), getTargetConfiguration() .getBinDirectory(ruleContext.getRule().getRepository())), ruleContext.getConfiguration(), CppHelper.getToolchain(ruleContext, ":cc_toolchain"), CppHelper.getFdoSupport(ruleContext, ":cc_toolchain")) .addObjectFiles(nonLibraryInputs) .addLibraries(NestedSetBuilder.wrap(Order.LINK_ORDER, libraryInputs)) .setLinkType(type) .setCrosstoolInputs(NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)) .setLinkStaticness( type.staticness() == Staticness.STATIC ? LinkStaticness.FULLY_STATIC : LinkStaticness.MOSTLY_STATIC) .setFeatureConfiguration(featureConfiguration); return builder; } private CppLinkActionBuilder createLinkBuilder(Link.LinkTargetType type) throws Exception { PathFragment output = PathFragment.create("dummyRuleContext/output/path.a"); return createLinkBuilder( type, output.getPathString(), ImmutableList.<Artifact>of(), ImmutableList.<LibraryToLink>of(), new FeatureConfiguration()); } public Artifact getOutputArtifact(String relpath) { return new Artifact( getTargetConfiguration().getBinDirectory(RepositoryName.MAIN).getPath() .getRelative(relpath), getTargetConfiguration().getBinDirectory(RepositoryName.MAIN), getTargetConfiguration().getBinFragment().getRelative(relpath)); } private Artifact scratchArtifact(String s) { try { return new Artifact( scratch.overwriteFile(outputBase.getRelative("WORKSPACE").getRelative(s).toString()), Root.asDerivedRoot(scratch.dir(outputBase.getRelative("WORKSPACE").toString()))); } catch (IOException e) { throw new RuntimeException(e); } } private static void assertError(String expectedSubstring, CppLinkActionBuilder builder) throws InterruptedException { try { builder.build(); fail(); } catch (RuntimeException e) { assertThat(e.getMessage()).contains(expectedSubstring); } } @Test public void testInterfaceOutputWithoutBuildingDynamicLibraryIsError() throws Exception { CppLinkActionBuilder builder = createLinkBuilder(LinkTargetType.EXECUTABLE) .setInterfaceOutput(scratchArtifact("FakeInterfaceOutput")); assertError("Interface output can only be used with non-fake DYNAMIC_LIBRARY targets", builder); } @Test public void testInterfaceOutputForDynamicLibrary() throws Exception { FeatureConfiguration featureConfiguration = CcToolchainFeaturesTest.buildFeatures( "feature {", " name: 'build_interface_libraries'", " flag_set {", " action: '" + LinkTargetType.DYNAMIC_LIBRARY.getActionName() + "',", " flag_group {", " flag: '%{generate_interface_library}'", " flag: '%{interface_library_builder_path}'", " flag: '%{interface_library_input_path}'", " flag: '%{interface_library_output_path}'", " }", " }", "}", "feature {", " name: 'dynamic_library_linker_tool'", " flag_set {", " action: 'c++-link-dynamic-library'", " flag_group {", " flag: 'dynamic_library_linker_tool'", " }", " }", "}", "feature {", " name: 'has_configured_linker_path'", "}", "action_config {", " config_name: '" + LinkTargetType.DYNAMIC_LIBRARY.getActionName() + "'", " action_name: '" + LinkTargetType.DYNAMIC_LIBRARY.getActionName() + "'", " tool {", " tool_path: 'custom/crosstool/scripts/link_dynamic_library.sh'", " }", " implies: 'has_configured_linker_path'", " implies: 'build_interface_libraries'", " implies: 'dynamic_library_linker_tool'", "}") .getFeatureConfiguration( "build_interface_libraries", "dynamic_library_linker_tool", LinkTargetType.DYNAMIC_LIBRARY.getActionName()); CppLinkActionBuilder builder = createLinkBuilder( LinkTargetType.DYNAMIC_LIBRARY, "foo.so", ImmutableList.<Artifact>of(), ImmutableList.<LibraryToLink>of(), featureConfiguration) .setLibraryIdentifier("foo") .setInterfaceOutput(scratchArtifact("FakeInterfaceOutput.ifso")); List<String> commandLine = builder.build().getCommandLine(); assertThat(commandLine).hasSize(6); assertThat(commandLine.get(0)).endsWith("custom/crosstool/scripts/link_dynamic_library.sh"); assertThat(commandLine.get(1)).isEqualTo("yes"); assertThat(commandLine.get(2)).endsWith("tools/cpp/build_interface_so"); assertThat(commandLine.get(3)).endsWith("foo.so"); assertThat(commandLine.get(4)).isEqualTo("FakeInterfaceOutput.ifso"); assertThat(commandLine.get(5)).isEqualTo("dynamic_library_linker_tool"); } @Test public void testStaticLinkWithDynamicIsError() throws Exception { CppLinkActionBuilder builder = createLinkBuilder(LinkTargetType.STATIC_LIBRARY) .setLinkStaticness(LinkStaticness.DYNAMIC) .setLibraryIdentifier("foo"); assertError("static library link must be static", builder); } @Test public void testStaticLinkWithSymbolsCountOutputIsError() throws Exception { CppLinkActionBuilder builder = createLinkBuilder(LinkTargetType.STATIC_LIBRARY) .setLinkStaticness(LinkStaticness.FULLY_STATIC) .setLibraryIdentifier("foo") .setSymbolCountsOutput(scratchArtifact("dummySymbolCounts")); assertError("the symbol counts output must be null for static links", builder); } @Test public void testStaticLinkWithNativeDepsIsError() throws Exception { CppLinkActionBuilder builder = createLinkBuilder(LinkTargetType.STATIC_LIBRARY) .setLinkStaticness(LinkStaticness.FULLY_STATIC) .setLibraryIdentifier("foo") .setNativeDeps(true); assertError("the native deps flag must be false for static links", builder); } @Test public void testStaticLinkWithWholeArchiveIsError() throws Exception { CppLinkActionBuilder builder = createLinkBuilder(LinkTargetType.STATIC_LIBRARY) .setLinkStaticness(LinkStaticness.FULLY_STATIC) .setLibraryIdentifier("foo") .setWholeArchive(true); assertError("the need whole archive flag must be false for static links", builder); } }