diff options
13 files changed, 727 insertions, 39 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCppRuleClasses.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCppRuleClasses.java index 08e70cfbbc..6ed5a96106 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCppRuleClasses.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCppRuleClasses.java @@ -112,10 +112,7 @@ public class BazelCppRuleClasses { static final String[] DEPS_ALLOWED_RULES = new String[] { - "cc_inc_library", - "cc_library", - "objc_library", - "cc_proto_library", + "cc_inc_library", "cc_library", "objc_library", "cc_proto_library", "cc_import", }; /** diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcImport.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcImport.java index abfe81ce26..b9173e71b9 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcImport.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcImport.java @@ -14,13 +14,20 @@ package com.google.devtools.build.lib.rules.cpp; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory; import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesProvider; -import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; -import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; +import com.google.devtools.build.lib.rules.cpp.CppConfiguration.HeadersCheckingMode; +import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink; +import com.google.devtools.build.lib.syntax.Type; +import com.google.devtools.build.lib.vfs.PathFragment; /** * A ConfiguredTarget for <code>cc_import</code> rule. @@ -35,9 +42,125 @@ public abstract class CcImport implements RuleConfiguredTargetFactory { @Override public ConfiguredTarget create(RuleContext ruleContext) throws RuleErrorException, InterruptedException { + Artifact staticLibrary = ruleContext.getPrerequisiteArtifact("static_library", Mode.TARGET); + Artifact sharedLibrary = ruleContext.getPrerequisiteArtifact("shared_library", Mode.TARGET); + Artifact interfaceLibrary = + ruleContext.getPrerequisiteArtifact("interface_library", Mode.TARGET); + + boolean systemProvided = ruleContext.attributes().get("system_provided", Type.BOOLEAN); + // If the shared library will be provided by system during runtime, users are not supposed to + // specify shared_library. + if (systemProvided && sharedLibrary != null) { + ruleContext.ruleError( + "'shared_library' shouldn't be specified when 'system_provided' is true"); + } + // If a shared library won't be provided by system during runtime and we are linking the shared + // library through interface library, the shared library must be specified. + if (!systemProvided && sharedLibrary == null && interfaceLibrary != null) { + ruleContext.ruleError( + "'shared_library' should be specified when 'system_provided' is false"); + } + + // Create CcLibraryHelper + CcToolchainProvider ccToolchain = + CppHelper.getToolchainUsingDefaultCcToolchainAttribute(ruleContext); + FeatureConfiguration featureConfiguration = + CcCommon.configureFeatures(ruleContext, ccToolchain); + FdoSupportProvider fdoSupport = + CppHelper.getFdoSupportUsingDefaultCcToolchainAttribute(ruleContext); + CcLibraryHelper helper = + new CcLibraryHelper(ruleContext, semantics, featureConfiguration, ccToolchain, fdoSupport); + + // Add headers + final CcCommon common = new CcCommon(ruleContext); + helper.addPublicHeaders(common.getHeaders()); + helper.setHeadersCheckingMode(HeadersCheckingMode.STRICT); + + // Get alwayslink attribute + boolean alwayslink = ruleContext.attributes().get("alwayslink", Type.BOOLEAN); + ArtifactCategory staticLibraryCategory = + alwayslink ? ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY : ArtifactCategory.STATIC_LIBRARY; + + PathFragment labelName = PathFragment.create(ruleContext.getLabel().getName()); + String libraryIdentifier = + ruleContext + .getPackageDirectory() + .getRelative(labelName.replaceName("lib" + labelName.getBaseName())) + .getPathString(); + + if (staticLibrary != null) { + if (CppFileTypes.PIC_ARCHIVE.matches(staticLibrary.getPath())) { + helper.addPicStaticLibraries( + ImmutableList.of( + LinkerInputs.opaqueLibraryToLink( + staticLibrary, staticLibraryCategory, libraryIdentifier, alwayslink))); + } else { + helper.addStaticLibraries( + ImmutableList.of( + LinkerInputs.opaqueLibraryToLink( + staticLibrary, staticLibraryCategory, libraryIdentifier, alwayslink))); + } + } + + // Now we are going to have some platform dependent behaviors + boolean targetWindows = featureConfiguration.isEnabled(CppRuleClasses.TARGETS_WINDOWS); + + Iterable<LibraryToLink> dynamicLibraryList = null; + Iterable<LibraryToLink> executionDynamicLibraryList = null; + if (sharedLibrary != null) { + if (targetWindows) { + executionDynamicLibraryList = + ImmutableList.of( + LinkerInputs.opaqueLibraryToLink( + sharedLibrary, ArtifactCategory.DYNAMIC_LIBRARY, libraryIdentifier)); + } else { + executionDynamicLibraryList = + ImmutableList.of( + LinkerInputs.solibLibraryToLink( + common.getDynamicLibrarySymlink(sharedLibrary, true), + sharedLibrary, + libraryIdentifier)); + } + helper.addExecutionDynamicLibraries(executionDynamicLibraryList); + } + + if (interfaceLibrary != null) { + if (targetWindows) { + dynamicLibraryList = + ImmutableList.of( + LinkerInputs.opaqueLibraryToLink( + interfaceLibrary, ArtifactCategory.INTERFACE_LIBRARY, libraryIdentifier)); + } else { + dynamicLibraryList = + ImmutableList.of( + LinkerInputs.solibLibraryToLink( + common.getDynamicLibrarySymlink(interfaceLibrary, true), + interfaceLibrary, + libraryIdentifier)); + } + } else { + // If interface_library is not specified and we are not building for Windows, then the dynamic + // library required at linking time is the same as the one required at execution time. + if (!targetWindows) { + dynamicLibraryList = executionDynamicLibraryList; + } else if (staticLibrary == null) { + ruleContext.ruleError( + "'interface library' must be specified when using cc_import for shared library on" + + " Windows"); + } + } + + if (dynamicLibraryList != null) { + helper.addDynamicLibraries(dynamicLibraryList); + } + + CcLibraryHelper.Info info = helper.build(); + return new RuleConfiguredTargetBuilder(ruleContext) - .setFilesToBuild(NestedSetBuilder.emptySet(Order.STABLE_ORDER)) - .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY) + .addProviders(info.getProviders()) + .addSkylarkTransitiveInfo(CcSkylarkApiProvider.NAME, new CcSkylarkApiProvider()) + .addOutputGroups(info.getOutputGroups()) + .addProvider(RunfilesProvider.class, RunfilesProvider.simple(Runfiles.EMPTY)) .build(); } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcImportRule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcImportRule.java index 298908bce1..262e4cc2ad 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcImportRule.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcImportRule.java @@ -34,23 +34,29 @@ public final class CcImportRule implements RuleDefinition { public RuleClass build(Builder builder, RuleDefinitionEnvironment env) { return builder /*<!-- #BLAZE_RULE($cc_import).ATTRIBUTE(static_library) --> - A single precompiled static library + A single precompiled static library. + Permited file types: <code>.a</code>, <code>.pic.a</code> or <code>.lib</code> <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/ .add( attr("static_library", LABEL) - .allowedFileTypes(CppFileTypes.ARCHIVE) + .allowedFileTypes(CppFileTypes.ARCHIVE, CppFileTypes.PIC_ARCHIVE) ) /*<!-- #BLAZE_RULE($cc_import).ATTRIBUTE(shared_library) --> A single precompiled shared library + Permited file types: <code>.so</code>, <code>.dll</code> or <code>.dylib</code> <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/ .add(attr("shared_library", LABEL) .allowedFileTypes(CppFileTypes.SHARED_LIBRARY) ) /*<!-- #BLAZE_RULE($cc_import).ATTRIBUTE(shared_library) --> A single interface library for linking the shared library + Permited file types: <code>.ifso</code>, <code>.tbd</code>, <code>.so</code> or + <code>.dylib</code> <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/ .add(attr("interface_library", LABEL) - .allowedFileTypes(CppFileTypes.INTERFACE_SHARED_LIBRARY)) + .allowedFileTypes( + CppFileTypes.INTERFACE_SHARED_LIBRARY, + CppFileTypes.UNIX_SHARED_LIBRARY)) /*<!-- #BLAZE_RULE($cc_import).ATTRIBUTE(hdrs) --> The list of header files published by this precompiled library to be directly included by sources in dependent rules. diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java index 4500ab109c..461ed4d11c 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibrary.java @@ -256,14 +256,16 @@ public abstract class CcLibrary implements RuleConfiguredTargetFactory { helper.addStaticLibraries(alwayslinkLibrariesFromSrcs); helper.addPicStaticLibraries(picStaticLibrariesFromSrcs); helper.addPicStaticLibraries(picAlwayslinkLibrariesFromSrcs); - helper.addDynamicLibraries( + Iterable<LibraryToLink> dynamicLibraries = Iterables.transform( precompiledFiles.getSharedLibraries(), library -> LinkerInputs.solibLibraryToLink( common.getDynamicLibrarySymlink(library, true), library, - CcLinkingOutputs.libraryIdentifierOf(library)))); + CcLinkingOutputs.libraryIdentifierOf(library))); + helper.addDynamicLibraries(dynamicLibraries); + helper.addExecutionDynamicLibraries(dynamicLibraries); CcLibraryHelper.Info info = helper.build(); /* diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibraryHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibraryHelper.java index ee2d8bc680..fbd1322b71 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibraryHelper.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLibraryHelper.java @@ -303,6 +303,7 @@ public final class CcLibraryHelper { private final List<LibraryToLink> staticLibraries = new ArrayList<>(); private final List<LibraryToLink> picStaticLibraries = new ArrayList<>(); private final List<LibraryToLink> dynamicLibraries = new ArrayList<>(); + private final List<LibraryToLink> executionDynamicLibraries = new ArrayList<>(); private boolean emitLinkActions = true; private boolean emitLinkActionsIfEmpty; @@ -653,6 +654,12 @@ public final class CcLibraryHelper { return this; } + /** Add the corresponding files as dynamic libraries required at runtime */ + public CcLibraryHelper addExecutionDynamicLibraries(Iterable<LibraryToLink> libraries) { + Iterables.addAll(executionDynamicLibraries, libraries); + return this; + } + public CcLibraryHelper setCopts(ImmutableList<String> copts) { this.copts = Preconditions.checkNotNull(copts); return this; @@ -988,8 +995,10 @@ public final class CcLibraryHelper { } } CcLinkingOutputs originalLinkingOutputs = ccLinkingOutputs; - if (!( - staticLibraries.isEmpty() && picStaticLibraries.isEmpty() && dynamicLibraries.isEmpty())) { + if (!(staticLibraries.isEmpty() + && picStaticLibraries.isEmpty() + && dynamicLibraries.isEmpty() + && executionDynamicLibraries.isEmpty())) { CcLinkingOutputs.Builder newOutputsBuilder = new CcLinkingOutputs.Builder(); if (!ccOutputs.isEmpty()) { @@ -998,7 +1007,9 @@ public final class CcLibraryHelper { newOutputsBuilder.merge(originalLinkingOutputs); ImmutableSetMultimap<String, LibraryToLink> precompiledLibraryMap = CcLinkingOutputs.getLibrariesByIdentifier( - Iterables.concat(staticLibraries, picStaticLibraries, dynamicLibraries)); + Iterables.concat( + staticLibraries, picStaticLibraries, + dynamicLibraries, executionDynamicLibraries)); ImmutableSetMultimap<String, LibraryToLink> linkedLibraryMap = originalLinkingOutputs.getLibrariesByIdentifier(); for (String matchingIdentifier : @@ -1031,7 +1042,7 @@ public final class CcLibraryHelper { .addStaticLibraries(staticLibraries) .addPicStaticLibraries(picStaticLibraries) .addDynamicLibraries(dynamicLibraries) - .addExecutionDynamicLibraries(dynamicLibraries) + .addExecutionDynamicLibraries(executionDynamicLibraries) .build(); } @@ -1567,7 +1578,9 @@ public final class CcLibraryHelper { builder.addLibraries( ccLinkingOutputs.getPreferredLibraries( linkingStatically, /*preferPic=*/ linkShared || forcePic)); - if (!linkingStatically) { + if (!linkingStatically + || (ccLinkingOutputs.getStaticLibraries().isEmpty() + && ccLinkingOutputs.getPicStaticLibraries().isEmpty())) { builder.addExecutionDynamicLibraries( LinkerInputs.toLibraryArtifacts(ccLinkingOutputs.getExecutionDynamicLibraries())); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java index d92add9ee6..fd2c60fcc9 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppFileTypes.java @@ -132,7 +132,9 @@ public final class CppFileTypes { public static final FileType LTO_INDEXING_OBJECT_FILE = FileType.of(".indexing.o"); public static final FileType SHARED_LIBRARY = FileType.of(".so", ".dylib", ".dll"); - public static final FileType INTERFACE_SHARED_LIBRARY = FileType.of(".ifso"); + // Unix shared libraries can be passed to linker, but not .dll on Windows + public static final FileType UNIX_SHARED_LIBRARY = FileType.of(".so", ".dylib"); + public static final FileType INTERFACE_SHARED_LIBRARY = FileType.of(".ifso", ".tbd"); public static final FileType LINKER_SCRIPT = FileType.of(".ld", ".lds", ".ldscript"); // Windows DEF file: https://msdn.microsoft.com/en-us/library/28d6s79h.aspx diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java index 52fdab4b83..1777257632 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java @@ -1856,14 +1856,17 @@ public class CppLinkActionBuilder { Map<Artifact, Artifact> ltoMap) { boolean includeRuntimeSolibDir = false; for (LinkerInput input : runtimeLinkerInputs) { - if (input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY) { + if (input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY + || input.getArtifactCategory() == ArtifactCategory.INTERFACE_LIBRARY) { PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory(); - Preconditions.checkState( - runtimeSolibDir != null && libDir.equals(runtimeSolibDir), - "Artifact '%s' is not under directory '%s'.", - input.getArtifact(), - solibDir); - includeRuntimeSolibDir = true; + if (!featureConfiguration.isEnabled(CppRuleClasses.COPY_DYNAMIC_LIBRARIES_TO_BINARY)) { + Preconditions.checkState( + runtimeSolibDir != null && libDir.equals(runtimeSolibDir), + "Artifact '%s' is not under directory '%s'.", + input.getArtifact(), + solibDir); + includeRuntimeSolibDir = true; + } addDynamicInputLinkOptions( input, librariesToLink, @@ -1887,19 +1890,20 @@ public class CppLinkActionBuilder { Map<Artifact, Artifact> ltoMap) { boolean includeSolibDir = false; for (LinkerInput input : linkerInputs) { - if (input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY) { + if (input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY + || input.getArtifactCategory() == ArtifactCategory.INTERFACE_LIBRARY) { PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory(); // When COPY_DYNAMIC_LIBRARIES_TO_BINARY is enabled, dynamic libraries are not symlinked - // under solibDir, so don't check it. + // under solibDir, so don't check it and don't include solibDir. if (!featureConfiguration.isEnabled(CppRuleClasses.COPY_DYNAMIC_LIBRARIES_TO_BINARY)) { Preconditions.checkState( libDir.startsWith(solibDir), "Artifact '%s' is not under directory '%s'.", input.getArtifact(), solibDir); - } - if (libDir.equals(solibDir)) { - includeSolibDir = true; + if (libDir.equals(solibDir)) { + includeSolibDir = true; + } } addDynamicInputLinkOptions( input, @@ -1927,7 +1931,9 @@ public class CppLinkActionBuilder { ImmutableSet.Builder<String> rpathRootsForExplicitSoDeps, PathFragment solibDir, String rpathRoot) { - Preconditions.checkState(input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY); + Preconditions.checkState( + input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY + || input.getArtifactCategory() == ArtifactCategory.INTERFACE_LIBRARY); Preconditions.checkState( !Link.useStartEndLib(input, CppHelper.getArchiveType(cppConfiguration, toolchain))); diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInputs.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInputs.java index 3dbbd3c722..469d424da5 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInputs.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkerInputs.java @@ -267,17 +267,21 @@ public abstract class LinkerInputs { String libraryIdentifier, Iterable<Artifact> objectFiles, ImmutableMap<Artifact, Artifact> ltoBitcodeFiles, - ImmutableMap<Artifact, LtoBackendArtifacts> sharedNonLtoBackends) { + ImmutableMap<Artifact, LtoBackendArtifacts> sharedNonLtoBackends, + boolean allowArchiveTypeInAlwayslink) { String basename = libraryArtifact.getFilename(); switch (category) { case ALWAYSLINK_STATIC_LIBRARY: - Preconditions.checkState(Link.LINK_LIBRARY_FILETYPES.matches(basename)); + Preconditions.checkState( + Link.LINK_LIBRARY_FILETYPES.matches(basename) + || (allowArchiveTypeInAlwayslink && Link.ARCHIVE_FILETYPES.matches(basename))); break; case STATIC_LIBRARY: Preconditions.checkState(Link.ARCHIVE_FILETYPES.matches(basename)); break; + case INTERFACE_LIBRARY: case DYNAMIC_LIBRARY: Preconditions.checkState(Link.SHARED_LIBRARY_FILETYPES.matches(basename)); break; @@ -427,7 +431,8 @@ public abstract class LinkerInputs { CcLinkingOutputs.libraryIdentifierOf(artifact), /* objectFiles= */ null, /* ltoBitcodeFiles= */ null, - /* sharedNonLtoBackends= */ null); + /* sharedNonLtoBackends= */ null, + /* allowArchiveTypeInAlwayslink= */ false); } public static LibraryToLink opaqueLibraryToLink( @@ -438,7 +443,17 @@ public abstract class LinkerInputs { libraryIdentifier, /* objectFiles= */ null, /* ltoBitcodeFiles= */ null, - /* sharedNonLtoBackends= */ null); + /* sharedNonLtoBackends= */ null, + /* allowArchiveTypeInAlwayslink= */ false); + } + + public static LibraryToLink opaqueLibraryToLink( + Artifact artifact, + ArtifactCategory category, + String libraryIdentifier, + boolean allowArchiveTypeInAlwayslink) { + return new CompoundLibraryToLink( + artifact, category, libraryIdentifier, null, null, null, allowArchiveTypeInAlwayslink); } /** Creates a library to link with the specified object files. */ @@ -450,7 +465,8 @@ public abstract class LinkerInputs { ImmutableMap<Artifact, Artifact> ltoBitcodeFiles, ImmutableMap<Artifact, LtoBackendArtifacts> sharedNonLtoBackends) { return new CompoundLibraryToLink( - library, category, libraryIdentifier, objectFiles, ltoBitcodeFiles, sharedNonLtoBackends); + library, category, libraryIdentifier, objectFiles, ltoBitcodeFiles, sharedNonLtoBackends, + /* allowArchiveTypeInAlwayslink= */ false); } public static Iterable<Artifact> toNonSolibArtifacts(Iterable<LibraryToLink> libraries) { diff --git a/src/test/java/com/google/devtools/build/lib/packages/util/MockCcSupport.java b/src/test/java/com/google/devtools/build/lib/packages/util/MockCcSupport.java index 326539f231..c7a78d4fe3 100644 --- a/src/test/java/com/google/devtools/build/lib/packages/util/MockCcSupport.java +++ b/src/test/java/com/google/devtools/build/lib/packages/util/MockCcSupport.java @@ -414,6 +414,14 @@ public abstract class MockCcSupport { public static final String COPY_DYNAMIC_LIBRARIES_TO_BINARY_CONFIGURATION = "" + "feature { " + " name: 'copy_dynamic_libraries_to_binary'" + "}"; + public static final String TARGETS_WINDOWS_CONFIGURATION = + "" + + "feature {" + + " name: 'targets_windows'" + + " implies: 'copy_dynamic_libraries_to_binary'" + + " enabled: true" + + "}"; + public static final String STATIC_LINK_TWEAKED_CONFIGURATION = "" + "artifact_name_pattern {" diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcImportConfiguredTargetTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcImportConfiguredTargetTest.java index e7fa1657cd..b11b104a45 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcImportConfiguredTargetTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcImportConfiguredTargetTest.java @@ -15,8 +15,14 @@ package com.google.devtools.build.lib.rules.cpp; -import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; +import static com.google.common.truth.Truth.assertThat; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +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.MockCcSupport; +import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -29,7 +35,8 @@ public class CcImportConfiguredTargetTest extends BuildViewTestCase { @Test public void testCcImportRule() throws Exception { - scratch.file("third_party/BUILD", + scratch.file( + "third_party/BUILD", "cc_import(", " name = 'a_import',", " static_library = 'A.a',", @@ -37,7 +44,246 @@ public class CcImportConfiguredTargetTest extends BuildViewTestCase { " interface_library = 'A.ifso',", " hdrs = ['a.h'],", " alwayslink = 1,", + " system_provided = 0,", ")"); getConfiguredTarget("//third_party:a_import"); } + + @Test + public void testWrongCcImportDefinitions() throws Exception { + checkError("a", "foo", + "'//a:libfoo.so' does not produce any cc_import static_library files " + + "(expected .a, .lib or .pic.a)", + "cc_import(", + " name = 'foo',", + " static_library = 'libfoo.so',", + ")" + ); + checkError("b", "foo", + "'//b:libfoo.a' does not produce any cc_import shared_library files " + + "(expected .so, .dylib or .dll)", + "cc_import(", + " name = 'foo',", + " shared_library = 'libfoo.a',", + ")" + ); + checkError("c", "foo", + "'//c:libfoo.a' does not produce any cc_import interface_library files " + + "(expected .ifso, .tbd, .so or .dylib)", + "cc_import(", + " name = 'foo',", + " shared_library = 'libfoo.dll',", + " interface_library = 'libfoo.a',", + ")" + ); + checkError("d", "foo", + "'shared_library' shouldn't be specified when 'system_provided' is true", + "cc_import(", + " name = 'foo',", + " shared_library = 'libfoo.so',", + " system_provided = 1,", + ")" + ); + checkError("e", "foo", + "'shared_library' should be specified when 'system_provided' is false", + "cc_import(", + " name = 'foo',", + " interface_library = 'libfoo.ifso',", + " system_provided = 0,", + ")" + ); + } + + @Test + public void testWrongCcImportDefinitionsOnWindows() throws Exception { + AnalysisMock.get() + .ccSupport() + .setupCrosstool( + mockToolsConfig, + MockCcSupport.COPY_DYNAMIC_LIBRARIES_TO_BINARY_CONFIGURATION, + MockCcSupport.TARGETS_WINDOWS_CONFIGURATION); + useConfiguration(); + checkError("a", "foo", + "'interface library' must be specified when using cc_import for shared library on Windows", + "cc_import(", + " name = 'foo',", + " shared_library = 'libfoo.dll',", + ")" + ); + } + + @Test + public void testCcImportWithStaticLibrary() throws Exception { + ConfiguredTarget target = + scratchConfiguredTarget("a", "foo", "cc_import(name = 'foo', static_library = 'libfoo.a')"); + Iterable<Artifact> libraries = + LinkerInputs.toNonSolibArtifacts( + target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(false, false).getLibraries()); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.a"); + + libraries = + LinkerInputs.toNonSolibArtifacts( + target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(false, true).getLibraries()); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.a"); + + libraries = + LinkerInputs.toNonSolibArtifacts( + target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(true, false).getLibraries()); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.a"); + + libraries = + LinkerInputs.toNonSolibArtifacts( + target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(true, true).getLibraries()); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.a"); + } + + @Test + public void testCcImportWithSharedLibrary() throws Exception { + useConfiguration("--cpu=k8"); + ConfiguredTarget target = + scratchConfiguredTarget( + "a", "foo", "cc_import(name = 'foo', shared_library = 'libfoo.so')"); + CcLinkParams ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(false, false); + Iterable<Artifact> libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + Iterable<Artifact> executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.so"); + assertThat(artifactsToStrings(executionDynamicLibraries)) + .containsExactly("bin _solib_k8/_U_S_Sa_Cfoo___Ua/libfoo.so"); + + ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(false, true); + libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.so"); + assertThat(artifactsToStrings(executionDynamicLibraries)) + .containsExactly("bin _solib_k8/_U_S_Sa_Cfoo___Ua/libfoo.so"); + + ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(true, false); + libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.so"); + assertThat(artifactsToStrings(executionDynamicLibraries)) + .containsExactly("bin _solib_k8/_U_S_Sa_Cfoo___Ua/libfoo.so"); + + ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(true, true); + libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.so"); + assertThat(artifactsToStrings(executionDynamicLibraries)) + .containsExactly("bin _solib_k8/_U_S_Sa_Cfoo___Ua/libfoo.so"); + } + + @Test + public void testCcImportWithInterfaceSharedLibrary() throws Exception { + useConfiguration("--cpu=k8"); + ConfiguredTarget target = + scratchConfiguredTarget( + "b", + "foo", + "cc_import(name = 'foo', shared_library = 'libfoo.so'," + + " interface_library = 'libfoo.ifso')"); + CcLinkParams ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(false, false); + Iterable<Artifact> libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + Iterable<Artifact> executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src b/libfoo.ifso"); + assertThat(artifactsToStrings(executionDynamicLibraries)) + .containsExactly("bin _solib_k8/_U_S_Sb_Cfoo___Ub/libfoo.so"); + + ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(false, true); + libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src b/libfoo.ifso"); + assertThat(artifactsToStrings(executionDynamicLibraries)) + .containsExactly("bin _solib_k8/_U_S_Sb_Cfoo___Ub/libfoo.so"); + + ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(true, false); + libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src b/libfoo.ifso"); + assertThat(artifactsToStrings(executionDynamicLibraries)) + .containsExactly("bin _solib_k8/_U_S_Sb_Cfoo___Ub/libfoo.so"); + + ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(true, true); + libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src b/libfoo.ifso"); + assertThat(artifactsToStrings(executionDynamicLibraries)) + .containsExactly("bin _solib_k8/_U_S_Sb_Cfoo___Ub/libfoo.so"); + } + + @Test + public void testCcImportWithBothStaticAndSharedLibraries() throws Exception { + useConfiguration("--cpu=k8"); + ConfiguredTarget target = + scratchConfiguredTarget( + "a", + "foo", + "cc_import(name = 'foo', static_library = 'libfoo.a', shared_library = 'libfoo.so')"); + CcLinkParams ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(false, false); + Iterable<Artifact> libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + Iterable<Artifact> executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.so"); + assertThat(artifactsToStrings(executionDynamicLibraries)) + .containsExactly("bin _solib_k8/_U_S_Sa_Cfoo___Ua/libfoo.so"); + + ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(false, true); + libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.so"); + assertThat(artifactsToStrings(executionDynamicLibraries)) + .containsExactly("bin _solib_k8/_U_S_Sa_Cfoo___Ua/libfoo.so"); + + ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(true, false); + libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.a"); + assertThat(artifactsToStrings(executionDynamicLibraries)).isEmpty(); + + ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(true, true); + libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.a"); + assertThat(artifactsToStrings(executionDynamicLibraries)).isEmpty(); + } + + @Test + public void testCcImportWithAlwaysLinkStaticLibrary() throws Exception { + ConfiguredTarget target = + scratchConfiguredTarget( + "a", "foo", "cc_import(name = 'foo', static_library = 'libfoo.a', alwayslink = 1)"); + LibraryToLink libraryToLink = + target + .get(CcLinkParamsInfo.PROVIDER) + .getCcLinkParams(false, false) + .getLibraries() + .toList() + .get(0); + assertThat(libraryToLink.getArtifactCategory()) + .isEqualTo(ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY); + } + + @Test + public void testCcImportSystemProvidedIsTrue() throws Exception { + ConfiguredTarget target = + scratchConfiguredTarget( + "a", + "foo", + "cc_import(name = 'foo', interface_library = 'libfoo.ifso', system_provided = 1)"); + CcLinkParams ccLinkParams = target.get(CcLinkParamsInfo.PROVIDER).getCcLinkParams(false, false); + Iterable<Artifact> libraries = LinkerInputs.toNonSolibArtifacts(ccLinkParams.getLibraries()); + Iterable<Artifact> executionDynamicLibraries = ccLinkParams.getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).containsExactly("src a/libfoo.ifso"); + assertThat(artifactsToStrings(executionDynamicLibraries)).isEmpty(); + } + + @Test + public void testCcImportProvideHeaderFiles() throws Exception { + Iterable<Artifact> headers = + scratchConfiguredTarget( + "a", + "foo", + "cc_import(name = 'foo', static_library = 'libfoo.a', hdrs = ['foo.h'])") + .getProvider(CppCompilationContext.class) + .getDeclaredIncludeSrcs(); + assertThat(artifactsToStrings(headers)).containsExactly("src a/foo.h"); + } } diff --git a/src/test/py/bazel/BUILD b/src/test/py/bazel/BUILD index afec10a9f1..4198b90f44 100644 --- a/src/test/py/bazel/BUILD +++ b/src/test/py/bazel/BUILD @@ -32,6 +32,13 @@ py_test( ) py_test( + name = "cc_import_test", + size = "medium", + srcs = ["cc_import_test.py"], + deps = [":test_base"], +) + +py_test( name = "bazel_server_mode_test", size = "medium", srcs = ["bazel_server_mode_test.py"], diff --git a/src/test/py/bazel/cc_import_test.py b/src/test/py/bazel/cc_import_test.py new file mode 100644 index 0000000000..1563957495 --- /dev/null +++ b/src/test/py/bazel/cc_import_test.py @@ -0,0 +1,257 @@ +# Copyright 2017 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. + +import os +import shutil +import stat +import unittest +from src.test.py.bazel import test_base + + +class CcImportTest(test_base.TestBase): + + def createProjectFiles(self, + alwayslink=0, + system_provided=0, + linkstatic=1, + provide_header=True): + self.ScratchFile('WORKSPACE') + + # We use the outputs of cc_binary and cc_library as precompiled + # libraries for cc_import + self.ScratchFile( + 'lib/BUILD', + [ + 'package(default_visibility = ["//visibility:public"])', + '', + 'cc_binary(', + ' name = "libA.so",', + ' srcs = ["a.cc"],', + ' linkshared = 1,', + ')', + '', + 'filegroup(', + ' name = "libA_ifso",', + ' srcs = [":libA.so"],', + ' output_group = "interface_library",', + ')', + '', + 'cc_library(', + ' name = "libA",', + ' srcs = ["a.cc", "a_al.cc"],', + ')', + '', + 'filegroup(', + ' name = "libA_archive",', + ' srcs = [":libA"],', + ' output_group = "archive",', + ')', + '', + 'cc_import(', + ' name = "A",', + ' static_library = "//lib:libA_archive",', + ' shared_library = "//lib:libA.so",' + if not system_provided else '', + # On Windows, we always need the interface library + ' interface_library = "//lib:libA_ifso",' + if self.IsWindows() else ( + # On Unix, we use .so file as interface library + # if system_provided is true + ' interface_library = "//lib:libA.so",' + if system_provided else ''), + ' hdrs = ["a.h"],' if provide_header else '', + ' alwayslink = %s,' % str(alwayslink), + ' system_provided = %s,' % str(system_provided), + ')', + ]) + + self.ScratchFile('lib/a.cc', [ + '#include <stdio.h>', + '', + '#ifdef COMPILER_MSVC', + ' #define DLLEXPORT __declspec(dllexport)', + '#else', + ' #define DLLEXPORT', + '#endif', + '', + 'DLLEXPORT void HelloWorld() {', + ' printf("HelloWorld\\n");', + '}', + ]) + + # For testing alwayslink=1 + self.ScratchFile('lib/a_al.cc', [ + 'extern int global_variable;', + 'int init() {', + ' ++global_variable;', + ' return global_variable;', + '}', + 'int x = init();', + 'int y = init();', + ]) + + self.ScratchFile('lib/a.h', [ + 'void HelloWorld();', + ]) + + self.ScratchFile('main/BUILD', [ + 'cc_binary(', + ' name = "B",', + ' srcs = ["b.cc"],', + ' deps = ["//lib:A",],', + ' linkstatic = %s,' % str(linkstatic), + ')', + ]) + + self.ScratchFile('main/b.cc', [ + '#include <stdio.h>', + '#include "lib/a.h"', + 'int global_variable = 0;', + 'int main() {', + ' HelloWorld();', + ' printf("global : %d\\n", global_variable);', + ' return 0;', + '}', + ]) + + def getBazelInfo(self, info_key): + exit_code, stdout, stderr = self.RunBazel(['info', info_key]) + self.AssertExitCode(exit_code, 0, stderr) + return stdout[0] + + def testLinkStaticLibrary(self): + self.createProjectFiles(alwayslink=0, linkstatic=1) + bazel_bin = self.getBazelInfo('bazel-bin') + suffix = '.exe' if self.IsWindows() else '' + + exit_code, _, stderr = self.RunBazel(['build', '//main:B']) + self.AssertExitCode(exit_code, 0, stderr) + + b_bin = os.path.join(bazel_bin, 'main/B' + suffix) + self.assertTrue(os.path.exists(b_bin)) + exit_code, stdout, stderr = self.RunProgram([b_bin]) + self.AssertExitCode(exit_code, 0, stderr) + self.assertEqual(stdout[0], 'HelloWorld') + self.assertEqual(stdout[1], 'global : 0') + + def testAlwayslinkStaticLibrary(self): + self.createProjectFiles(alwayslink=1, linkstatic=1) + bazel_bin = self.getBazelInfo('bazel-bin') + suffix = '.exe' if self.IsWindows() else '' + + exit_code, _, stderr = self.RunBazel(['build', '//main:B']) + self.AssertExitCode(exit_code, 0, stderr) + + b_bin = os.path.join(bazel_bin, 'main/B' + suffix) + self.assertTrue(os.path.exists(b_bin)) + exit_code, stdout, stderr = self.RunProgram([b_bin]) + self.AssertExitCode(exit_code, 0, stderr) + self.assertEqual(stdout[0], 'HelloWorld') + self.assertEqual(stdout[1], 'global : 2') + + def testLinkSharedLibrary(self): + self.createProjectFiles(linkstatic=0) + bazel_bin = self.getBazelInfo('bazel-bin') + suffix = '.exe' if self.IsWindows() else '' + + exit_code, _, stderr = self.RunBazel(['build', '//main:B']) + self.AssertExitCode(exit_code, 0, stderr) + + b_bin = os.path.join(bazel_bin, 'main/B' + suffix) + self.assertTrue(os.path.exists(b_bin)) + if self.IsWindows(): + self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'main/libA.so'))) + exit_code, stdout, stderr = self.RunProgram([b_bin]) + self.AssertExitCode(exit_code, 0, stderr) + self.assertEqual(stdout[0], 'HelloWorld') + + def testSystemProvidedSharedLibraryOnWinodws(self): + if not self.IsWindows(): + return + self.createProjectFiles(system_provided=1, linkstatic=0) + bazel_bin = self.getBazelInfo('bazel-bin') + + exit_code, _, stderr = self.RunBazel(['build', '//main:B']) + self.AssertExitCode(exit_code, 0, stderr) + + b_bin = os.path.join(bazel_bin, 'main/B.exe') + exit_code, stdout, stderr = self.RunProgram([b_bin]) + # Should fail because missing libA.so + self.assertFalse(exit_code == 0) + + # Let's build libA.so and add it into PATH + exit_code, stdout, stderr = self.RunBazel(['build', '//lib:libA.so']) + self.AssertExitCode(exit_code, 0, stderr) + + exit_code, stdout, stderr = self.RunProgram( + [b_bin], env_add={ + 'PATH': str(os.path.join(bazel_bin, 'lib')) + }) + self.AssertExitCode(exit_code, 0, stderr) + self.assertEqual(stdout[0], 'HelloWorld') + + def testSystemProvidedSharedLibraryOnUnix(self): + if not self.IsUnix(): + return + self.createProjectFiles(system_provided=1, linkstatic=0) + bazel_bin = self.getBazelInfo('bazel-bin') + + exit_code, _, stderr = self.RunBazel(['build', '//main:B']) + self.AssertExitCode(exit_code, 0, stderr) + + b_bin = os.path.join(bazel_bin, 'main/B') + tmp_dir = self.ScratchDir('temp_dir_for_run_b_bin') + b_bin_tmp = os.path.join(tmp_dir, 'B') + # Copy the binary to a temp directory to make sure it cannot find + # libA.so + shutil.copyfile(b_bin, b_bin_tmp) + os.chmod(b_bin_tmp, stat.S_IRWXU) + exit_code, stdout, stderr = self.RunProgram([b_bin_tmp]) + # Should fail because missing libA.so + self.assertFalse(exit_code == 0) + + # Let's build libA.so and add it into PATH + exit_code, stdout, stderr = self.RunBazel(['build', '//lib:libA.so']) + self.AssertExitCode(exit_code, 0, stderr) + + exit_code, stdout, stderr = self.RunProgram( + [b_bin_tmp], env_add={ + # For Linux + 'LD_LIBRARY_PATH': str(os.path.join(bazel_bin, 'lib')), + # For Mac + 'DYLD_LIBRARY_PATH': str(os.path.join(bazel_bin, 'lib')), + }) + self.AssertExitCode(exit_code, 0, stderr) + self.assertEqual(stdout[0], 'HelloWorld') + + def testCcImportHeaderCheck(self): + self.createProjectFiles(provide_header=False) + # Build should fail, because lib/a.h is not declared in BUILD file, disable + # sandbox so that bazel produces same error across different platforms. + exit_code, _, stderr = self.RunBazel( + ['build', '//main:B', '--spawn_strategy=standalone']) + self.AssertExitCode(exit_code, 1, stderr) + self.assertIn('this rule is missing dependency declarations for the' + ' following files included by \'main/b.cc\':', + ''.join(stderr)) + + def AssertFileContentContains(self, file_path, entry): + f = open(file_path, 'r') + if entry not in f.read(): + self.fail('File "%s" does not contain "%s"' % (file_path, entry)) + f.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/py/bazel/test_base.py b/src/test/py/bazel/test_base.py index 695aef9882..9eb763dd77 100644 --- a/src/test/py/bazel/test_base.py +++ b/src/test/py/bazel/test_base.py @@ -94,6 +94,11 @@ class TestBase(unittest.TestCase): """Returns true if the current platform is Windows.""" return os.name == 'nt' + @staticmethod + def IsUnix(): + """Returns true if the current platform is Unix platform.""" + return os.name == 'posix' + def Path(self, path): """Returns the absolute path of `path` relative to self._test_cwd. |