diff options
author | 2017-09-20 10:25:48 +0200 | |
---|---|---|
committer | 2017-09-20 11:59:04 +0200 | |
commit | 481657d6149b3c68b893ad8221ac1bccccf1060d (patch) | |
tree | db33e89dbfd5f9df16143de5553cbaf1ab638159 /src | |
parent | e28b772d85a463e4fe154767d0d82c0fb9e63c3d (diff) |
Windows: Make dynamic libraries available to binary at runtime
When copy_dynamic_libraries_to_binary is enabled, we copy the shared
libraries required by the binary to the binary's directory.
Bazel will throw errors if there are confilct actions generating the
same artifacts.
Change-Id: I09a5a599ca0ec7a67efd49d5aa89481450fa4e90
PiperOrigin-RevId: 169364498
Diffstat (limited to 'src')
9 files changed, 259 insertions, 30 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java index 20b8ae5780..14eeb3bbe8 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java @@ -36,6 +36,7 @@ import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; import com.google.devtools.build.lib.analysis.actions.FileWriteAction; import com.google.devtools.build.lib.analysis.actions.ParamFileInfo; import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.analysis.actions.SymlinkAction; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.test.ExecutionInfo; import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider; @@ -236,6 +237,14 @@ public abstract class CcBinary implements RuleConfiguredTargetFactory { ruleContext.attributeError("linkshared", "'linkshared' used in non-shared library"); return null; } + + CcLinkParams linkParams = + collectCcLinkParams( + ruleContext, + linkStaticness != LinkStaticness.DYNAMIC, + isLinkShared(ruleContext), + linkopts); + CppLinkActionBuilder linkActionBuilder = determineLinkerArguments( ruleContext, @@ -248,8 +257,7 @@ public abstract class CcBinary implements RuleConfiguredTargetFactory { cppCompilationContext.getTransitiveCompilationPrerequisites(), fake, binary, - linkStaticness, - linkopts, + linkParams, linkCompileOutputSeparately); linkActionBuilder.setUseTestOnlyFlags(ruleContext.isTestTarget()); if (linkStaticness == LinkStaticness.DYNAMIC) { @@ -363,6 +371,17 @@ public abstract class CcBinary implements RuleConfiguredTargetFactory { } } + // If the binary is linked dynamically and COPY_DYNAMIC_LIBRARIES_TO_BINARY is enabled, collect + // all the dynamic libraries we need at runtime. Then copy these libraries next to the binary. + if (featureConfiguration.isEnabled(CppRuleClasses.COPY_DYNAMIC_LIBRARIES_TO_BINARY)) { + filesToBuild = + NestedSetBuilder.fromNestedSet(filesToBuild) + .addAll( + createDynamicLibrariesCopyActions( + ruleContext, linkParams.getExecutionDynamicLibraries())) + .build(); + } + // TODO(bazel-team): Do we need to put original shared libraries (along with // mangled symlinks) into the RunfilesSupport object? It does not seem // logical since all symlinked libraries will be linked anyway and would @@ -462,8 +481,7 @@ public abstract class CcBinary implements RuleConfiguredTargetFactory { ImmutableSet<Artifact> compilationPrerequisites, boolean fake, Artifact binary, - LinkStaticness linkStaticness, - List<String> linkopts, + CcLinkParams linkParams, boolean linkCompileOutputSeparately) throws InterruptedException { CppLinkActionBuilder builder = @@ -512,9 +530,8 @@ public abstract class CcBinary implements RuleConfiguredTargetFactory { } // Then the link params from the closure of deps. - CcLinkParams linkParams = collectCcLinkParams( - context, linkStaticness != LinkStaticness.DYNAMIC, isLinkShared(context), linkopts); builder.addLinkParams(linkParams, context); + return builder; } @@ -689,6 +706,29 @@ public abstract class CcBinary implements RuleConfiguredTargetFactory { } /** + * Create the actions to symlink/copy execution dynamic libraries to binary directory so that they + * are available at runtime. + * + * @param executionDynamicLibraries The libraries to be copied. + * @return The result artifacts of the copies. + */ + private static ImmutableList<Artifact> createDynamicLibrariesCopyActions( + RuleContext ruleContext, NestedSet<Artifact> executionDynamicLibraries) { + ImmutableList.Builder<Artifact> result = ImmutableList.builder(); + for (Artifact target : executionDynamicLibraries) { + if (!ruleContext.getLabel().getPackageName().equals(target.getOwner().getPackageName())) { + // SymlinkAction on file is actually copy on Windows. + Artifact copy = ruleContext.getBinArtifact(target.getFilename()); + ruleContext.registerAction( + new SymlinkAction( + ruleContext.getActionOwner(), target, copy, "Copying Execution Dynamic Library")); + result.add(copy); + } + } + return result.build(); + } + + /** * Returns a new SpawnAction builder for generating dwp files, pre-initialized with standard * settings. */ 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 4fe0b4bebd..fd7a3cbb0a 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 @@ -1558,13 +1558,15 @@ public final class CcLibraryHelper { CcLinkParams.Builder builder, boolean linkingStatically, boolean linkShared) { builder.addLinkstamps(linkstamps.build(), cppCompilationContext); builder.addTransitiveTargets( - deps, - CcLinkParamsInfo.TO_LINK_PARAMS, - CcSpecificLinkParamsProvider.TO_LINK_PARAMS); + deps, CcLinkParamsInfo.TO_LINK_PARAMS, CcSpecificLinkParamsProvider.TO_LINK_PARAMS); if (!neverlink) { builder.addLibraries( ccLinkingOutputs.getPreferredLibraries( linkingStatically, /*preferPic=*/ linkShared || forcePic)); + if (!linkingStatically) { + builder.addExecutionDynamicLibraries( + LinkerInputs.toLibraryArtifacts(ccLinkingOutputs.getExecutionDynamicLibraries())); + } builder.addLinkOpts(linkopts); } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParams.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParams.java index 75b5662b1e..afb863101c 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParams.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkParams.java @@ -69,15 +69,19 @@ public final class CcLinkParams { private final NestedSet<LinkOptions> linkOpts; private final NestedSet<Linkstamp> linkstamps; private final NestedSet<LibraryToLink> libraries; + private final NestedSet<Artifact> executionDynamicLibraries; private final ExtraLinkTimeLibraries extraLinkTimeLibraries; - private CcLinkParams(NestedSet<LinkOptions> linkOpts, - NestedSet<Linkstamp> linkstamps, - NestedSet<LibraryToLink> libraries, - ExtraLinkTimeLibraries extraLinkTimeLibraries) { + private CcLinkParams( + NestedSet<LinkOptions> linkOpts, + NestedSet<Linkstamp> linkstamps, + NestedSet<LibraryToLink> libraries, + NestedSet<Artifact> executionDynamicLibraries, + ExtraLinkTimeLibraries extraLinkTimeLibraries) { this.linkOpts = linkOpts; this.linkstamps = linkstamps; this.libraries = libraries; + this.executionDynamicLibraries = executionDynamicLibraries; this.extraLinkTimeLibraries = extraLinkTimeLibraries; } @@ -106,6 +110,11 @@ public final class CcLinkParams { return libraries; } + /** @return the executionDynamicLibraries */ + public NestedSet<Artifact> getExecutionDynamicLibraries() { + return executionDynamicLibraries; + } + /** * The extra link time libraries; will be null if there are no such libraries. */ @@ -146,6 +155,8 @@ public final class CcLinkParams { NestedSetBuilder.compileOrder(); private final NestedSetBuilder<LibraryToLink> librariesBuilder = NestedSetBuilder.linkOrder(); + private final NestedSetBuilder<Artifact> executionDynamicLibrariesBuilder = + NestedSetBuilder.stableOrder(); /** * A builder for the list of link time libraries. Most builds @@ -176,8 +187,12 @@ public final class CcLinkParams { if (extraLinkTimeLibrariesBuilder != null) { extraLinkTimeLibraries = extraLinkTimeLibrariesBuilder.build(); } - return new CcLinkParams(linkOptsBuilder.build(), linkstampsBuilder.build(), - librariesBuilder.build(), extraLinkTimeLibraries); + return new CcLinkParams( + linkOptsBuilder.build(), + linkstampsBuilder.build(), + librariesBuilder.build(), + executionDynamicLibrariesBuilder.build(), + extraLinkTimeLibraries); } public boolean add(CcLinkParamsStore store) { @@ -263,6 +278,7 @@ public final class CcLinkParams { linkOptsBuilder.addTransitive(args.getLinkopts()); linkstampsBuilder.addTransitive(args.getLinkstamps()); librariesBuilder.addTransitive(args.getLibraries()); + executionDynamicLibrariesBuilder.addTransitive(args.getExecutionDynamicLibraries()); if (args.getExtraLinkTimeLibraries() != null) { if (extraLinkTimeLibrariesBuilder == null) { extraLinkTimeLibrariesBuilder = ExtraLinkTimeLibraries.builder(); @@ -306,6 +322,12 @@ public final class CcLinkParams { return this; } + /** Adds a collection of library artifacts. */ + public Builder addExecutionDynamicLibraries(Iterable<Artifact> libraries) { + executionDynamicLibrariesBuilder.addAll(libraries); + return this; + } + /** * Adds an extra link time library, a library that is actually * built at link time. @@ -334,6 +356,10 @@ public final class CcLinkParams { if (!neverlink) { addLibraries(linkingOutputs.getPreferredLibraries(linkingStatically, linkShared || context.getFragment(CppConfiguration.class).forcePic())); + if (!linkingStatically) { + addExecutionDynamicLibraries( + LinkerInputs.toLibraryArtifacts(linkingOutputs.getExecutionDynamicLibraries())); + } addLinkOpts(linkopts); } return this; @@ -388,12 +414,12 @@ public final class CcLinkParams { } } - /** - * Empty CcLinkParams. - */ - public static final CcLinkParams EMPTY = new CcLinkParams( - NestedSetBuilder.<LinkOptions>emptySet(Order.LINK_ORDER), - NestedSetBuilder.<Linkstamp>emptySet(Order.COMPILE_ORDER), - NestedSetBuilder.<LibraryToLink>emptySet(Order.LINK_ORDER), - null); + /** Empty CcLinkParams. */ + public static final CcLinkParams EMPTY = + new CcLinkParams( + NestedSetBuilder.<LinkOptions>emptySet(Order.LINK_ORDER), + NestedSetBuilder.<Linkstamp>emptySet(Order.COMPILE_ORDER), + NestedSetBuilder.<LibraryToLink>emptySet(Order.LINK_ORDER), + NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER), + null); } 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 194bb00484..2df7719e49 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 @@ -1736,11 +1736,15 @@ public class CppLinkActionBuilder { for (LinkerInput input : linkerInputs) { if (input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY) { PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory(); - Preconditions.checkState( - libDir.startsWith(solibDir), - "Artifact '%s' is not under directory '%s'.", - input.getArtifact(), - solibDir); + // When COPY_DYNAMIC_LIBRARIES_TO_BINARY is enabled, dynamic libraries are not symlinked + // under solibDir, so don't check it. + 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; } diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModel.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModel.java index 10b3aea37a..42a596c992 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModel.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppModel.java @@ -1543,7 +1543,11 @@ public final class CppModel { // If shared library has neverlink=1, then leave it untouched. Otherwise, // create a mangled symlink for it and from now on reference it through // mangled name only. - if (neverLink) { + // + // When COPY_DYNAMIC_LIBRARIES_TO_BINARY is enabled, we don't need to create the special + // solibDir, instead we use the original interface library and dynamic library. + if (neverLink + || featureConfiguration.isEnabled(CppRuleClasses.COPY_DYNAMIC_LIBRARIES_TO_BINARY)) { result.addDynamicLibrary(interfaceLibrary); result.addExecutionDynamicLibrary(dynamicLibrary); } else { diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java index a4ad59ec05..97ad0f391e 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java @@ -300,6 +300,9 @@ public class CppRuleClasses { /** A string constant for a feature to disable WINDOWS_EXPORT_ALL_SYMBOLS. */ public static final String NO_WINDOWS_EXPORT_ALL_SYMBOLS = "no_windows_export_all_symbols"; + /** A string constant for a feature to copy dynamic libraries to the binary's directory. */ + public static final String COPY_DYNAMIC_LIBRARIES_TO_BINARY = "copy_dynamic_libraries_to_binary"; + /** * A string constant for a feature that indicates we are using a toolchain building for Windows. */ 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 39c4b119f5..eaa5b694d1 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 @@ -406,6 +406,9 @@ 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 STATIC_LINK_TWEAKED_CONFIGURATION = "" + "artifact_name_pattern {" diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcLibraryConfiguredTargetTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcLibraryConfiguredTargetTest.java index 98ba0c223f..0770ead00d 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcLibraryConfiguredTargetTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcLibraryConfiguredTargetTest.java @@ -1186,6 +1186,52 @@ public class CcLibraryConfiguredTargetTest extends BuildViewTestCase { } @Test + public void testCcLinkParamsHasExecutionDynamicLibraries() throws Exception { + AnalysisMock.get() + .ccSupport() + .setupCrosstool( + mockToolsConfig, MockCcSupport.COPY_DYNAMIC_LIBRARIES_TO_BINARY_CONFIGURATION); + useConfiguration("--cpu=k8", "--features=copy_dynamic_libraries_to_binary"); + ConfiguredTarget target = + scratchConfiguredTarget("a", "foo", "cc_library(name = 'foo', srcs = ['foo.cc'])"); + Iterable<Artifact> libraries = + target + .get(CcLinkParamsInfo.PROVIDER) + .getCcLinkParams(false, true) + .getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).doesNotContain("bin a/libfoo.ifso"); + assertThat(artifactsToStrings(libraries)).contains("bin a/libfoo.so"); + } + + @Test + public void testCcLinkParamsHasExecutionDynamicLibrariesWithoutCopyFeature() throws Exception { + useConfiguration("--cpu=k8"); + ConfiguredTarget target = + scratchConfiguredTarget("a", "foo", "cc_library(name = 'foo', srcs = ['foo.cc'])"); + Iterable<Artifact> libraries = + target + .get(CcLinkParamsInfo.PROVIDER) + .getCcLinkParams(false, true) + .getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).doesNotContain("bin _solib_k8/liba_Slibfoo.ifso"); + assertThat(artifactsToStrings(libraries)).contains("bin _solib_k8/liba_Slibfoo.so"); + } + + @Test + public void testCcLinkParamsDoNotHasExecutionDynamicLibraries() throws Exception { + useConfiguration("--cpu=k8"); + ConfiguredTarget target = + scratchConfiguredTarget( + "a", "foo", "cc_library(name = 'foo', srcs = ['foo.cc'], linkstatic=1)"); + Iterable<Artifact> libraries = + target + .get(CcLinkParamsInfo.PROVIDER) + .getCcLinkParams(false, true) + .getExecutionDynamicLibraries(); + assertThat(artifactsToStrings(libraries)).isEmpty(); + } + + @Test public void forbidBuildingAndWrappingSameLibraryIdentifier() throws Exception { useConfiguration("--cpu=k8"); checkError( diff --git a/src/test/py/bazel/bazel_windows_dynamic_link_test.py b/src/test/py/bazel/bazel_windows_dynamic_link_test.py index 33440cd033..1093037ad0 100644 --- a/src/test/py/bazel/bazel_windows_dynamic_link_test.py +++ b/src/test/py/bazel/bazel_windows_dynamic_link_test.py @@ -23,6 +23,7 @@ class BazelWindowsDynamicLinkTest(test_base.TestBase): self.ScratchFile('WORKSPACE') self.ScratchFile('BUILD', [ 'package(', + ' default_visibility = ["//visibility:public"],', ' features=["windows_export_all_symbols"]', ')', '', @@ -49,6 +50,7 @@ class BazelWindowsDynamicLinkTest(test_base.TestBase): ' linkstatic = 0,', ')', ]) + self.ScratchFile('a.cc', [ '#include <stdio.h>', '#include "a.h"', @@ -58,6 +60,7 @@ class BazelWindowsDynamicLinkTest(test_base.TestBase): ' printf("Hello A, %d\\n", a);', '}', ]) + self.ScratchFile('b.cc', [ '#include <stdio.h>', '#include "a.h"', @@ -88,7 +91,7 @@ class BazelWindowsDynamicLinkTest(test_base.TestBase): self.ScratchFile('b.h', [line.replace('%{name}', 'B') for line in header_temp]) - self.ScratchFile('c.cc', [ + c_cc_content = [ '#include <stdio.h>', '#include "a.h"', '#include "b.h"', @@ -103,7 +106,21 @@ class BazelWindowsDynamicLinkTest(test_base.TestBase): ' hello_C();', ' return 0;', '}', + ] + + self.ScratchFile('c.cc', c_cc_content) + + self.ScratchFile('lib/BUILD', [ + 'cc_library(', + ' name = "A",', + ' srcs = ["dummy.cc"],', + ' features = ["windows_export_all_symbols"],', + ' visibility = ["//visibility:public"],', + ')', ]) + self.ScratchFile('lib/dummy.cc', ['void dummy() {}']) + + self.ScratchFile('main/main.cc', c_cc_content) def getBazelInfo(self, info_key): exit_code, stdout, stderr = self.RunBazel(['info', info_key]) @@ -188,6 +205,90 @@ class BazelWindowsDynamicLinkTest(test_base.TestBase): # c_exe self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'C.exe'))) + def testBuildCcBinaryFromDifferentPackage(self): + self.createProjectFiles() + self.ScratchFile('main/BUILD', [ + 'cc_binary(', + ' name = "main",', + ' srcs = ["main.cc"],', + ' deps = ["//:B"],', + ' linkstatic = 0,' + ')', + ]) + bazel_bin = self.getBazelInfo('bazel-bin') + + # We dynamically link to msvcrt by setting USE_DYNAMIC_CRT=1 + exit_code, _, stderr = self.RunBazel( + ['build', '//main:main', '--action_env=USE_DYNAMIC_CRT=1']) + self.AssertExitCode(exit_code, 0, stderr) + + # Test if libA.so and libB.so are copied to the directory of main.exe + main_bin = os.path.join(bazel_bin, 'main/main.exe') + self.assertTrue(os.path.exists(main_bin)) + self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'main/libA.so'))) + self.assertTrue(os.path.exists(os.path.join(bazel_bin, 'main/libB.so'))) + + # Run the binary to see if it runs successfully + exit_code, stdout, stderr = self.RunProgram([main_bin]) + self.AssertExitCode(exit_code, 0, stderr) + self.assertEqual(['Hello A, 1', 'Hello A, 2', 'Hello B', 'Hello C'], stdout) + + def testBuildCcBinaryDependsOnConflictDLLs(self): + self.createProjectFiles() + self.ScratchFile( + 'main/BUILD', + [ + 'cc_binary(', + ' name = "main",', + ' srcs = ["main.cc"],', + ' deps = ["//:B", "//lib:A"],', # Transitively depends on //:A + ' linkstatic = 0,' + ')', + ]) + + # //main:main depends on both //lib:A and //:A, + # their dlls are both called libA.so, + # so there should be a conflict error + exit_code, _, stderr = self.RunBazel(['build', '//main:main']) + self.AssertExitCode(exit_code, 1, stderr) + self.assertIn( + 'ERROR: file \'main/libA.so\' is generated by these conflicting ' + 'actions:', + ''.join(stderr)) + + def testBuildDifferentCcBinariesDependOnConflictDLLs(self): + self.createProjectFiles() + self.ScratchFile( + 'main/BUILD', + [ + 'cc_binary(', + ' name = "main",', + ' srcs = ["main.cc"],', + ' deps = ["//:B"],', # Transitively depends on //:A + ' linkstatic = 0,' + ')', + '', + 'cc_binary(', + ' name = "other_main",', + ' srcs = ["other_main.cc"],', + ' deps = ["//lib:A"],', + ' linkstatic = 0,' + ')', + ]) + self.ScratchFile('main/other_main.cc', ['int main() {return 0;}']) + + # Building //main:main should succeed + exit_code, _, stderr = self.RunBazel(['build', '//main:main']) + self.AssertExitCode(exit_code, 0, stderr) + + # Building //main:other_main after //main:main should fail + exit_code, _, stderr = self.RunBazel(['build', '//main:other_main']) + self.AssertExitCode(exit_code, 1, stderr) + self.assertIn( + 'ERROR: file \'main/libA.so\' is generated by these conflicting ' + 'actions:', + ''.join(stderr)) + if __name__ == '__main__': unittest.main() |