Skip to content

Commit cdf9913

Browse files
novas0x2acopybara-github
authored andcommitted
fix copy_dynamic_libraries_to_binary for subdirectories
Copybara Import from #623 BEGIN_PUBLIC fix copy_dynamic_libraries_to_binary for subdirectories (#623) This is a new instance of a similar problem described in bazelbuild/bazel#12448. In order for windows binaries to run, the (non-system) dlls need to be copied to the same directory. However, the code that determined that was only looking at the package label and the workspace, but not the actual subdirectory. Previously, this was fixed by adding the `lib.is_source` check, but this only solves it for source files. An example of code that can trigger this is https://github.com/bazel-contrib/rules_foreign_cc/blob/d8e34f6f9b66a13cd845011200d444b060dc1af1/examples/cmake_hello_world_lib/shared/BUILD.bazel Closes #623 END_PUBLIC COPYBARA_INTEGRATE_REVIEW=#623 from novas0x2a:fix-windows-dlls 83bc49c PiperOrigin-RevId: 885077676 Change-Id: Ic94fb3f7488b4ba28e9fb13a7ed68657f70c3a25
1 parent d9d7798 commit cdf9913

3 files changed

Lines changed: 118 additions & 19 deletions

File tree

.bazelci/presubmit.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ test_targets_bazel_6: &test_targets_bazel_6
3030
- "-//cc:optional_current_cc_toolchain" # Not supported in Bazel 6
3131
- "-//tests/rule_based_toolchain/tool_map:_duplicate_action_test_subject" # Intentionally broken rule.
3232
- "-//tests/system_library:system_library_test" # Should be skipped on Windows and MacOS
33+
- "-//tests/cc/common:test_runtime_dynamic_libraries_copy_behavior" # known bug, see https://github.com/bazelbuild/rules_cc/pull/623
3334

3435
buildifier:
3536
version: latest

