// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.cpp; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.baseArtifactNames; import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.baseNamesOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; 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.Action; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.util.ActionsTestUtil; import com.google.devtools.build.lib.analysis.AnalysisUtils; import com.google.devtools.build.lib.analysis.BaseRuleClasses; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.OutputGroupProvider; import com.google.devtools.build.lib.analysis.RuleDefinition; import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.ConfigurationFactory; import com.google.devtools.build.lib.analysis.mock.BazelAnalysisMock; import com.google.devtools.build.lib.analysis.util.AnalysisMock; import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; import com.google.devtools.build.lib.bazel.rules.BazelRuleClassProvider; import com.google.devtools.build.lib.bazel.rules.BazelToolchainType; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.flags.InvocationPolicyEnforcer; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.rules.ToolchainType; import com.google.devtools.build.lib.testutil.MoreAsserts; import com.google.devtools.build.lib.util.FileType; import com.google.devtools.build.lib.util.OsUtils; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.ModifiedFileSet; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig; import java.util.Arrays; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** A test for {@link CcCommon}. */ @RunWith(JUnit4.class) public class CcCommonTest extends BuildViewTestCase { private static final String STATIC_LIB = "statically/libstatically.a"; @Before public final void createBuildFiles() throws Exception { // Having lots of setUp code leads to bad running time. Don't add anything here! scratch.file("empty/BUILD", "cc_library(name = 'emptylib')", "cc_binary(name = 'emptybinary')"); scratch.file("foo/BUILD", "cc_library(name = 'foo',", " srcs = ['foo.cc'])"); scratch.file("bar/BUILD", "cc_library(name = 'bar',", " srcs = ['bar.cc'])"); } @Test public void testSameCcFileTwice() throws Exception { scratch.file( "a/BUILD", "cc_library(name='a', srcs=['a1', 'a2'])", "filegroup(name='a1', srcs=['a.cc'])", "filegroup(name='a2', srcs=['a.cc'])"); reporter.removeHandler(failFastHandler); getConfiguredTarget("//a:a"); assertContainsEvent("Artifact 'a/a.cc' is duplicated"); } @Test public void testSameHeaderFileTwice() throws Exception { scratch.file( "a/BUILD", "package(features=['parse_headers'])", "cc_library(name='a', srcs=['a1', 'a2', 'a.cc'])", "filegroup(name='a1', srcs=['a.h'])", "filegroup(name='a2', srcs=['a.h'])"); reporter.removeHandler(failFastHandler); getConfiguredTarget("//a:a"); assertNoEvents(); } @Test public void testEmptyLibrary() throws Exception { ConfiguredTarget emptylib = getConfiguredTarget("//empty:emptylib"); // We create .a for empty libraries, for simplicity (in Blaze). // But we avoid creating .so files for empty libraries, // because those have a potentially significant run-time startup cost. if (emptyShouldOutputStaticLibrary()) { assertEquals("libemptylib.a", baseNamesOf(getFilesToBuild(emptylib))); } else { assertThat(getFilesToBuild(emptylib)).isEmpty(); } assertTrue( emptylib .getProvider(CcExecutionDynamicLibrariesProvider.class) .getExecutionDynamicLibraryArtifacts() .isEmpty()); } protected boolean emptyShouldOutputStaticLibrary() { return !getAnalysisMock().isThisBazel(); } @Test public void testEmptyBinary() throws Exception { ConfiguredTarget emptybin = getConfiguredTarget("//empty:emptybinary"); assertEquals( "emptybinary" + OsUtils.executableExtension(), baseNamesOf(getFilesToBuild(emptybin))); } private List<String> getCopts(String target) throws Exception { ConfiguredTarget cLib = getConfiguredTarget(target); Artifact object = getOnlyElement(getOutputGroup(cLib, OutputGroupProvider.FILES_TO_COMPILE)); CppCompileAction compileAction = (CppCompileAction) getGeneratingAction(object); return compileAction.getCompilerOptions(); } @Test public void testCopts() throws Exception { scratch.file( "copts/BUILD", "cc_library(name = 'c_lib',", " srcs = ['foo.cc'],", " copts = [ '-Wmy-warning', '-frun-faster' ])"); MoreAsserts.assertContainsSublist(getCopts("//copts:c_lib"), "-Wmy-warning", "-frun-faster"); } @Test public void testCoptsTokenization() throws Exception { scratch.file( "copts/BUILD", "cc_library(name = 'c_lib',", " srcs = ['foo.cc'],", " copts = ['-Wmy-warning -frun-faster'])"); List<String> copts = getCopts("//copts:c_lib"); MoreAsserts.assertContainsSublist(copts, "-Wmy-warning", "-frun-faster"); assertContainsEvent("each item in the list should contain only one option"); } @Test public void testCoptsNoTokenization() throws Exception { scratch.file( "copts/BUILD", "package(features = ['no_copts_tokenization'])", "cc_library(name = 'c_lib',", " srcs = ['foo.cc'],", " copts = ['-Wmy-warning -frun-faster'])"); List<String> copts = getCopts("//copts:c_lib"); MoreAsserts.assertContainsSublist(copts, "-Wmy-warning -frun-faster"); } /** * Test that we handle ".a" files in cc_library srcs correctly when linking dynamically. In * particular, if srcs contains only the ".a" file for a library, with no corresponding ".so", * then we need to link in the ".a" file even when we're linking dynamically. If srcs contains * both ".a" and ".so" then we should only link in the ".so". */ @Test public void testArchiveInCcLibrarySrcs() throws Exception { useConfiguration("--cpu=k8"); ConfiguredTarget archiveInSrcsTest = scratchConfiguredTarget( "archive_in_srcs", "archive_in_srcs_test", "cc_test(name = 'archive_in_srcs_test',", " srcs = ['archive_in_srcs_test.cc'],", " deps = [':archive_in_srcs_lib'])", "cc_library(name = 'archive_in_srcs_lib',", " srcs = ['libstatic.a', 'libboth.a', 'libboth.so'])"); List<String> artifactNames = baseArtifactNames(getLinkerInputs(archiveInSrcsTest)); assertThat(artifactNames).containsAllOf("libboth.so", "libstatic.a"); assertThat(artifactNames).doesNotContain("libboth.a"); } private Iterable<Artifact> getLinkerInputs(ConfiguredTarget target) { Artifact executable = getExecutable(target); CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(executable); return LinkerInputs.toLibraryArtifacts(linkAction.getLinkCommandLine().getLinkerInputs()); } @Test public void testDylibLibrarySuffixIsStripped() throws Exception { ConfiguredTarget archiveInSrcsTest = scratchConfiguredTarget( "archive_in_src_darwin", "archive_in_srcs", "cc_binary(name = 'archive_in_srcs',", " srcs = ['libarchive.34.dylib'])"); Artifact executable = getExecutable(archiveInSrcsTest); CppLinkAction linkAction = (CppLinkAction) getGeneratingAction(executable); assertThat(linkAction.getLinkCommandLine().toString()).contains(" -larchive.34 "); } @Test public void testLinkStaticStatically() throws Exception { ConfiguredTarget statically = scratchConfiguredTarget( "statically", "statically", "cc_library(name = 'statically',", " srcs = ['statically.cc'],", " linkstatic=1)"); assertTrue( statically .getProvider(CcExecutionDynamicLibrariesProvider.class) .getExecutionDynamicLibraryArtifacts() .isEmpty()); Artifact staticallyDotA = getOnlyElement(getFilesToBuild(statically)); assertThat(getGeneratingAction(staticallyDotA)).isInstanceOf(CppLinkAction.class); PathFragment dotAPath = staticallyDotA.getExecPath(); assertThat(dotAPath.getPathString()).endsWith(STATIC_LIB); } @Test public void testIsolatedDefines() throws Exception { ConfiguredTarget isolatedDefines = scratchConfiguredTarget( "isolated_defines", "defineslib", "cc_library(name = 'defineslib',", " srcs = ['defines.cc'],", " defines = ['FOO', 'BAR'])"); assertThat(isolatedDefines.getProvider(CppCompilationContext.class).getDefines()) .containsExactly("FOO", "BAR") .inOrder(); } @Test public void testStartEndLib() throws Exception { getAnalysisMock().ccSupport().setupCrosstool(mockToolsConfig, CrosstoolConfig.CToolchain.newBuilder().setSupportsStartEndLib(true).buildPartial()); useConfiguration( // Prevent Android from trying to setup ARM crosstool by forcing it on system cpu. "--fat_apk_cpu=" + CrosstoolConfigurationHelper.defaultCpu(), "--start_end_lib"); scratch.file( "test/BUILD", "cc_library(name='lib',", " srcs=['lib.c'])", "cc_binary(name='bin',", " srcs=['bin.c'])"); ConfiguredTarget target = getConfiguredTarget("//test:bin"); CppLinkAction action = (CppLinkAction) getGeneratingAction(getExecutable(target)); for (Artifact input : action.getInputs()) { String name = input.getFilename(); assertTrue(!CppFileTypes.ARCHIVE.matches(name) && !CppFileTypes.PIC_ARCHIVE.matches(name)); } } @Test public void testTempsWithDifferentExtensions() throws Exception { useConfiguration("--cpu=k8", "--save_temps"); scratch.file( "ananas/BUILD", "cc_library(name='ananas',", " srcs=['1.c', '2.cc', '3.cpp', '4.S', '5.h', '6.hpp'])"); ConfiguredTarget ananas = getConfiguredTarget("//ananas:ananas"); Iterable<String> temps = ActionsTestUtil.baseArtifactNames(getOutputGroup(ananas, OutputGroupProvider.TEMP_FILES)); assertThat(temps) .containsExactly( "1.pic.i", "1.pic.s", "2.pic.ii", "2.pic.s", "3.pic.ii", "3.pic.s"); } @Test public void testTempsForCc() throws Exception { for (String cpu : new String[] {"k8", "piii"}) { useConfiguration("--cpu=" + cpu, "--save_temps"); ConfiguredTarget foo = getConfiguredTarget("//foo:foo"); List<String> temps = ActionsTestUtil.baseArtifactNames(getOutputGroup(foo, OutputGroupProvider.TEMP_FILES)); if (getTargetConfiguration().getFragment(CppConfiguration.class).usePicForBinaries()) { assertThat(temps).named(cpu).containsExactly("foo.pic.ii", "foo.pic.s"); } else { assertThat(temps).named(cpu).containsExactly("foo.ii", "foo.s"); } } } @Test public void testTempsForC() throws Exception { scratch.file("csrc/BUILD", "cc_library(name='csrc', srcs=['foo.c'])"); for (String cpu : new String[] {"k8", "piii"}) { useConfiguration("--cpu=" + cpu, "--save_temps"); // Now try with a .c source file. ConfiguredTarget csrc = getConfiguredTarget("//csrc:csrc"); List<String> temps = ActionsTestUtil.baseArtifactNames(getOutputGroup(csrc, OutputGroupProvider.TEMP_FILES)); if (getTargetConfiguration().getFragment(CppConfiguration.class).usePicForBinaries()) { assertThat(temps).named(cpu).containsExactly("foo.pic.i", "foo.pic.s"); } else { assertThat(temps).named(cpu).containsExactly("foo.i", "foo.s"); } } } @Test public void testAlwaysLinkYieldsLo() throws Exception { ConfiguredTarget alwaysLink = scratchConfiguredTarget( "always_link", "always_link", "cc_library(name = 'always_link',", " alwayslink = 1,", " srcs = ['always_link.cc'])"); assertThat(baseNamesOf(getFilesToBuild(alwaysLink))).contains("libalways_link.lo"); } /** * Tests that nocopts= "-fPIC" takes '-fPIC' out of a compile invocation even if the crosstool * requires fPIC compilation (i.e. nocopts overrides crosstool settings on a rule-specific * basis). */ @Test public void testNoCoptfPicOverride() throws Exception { getAnalysisMock().ccSupport().setupCrosstool(mockToolsConfig, CrosstoolConfig.CToolchain.newBuilder().setNeedsPic(true).buildPartial()); useConfiguration( // Prevent Android from trying to setup ARM crosstool by forcing it on system cpu. "--fat_apk_cpu=" + CrosstoolConfigurationHelper.defaultCpu()); scratch.file( "a/BUILD", "cc_binary(name = 'pic',", " srcs = [ 'binary.cc' ])", "cc_binary(name = 'libpic.so',", " srcs = [ 'binary.cc' ])", "cc_library(name = 'piclib',", " srcs = [ 'library.cc' ])", "cc_binary(name = 'nopic',", " srcs = [ 'binary.cc' ],", " nocopts = '-fPIC')", "cc_binary(name = 'libnopic.so',", " srcs = [ 'binary.cc' ],", " nocopts = '-fPIC')", "cc_library(name = 'nopiclib',", " srcs = [ 'library.cc' ],", " nocopts = '-fPIC')"); assertThat(getCppCompileAction("//a:pic").getArgv()).contains("-fPIC"); assertThat(getCppCompileAction("//a:libpic.so").getArgv()).contains("-fPIC"); assertThat(getCppCompileAction("//a:piclib").getArgv()).contains("-fPIC"); assertThat(getCppCompileAction("//a:nopic").getArgv()).doesNotContain("-fPIC"); assertThat(getCppCompileAction("//a:libnopic.so").getArgv()).doesNotContain("-fPIC"); assertThat(getCppCompileAction("//a:nopiclib").getArgv()).doesNotContain("-fPIC"); } @Test public void testPicModeAssembly() throws Exception { useConfiguration("--cpu=k8"); scratch.file("a/BUILD", "cc_library(name='preprocess', srcs=['preprocess.S'])"); List<String> argv = getCppCompileAction("//a:preprocess").getArgv(); assertThat(argv).contains("-fPIC"); } private CppCompileAction getCppCompileAction(String label) throws Exception { ConfiguredTarget target = getConfiguredTarget(label); List<CppCompileAction> compilationSteps = actionsTestUtil() .findTransitivePrerequisitesOf( getFilesToBuild(target).iterator().next(), CppCompileAction.class); return compilationSteps.get(0); } @Test public void testIsolatedIncludes() throws Exception { // Tests the (immediate) effect of declaring the includes attribute on a // cc_library. scratch.file( "bang/BUILD", "cc_library(name = 'bang',", " srcs = ['bang.cc'],", " includes = ['bang_includes'])"); ConfiguredTarget foo = getConfiguredTarget("//bang:bang"); String includesRoot = "bang/bang_includes"; assertThat(foo.getProvider(CppCompilationContext.class).getSystemIncludeDirs()) .containsAllOf( PathFragment.create(includesRoot), targetConfig.getGenfilesFragment().getRelative(includesRoot)); } @Test public void testUseIsystemForIncludes() throws Exception { // Tests the effect of --use_isystem_for_includes. scratch.file( "no_includes/BUILD", "cc_library(name = 'no_includes',", " srcs = ['no_includes.cc'])"); ConfiguredTarget noIncludes = getConfiguredTarget("//no_includes:no_includes"); scratch.file( "bang/BUILD", "cc_library(name = 'bang',", " srcs = ['bang.cc'],", " includes = ['bang_includes'])"); ConfiguredTarget foo = getConfiguredTarget("//bang:bang"); String includesRoot = "bang/bang_includes"; List<PathFragment> expected = new ImmutableList.Builder<PathFragment>() .addAll(noIncludes.getProvider(CppCompilationContext.class).getSystemIncludeDirs()) .add(PathFragment.create(includesRoot)) .add(targetConfig.getGenfilesFragment().getRelative(includesRoot)) .build(); assertThat(foo.getProvider(CppCompilationContext.class).getSystemIncludeDirs()) .containsExactlyElementsIn(expected); } @Test public void testCcTestDisallowsAlwaysLink() throws Exception { scratch.file( "cc/common/BUILD", "cc_library(name = 'lib1',", " srcs = ['foo1.cc'],", " deps = ['//left'])", "", "cc_test(name = 'testlib',", " deps = [':lib1'],", " alwayslink=1)"); reporter.removeHandler(failFastHandler); getPackageManager().getPackage(reporter, PackageIdentifier.createInMainRepo("cc/common")); assertContainsEvent( "//cc/common:testlib: no such attribute 'alwayslink'" + " in 'cc_test' rule"); } @Test public void testCcTestBuiltWithFissionHasDwp() throws Exception { // Tests that cc_tests built statically and with Fission will have the .dwp file // in their runfiles. useConfiguration("--cpu=k8", "--build_test_dwp", "--dynamic_mode=off", "--fission=yes"); ConfiguredTarget target = scratchConfiguredTarget( "mypackage", "mytest", "cc_test(name = 'mytest', ", " srcs = ['mytest.cc'])"); Iterable<Artifact> runfiles = collectRunfiles(target); assertThat(baseArtifactNames(runfiles)).contains("mytest.dwp"); } @Test public void testCcLibraryBadIncludesWarnedAndIgnored() throws Exception { checkWarning( "badincludes", "flaky_lib", // message: "in includes attribute of cc_library rule //badincludes:flaky_lib: " + "ignoring invalid absolute path '//third_party/procps/proc'", // build file: "cc_library(name = 'flaky_lib',", " srcs = [ 'ok.cc' ],", " includes = [ '//third_party/procps/proc' ])"); } @Test public void testCcLibraryUplevelIncludesWarned() throws Exception { checkWarning( "third_party/uplevel", "lib", // message: "in includes attribute of cc_library rule //third_party/uplevel:lib: '../bar' resolves to " + "'third_party/bar' not below the relative path of its package 'third_party/uplevel'. " + "This will be an error in the future", // build file: "licenses(['unencumbered'])", "cc_library(name = 'lib',", " srcs = ['foo.cc'],", " includes = ['../bar'])"); } @Test public void testCcLibraryNonThirdPartyIncludesWarned() throws Exception { if (getAnalysisMock().isThisBazel()) { return; } checkWarning( "topdir", "lib", // message: "in includes attribute of cc_library rule //topdir:lib: './' resolves to 'topdir' not " + "in 'third_party'. This will be an error in the future", // build file: "cc_library(name = 'lib',", " srcs = ['foo.cc'],", " includes = ['./'])"); } @Test public void testCcLibraryThirdPartyIncludesNotWarned() throws Exception { eventCollector.clear(); ConfiguredTarget target = scratchConfiguredTarget( "third_party/pkg", "lib", "licenses(['unencumbered'])", "cc_library(name = 'lib',", " srcs = ['foo.cc'],", " includes = ['./'])"); assertThat(view.hasErrors(target)).isFalse(); assertNoEvents(); } @Test public void testCcLibraryExternalIncludesNotWarned() throws Exception { eventCollector.clear(); FileSystemUtils.appendIsoLatin1( scratch.resolve("WORKSPACE"), "local_repository(", " name = 'pkg',", " path = '/foo')"); getSkyframeExecutor() .invalidateFilesUnderPathForTesting( reporter, new ModifiedFileSet.Builder().modify(PathFragment.create("WORKSPACE")).build(), rootDirectory); FileSystemUtils.createDirectoryAndParents(scratch.resolve("/foo/bar")); scratch.file("/foo/WORKSPACE", "workspace(name = 'pkg')"); scratch.file( "/foo/bar/BUILD", "cc_library(name = 'lib',", " srcs = ['foo.cc'],", " includes = ['./'])"); Label label = Label.parseAbsolute("@pkg//bar:lib"); ConfiguredTarget target = view.getConfiguredTargetForTesting(reporter, label, targetConfig); assertThat(view.hasErrors(target)).isFalse(); assertNoEvents(); } @Test public void testCcLibraryRootIncludesError() throws Exception { checkError( "third_party/root", "lib", // message: "in includes attribute of cc_library rule //third_party/root:lib: '../..' resolves to the " + "workspace root, which would allow this rule and all of its transitive dependents to " + "include any file in your workspace. Please include only what you need", // build file: "licenses(['unencumbered'])", "cc_library(name = 'lib',", " srcs = ['foo.cc'],", " includes = ['../..'])"); } @Test public void testStaticallyLinkedBinaryNeedsSharedObject() throws Exception { scratch.file( "third_party/sophos_av_pua/BUILD", "licenses(['notice'])", "cc_library(name = 'savi',", " srcs = [ 'lib/libsavi.so' ])"); ConfiguredTarget wrapsophos = scratchConfiguredTarget( "quality/malware/support", "wrapsophos", "cc_library(name = 'sophosengine',", " srcs = [ 'sophosengine.cc' ],", " deps = [ '//third_party/sophos_av_pua:savi' ])", "cc_binary(name = 'wrapsophos',", " srcs = [ 'wrapsophos.cc' ],", " deps = [ ':sophosengine' ],", " linkstatic=1)"); List<String> artifactNames = baseArtifactNames(getLinkerInputs(wrapsophos)); assertThat(artifactNames).contains("libsavi.so"); } @Test public void testExpandLabelInLinkoptsAgainstSrc() throws Exception { scratch.file( "coolthing/BUILD", "genrule(name = 'build-that',", " srcs = [ 'foo' ],", " outs = [ 'nicelib.a' ],", " cmd = 'cat $< > $@')"); // In reality the linkopts might contain several externally-provided // '.a' files with cyclic dependencies amongst them, but in this test // it suffices to show that one label in linkopts was resolved. scratch.file( "myapp/BUILD", "cc_binary(name = 'myapp',", " srcs = [ '//coolthing:nicelib.a' ],", " linkopts = [ '//coolthing:nicelib.a' ])"); ConfiguredTarget theLib = getConfiguredTarget("//coolthing:build-that"); ConfiguredTarget theApp = getConfiguredTarget("//myapp:myapp"); // make sure we did not print warnings about the linkopt assertNoEvents(); // make sure the binary is dependent on the static lib Action linkAction = getGeneratingAction(getOnlyElement(getFilesToBuild(theApp))); ImmutableList<Artifact> filesToBuild = ImmutableList.copyOf(getFilesToBuild(theLib)); assertTrue(ImmutableSet.copyOf(linkAction.getInputs()).containsAll(filesToBuild)); } @Test public void testMissingLabelInLinkopts() throws Exception { scratch.file( "linklow/BUILD", "genrule(name = 'linklow_linker_script',", " srcs = [ 'default_linker_script' ],", " tools = [ 'default_linker_script' ],", " outs = [ 'linklow.lds' ],", " cmd = 'cat $< > $@')"); checkError( "ocean/scoring2", "ms-ascorer", // error: "could not resolve label '//linklow:linklow_linker_script'", "cc_binary(name = 'ms-ascorer',", " srcs = [ ],", " deps = [ ':ascorer-servlet'],", " linkopts = [ '-static', '-Xlinker', '-script', '//linklow:linklow_linker_script'])", "cc_library(name = 'ascorer-servlet')"); } @Test public void testCcLibraryWithDashStatic() throws Exception { checkWarning( "badlib", "lib_with_dash_static", // message: "in linkopts attribute of cc_library rule //badlib:lib_with_dash_static: " + "Using '-static' here won't work. Did you mean to use 'linkstatic=1' instead?", // build file: "cc_library(name = 'lib_with_dash_static',", " srcs = [ 'ok.cc' ],", " linkopts = [ '-static' ])"); } @Test public void testStampTests() throws Exception { scratch.file( "test/BUILD", "cc_test(name ='a', srcs = ['a.cc'])", "cc_test(name ='b', srcs = ['b.cc'], stamp = 0)", "cc_test(name ='c', srcs = ['c.cc'], stamp = 1)", "cc_binary(name ='d', srcs = ['d.cc'])", "cc_binary(name ='e', srcs = ['e.cc'], stamp = 0)", "cc_binary(name ='f', srcs = ['f.cc'], stamp = 1)"); assertStamping(false, "//test:a"); assertStamping(false, "//test:b"); assertStamping(true, "//test:c"); assertStamping(true, "//test:d"); assertStamping(false, "//test:e"); assertStamping(true, "//test:f"); useConfiguration("--stamp"); assertStamping(false, "//test:a"); assertStamping(false, "//test:b"); assertStamping(true, "//test:c"); assertStamping(true, "//test:d"); assertStamping(false, "//test:e"); assertStamping(true, "//test:f"); useConfiguration("--nostamp"); assertStamping(false, "//test:a"); assertStamping(false, "//test:b"); assertStamping(true, "//test:c"); assertStamping(false, "//test:d"); assertStamping(false, "//test:e"); assertStamping(true, "//test:f"); } private void assertStamping(boolean enabled, String label) throws Exception { assertEquals( enabled, AnalysisUtils.isStampingEnabled(getRuleContext(getConfiguredTarget(label)))); } @Test public void testIncludeRelativeHeadersAboveExecRoot() throws Exception { checkError( "test", "bad_relative_include", "Path references a path above the execution root.", "cc_library(name='bad_relative_include', srcs=[], includes=['../..'])"); } @Test public void testIncludeAbsoluteHeaders() throws Exception { checkWarning( "test", "bad_absolute_include", "ignoring invalid absolute path", "cc_library(name='bad_absolute_include', srcs=[], includes=['/usr/include/'])"); } @Test public void testSelectPreferredLibrariesInvariant() { // All combinations of libraries: // a - static+pic+shared // b - static+pic // c - static+shared // d - static // e - pic+shared // f - pic // g - shared CcLinkingOutputs linkingOutputs = CcLinkingOutputs.builder() .addStaticLibraries( ImmutableList.copyOf( LinkerInputs.opaqueLibrariesToLink(ArtifactCategory.STATIC_LIBRARY, Arrays.asList( getSourceArtifact("liba.a"), getSourceArtifact("libb.a"), getSourceArtifact("libc.a"), getSourceArtifact("libd.a"))))) .addPicStaticLibraries( ImmutableList.copyOf( LinkerInputs.opaqueLibrariesToLink(ArtifactCategory.STATIC_LIBRARY, Arrays.asList( getSourceArtifact("liba.pic.a"), getSourceArtifact("libb.pic.a"), getSourceArtifact("libe.pic.a"), getSourceArtifact("libf.pic.a"))))) .addDynamicLibraries( ImmutableList.copyOf( LinkerInputs.opaqueLibrariesToLink(ArtifactCategory.DYNAMIC_LIBRARY, Arrays.asList( getSourceArtifact("liba.so"), getSourceArtifact("libc.so"), getSourceArtifact("libe.so"), getSourceArtifact("libg.so"))))) .build(); // Whether linkShared is true or false, this should return the identical results. List<Artifact> sharedLibraries1 = FileType.filterList( LinkerInputs.toLibraryArtifacts(linkingOutputs.getPreferredLibraries(true, false)), CppFileTypes.SHARED_LIBRARY); List<Artifact> sharedLibraries2 = FileType.filterList( LinkerInputs.toLibraryArtifacts(linkingOutputs.getPreferredLibraries(true, true)), CppFileTypes.SHARED_LIBRARY); assertEquals(sharedLibraries1, sharedLibraries2); } /** Tests that shared libraries of the form "libfoo.so.1.2" are permitted within "srcs". */ @Test public void testVersionedSharedLibrarySupport() throws Exception { ConfiguredTarget target = scratchConfiguredTarget( "mypackage", "mybinary", "cc_binary(name = 'mybinary',", " srcs = ['mybinary.cc'],", " deps = [':mylib'])", "cc_library(name = 'mylib',", " srcs = ['libshared.so', 'libshared.so.1.1', 'foo.cc'])"); List<String> artifactNames = baseArtifactNames(getLinkerInputs(target)); assertThat(artifactNames).containsAllOf("libshared.so", "libshared.so.1.1"); } @Test public void testNoHeaderInHdrsWarning() throws Exception { checkWarning( "hdrs_filetypes", "foo", "in hdrs attribute of cc_library rule //hdrs_filetypes:foo: file 'foo.a' " + "from target '//hdrs_filetypes:foo.a' is not allowed in hdrs", "cc_library(name = 'foo',", " srcs = [],", " hdrs = ['foo.a'])"); } @Test public void testLibraryInHdrs() throws Exception { scratchConfiguredTarget("a", "a", "cc_library(name='a', srcs=['a.cc'], hdrs=[':b'])", "cc_library(name='b', srcs=['b.cc'])"); } @Test public void testExpandedLinkopts() throws Exception { scratch.file( "a/BUILD", "genrule(name = 'linker', cmd='generate', outs=['a.lds'])", "cc_binary(", " name='bin',", " srcs=['b.cc'],", " linkopts=['-Wl,@$(location a.lds)'],", " deps=['a.lds'])"); ConfiguredTarget target = getConfiguredTarget("//a:bin"); CppLinkAction action = (CppLinkAction) getGeneratingAction(getOnlyElement(getFilesToBuild(target))); assertThat(action.getLinkCommandLine().getLinkopts()).containsExactly( String.format("-Wl,@%s/genfiles/a/a.lds", getTargetConfiguration().getOutputDirectory( RepositoryName.MAIN).getExecPath().getPathString())); } @Test public void testIncludeManglingSmoke() throws Exception { scratch.file( "third_party/a/BUILD", "licenses(['notice'])", "cc_library(name='a', hdrs=['v1/b/c.h'], strip_include_prefix='v1', include_prefix='lib')"); ConfiguredTarget lib = getConfiguredTarget("//third_party/a"); CppCompilationContext context = lib.getProvider(CppCompilationContext.class); assertThat(ActionsTestUtil.prettyArtifactNames(context.getDeclaredIncludeSrcs())) .containsExactly("third_party/a/_virtual_includes/a/lib/b/c.h"); assertThat(context.getIncludeDirs()).containsExactly( getTargetConfiguration().getBinFragment().getRelative("third_party/a/_virtual_includes/a")); } @Test public void testUpLevelReferencesInIncludeMangling() throws Exception { scratch.file( "third_party/a/BUILD", "licenses(['notice'])", "cc_library(name='sip', srcs=['a.h'], strip_include_prefix='a/../b')", "cc_library(name='ip', srcs=['a.h'], include_prefix='a/../b')", "cc_library(name='ipa', srcs=['a.h'], include_prefix='/foo')"); reporter.removeHandler(failFastHandler); getConfiguredTarget("//third_party/a:sip"); assertContainsEvent("should not contain uplevel references"); eventCollector.clear(); getConfiguredTarget("//third_party/a:ip"); assertContainsEvent("should not contain uplevel references"); eventCollector.clear(); getConfiguredTarget("//third_party/a:ipa"); assertContainsEvent("should be a relative path"); } @Test public void testAbsoluteAndRelativeStripPrefix() throws Exception { scratch.file("third_party/a/BUILD", "licenses(['notice'])", "cc_library(name='relative', hdrs=['v1/b.h'], strip_include_prefix='v1')", "cc_library(name='absolute', hdrs=['v1/b.h'], strip_include_prefix='/third_party')"); CppCompilationContext relative = getConfiguredTarget("//third_party/a:relative") .getProvider(CppCompilationContext.class); CppCompilationContext absolute = getConfiguredTarget("//third_party/a:absolute") .getProvider(CppCompilationContext.class); assertThat(ActionsTestUtil.prettyArtifactNames(relative.getDeclaredIncludeSrcs())) .containsExactly("third_party/a/_virtual_includes/relative/b.h"); assertThat(ActionsTestUtil.prettyArtifactNames(absolute.getDeclaredIncludeSrcs())) .containsExactly("third_party/a/_virtual_includes/absolute/a/v1/b.h"); } @Test public void testArtifactNotUnderStripPrefix() throws Exception { scratch.file("third_party/a/BUILD", "licenses(['notice'])", "cc_library(name='a', hdrs=['v1/b.h'], strip_include_prefix='v2')"); reporter.removeHandler(failFastHandler); getConfiguredTarget("//third_party/a:a"); assertContainsEvent( "header 'third_party/a/v1/b.h' is not under the specified strip prefix 'third_party/a/v2'"); } @Test public void testSymlinkActionIsNotRegisteredWhenIncludePrefixDoesntChangePath() throws Exception { scratch.file( "third_party/BUILD", "licenses(['notice'])", "cc_library(name='a', hdrs=['a.h'], include_prefix='third_party')"); CppCompilationContext context = getConfiguredTarget("//third_party:a").getProvider(CppCompilationContext.class); assertThat(ActionsTestUtil.prettyArtifactNames(context.getDeclaredIncludeSrcs())) .doesNotContain("third_party/_virtual_includes/a/third_party/a.h"); } /** * A {@code toolchain_type} rule for testing that only supports C++. */ public static class OnlyCppToolchainType extends ToolchainType { public OnlyCppToolchainType() { super( ImmutableMap.<Label, Class<? extends BuildConfiguration.Fragment>>of(), ImmutableMap.<Label, ImmutableMap<String, String>>of()); } } /** * A {@code toolchain_type} rule for testing that only supports C++. */ public static class OnlyCppToolchainTypeRule implements RuleDefinition { @Override public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { return builder // This means that *every* toolchain_type rule depends on every configuration fragment // that contributes Make variables, regardless of which one it is. .requiresConfigurationFragments(CppConfiguration.class) .removeAttribute("licenses") .removeAttribute("distribs") .build(); } @Override public Metadata getMetadata() { return Metadata.builder() .name("toolchain_type") .factoryClass(BazelToolchainType.class) .ancestors(BaseRuleClasses.BaseRule.class) .build(); } } /** * Tests for the case where there are only C++ rules defined. */ @RunWith(JUnit4.class) public static class OnlyCppRules extends CcCommonTest { @Override protected AnalysisMock getAnalysisMock() { final AnalysisMock original = BazelAnalysisMock.INSTANCE; return new AnalysisMock.Delegate(original) { @Override public ConfigurationFactory createConfigurationFactory() { return new ConfigurationFactory( createRuleClassProvider().getConfigurationCollectionFactory(), createRuleClassProvider().getConfigurationFragments()); } @Override public ConfiguredRuleClassProvider createRuleClassProvider() { ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder(); builder.setToolsRepository("@bazel_tools"); BazelRuleClassProvider.BAZEL_SETUP.init(builder); BazelRuleClassProvider.CORE_RULES.init(builder); BazelRuleClassProvider.CORE_WORKSPACE_RULES.init(builder); BazelRuleClassProvider.GENERIC_RULES.init(builder); BazelRuleClassProvider.CPP_RULES.init(builder); builder.addRuleDefinition(new OnlyCppToolchainTypeRule()); return builder.build(); } @Override public InvocationPolicyEnforcer getInvocationPolicyEnforcer() { return new InvocationPolicyEnforcer(null); } @Override public boolean isThisBazel() { return true; } }; } @Override public void testNoCoptfPicOverride() throws Exception { // Test sets --fat_apk_cpu, which doesn't exist. } @Override public void testStartEndLib() throws Exception { // Test sets --fat_apk_cpu, which doesn't exist. } @Override public void testExpandLabelInLinkoptsAgainstSrc() throws Exception { // genrule now requires JavaConfiguration, so isn't appropriate for OnlyCppRules. } @Override public void testExpandedLinkopts() throws Exception { // genrule now requires JavaConfiguration, so isn't appropriate for OnlyCppRules. } } }