cc/private/rules_impl/cc_binary_impl.bzl

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -176,24 +176,13 @@ def _collect_runfiles(ctx, feature_configuration, cc_toolchain, libraries, cc_li
176176

177177
return (builder.merge(ctx.runfiles(files = builder_artifacts, transitive_files = depset(builder_transitive_artifacts))), runtime_objects_for_coverage)
178178

179-
def _get_target_sub_dir(target_name):
180-
last_separator = target_name.rfind("/")
181-
if last_separator == -1:
182-
return ""
183-
return target_name[0:last_separator]
184-
185-
def _create_dynamic_libraries_copy_actions(ctx, dynamic_libraries_for_runtime):
179+
def _create_dynamic_libraries_copy_actions(ctx, binary, dynamic_libraries_for_runtime):
186180
result = []
187181
for lib in dynamic_libraries_for_runtime:
188-
# If the binary and the DLL don't belong to the same package or the DLL is a source file,
189-
# we should copy the DLL to the binary's directory.
190-
if ctx.label.package != lib.owner.package or ctx.label.workspace_name != lib.owner.workspace_name or lib.is_source:
191-
target_name = ctx.label.name
192-
target_sub_dir = _get_target_sub_dir(target_name)
193-
copy_file_path = lib.basename
194-
if target_sub_dir != "":
195-
copy_file_path = target_sub_dir + "/" + copy_file_path
196-
copy = ctx.actions.declare_file(copy_file_path)
182+
# If the binary and the DLL are not in the same directory, copy the DLL
183+
# to the binary's directory.
184+
if lib.dirname != binary.dirname:
185+
copy = ctx.actions.declare_file(lib.basename, sibling = binary)
197186
ctx.actions.symlink(output = copy, target_file = lib, progress_message = "Copying Execution Dynamic Library")
198187
result.append(copy)
199188
else:
@@ -740,7 +729,7 @@ def cc_binary_impl(ctx, additional_linkopts, force_linkstatic = False):
740729
libraries = []
741730
for linker_input in linker_inputs:
742731
libraries.extend(linker_input.libraries)
743-
copied_runtime_dynamic_libraries = _create_dynamic_libraries_copy_actions(ctx, _get_dynamic_libraries_for_runtime(is_static_mode, libraries))
732+
copied_runtime_dynamic_libraries = _create_dynamic_libraries_copy_actions(ctx, binary, _get_dynamic_libraries_for_runtime(is_static_mode, libraries))
744733

745734
# TODO(b/198254254)(bazel-team): Do we need to put original shared libraries (along with
746735
# mangled symlinks) into the RunfilesSupport object? It does not seem

tests/cc/common/cc_binary_configured_target_tests.bzl

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Tests for cc_binary."""
22

3-
load("@rules_testing//lib:analysis_test.bzl", "test_suite")
3+
load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
44
load("@rules_testing//lib:truth.bzl", "matching")
5-
load("@rules_testing//lib:util.bzl", "util")
5+
load("@rules_testing//lib:util.bzl", "TestingAspectInfo", "util")
66
load("//cc:cc_binary.bzl", "cc_binary")
7+
load("//cc:cc_import.bzl", "cc_import")
8+
load("//cc:cc_library.bzl", "cc_library")
79
load("//tests/cc/testutil:cc_analysis_test.bzl", "cc_analysis_test")
810
load("//tests/cc/testutil:cc_binary_target_subject.bzl", "cc_binary_target_subject")
911
load("//tests/cc/testutil:link_action_subject.bzl", "link_action_subject")
@@ -125,6 +127,112 @@ def _test_action_graph_impl(env, target):
125127

126128
# TODO: Test stripped action
127129

130+
def _make_dll(name):
131+
# make a fake dll so the dll shows up in the output directory (where the
132+
# binary will be) instead of as a source file (the way cc_import on its own would)
133+
# Example taken from
134+
# https://github.com/bazelbuild/bazel/blob/f720ed385e245e292b0afe19ebd84e4283c30565/examples/windows/dll/windows_dll_library.bzl
135+
dll = name + ".dll"
136+
mask = name + "_mask"
137+
138+
util.helper_target(
139+
cc_binary,
140+
name = dll,
141+
srcs = ["hello.cc"],
142+
linkshared = 1,
143+
)
144+
145+
# Mask the cc_binary behind a cc_import so we can depend on it as a library
146+
util.helper_target(
147+
cc_import,
148+
name = mask,
149+
shared_library = dll,
150+
)
151+
152+
# cc_imports are always source files, so make it a generated file again
153+
cc_library(
154+
name = name,
155+
deps = [mask],
156+
)
157+
158+
def _test_runtime_dynamic_libraries_copy_behavior(name, **kwargs):
159+
sub_dir_lib = name + "/dst/sub/sub_dir_lib"
160+
same_dir_lib = name + "/dst/same_dir_lib"
161+
binary_target = name + "/dst/hello"
162+
163+
# project a dll into the same directory as the binary, and a second one in
164+
# a subdirectory
165+
_make_dll(same_dir_lib)
166+
_make_dll(sub_dir_lib)
167+
168+
util.helper_target(
169+
cc_binary,
170+
name = binary_target,
171+
srcs = ["hello.cc"],
172+
deps = [
173+
same_dir_lib,
174+
sub_dir_lib,
175+
],
176+
linkstatic = False,
177+
)
178+
179+
analysis_test(
180+
name = name,
181+
impl = _test_runtime_dynamic_libraries_copy_behavior_impl,
182+
target = binary_target,
183+
# TODO: This would be better-done with the mock toolchain, once that is
184+
# wired up
185+
attrs = {
186+
"copy_feature_supported": attr.bool(),
187+
},
188+
attr_values = {
189+
"copy_feature_supported": select({
190+
# copy_dynamic_libraries_to_binary is only defined in the
191+
# windows toolchain currently
192+
"@platforms//os:windows": True,
193+
"//conditions:default": False,
194+
}),
195+
"size": "small",
196+
},
197+
**kwargs
198+
)
199+
200+
def _test_runtime_dynamic_libraries_copy_behavior_impl(env, target):
201+
if not env.ctx.attr.copy_feature_supported:
202+
return
203+
204+
test_name = env.ctx.label.name
205+
206+
expected_copied_library = "tests/cc/common/{name}/dst/sub_dir_lib.dll".format(
207+
name = test_name,
208+
)
209+
expected_same_dir_library = "tests/cc/common/{name}/dst/same_dir_lib.dll".format(
210+
name = test_name,
211+
)
212+
213+
# Both libraries should be listed as runtime dependencies, but...
214+
expected_libraries = [expected_copied_library, expected_same_dir_library]
215+
216+
env.expect \
217+
.that_target(target) \
218+
.output_group("runtime_dynamic_libraries") \
219+
.contains_exactly(expected_libraries)
220+
221+
actions = {}
222+
for action in target[TestingAspectInfo].actions:
223+
if action.mnemonic != "Symlink":
224+
continue
225+
226+
for output in action.outputs.to_list():
227+
if output.short_path in expected_libraries:
228+
actions[output.short_path] = action
229+
230+
# ... only one of the libraries should have a Symlink action (Copying
231+
# Execution Dynamic Library) since the other one should already be in the
232+
# same dir
233+
expected_copies = [expected_copied_library]
234+
env.expect.that_dict(actions).keys().contains_exactly(expected_copies)
235+
128236
def cc_binary_configured_target_tests(name):
129237
test_suite(
130238
name = name,
@@ -133,5 +241,6 @@ def cc_binary_configured_target_tests(name):
133241
_test_headers_not_passed_to_linking_action,
134242
_test_no_duplicate_linkopts,
135243
_test_action_graph,
244+
_test_runtime_dynamic_libraries_copy_behavior,
136245
],
137246
)

0 commit comments

Comments
 (0)