Skip to content

Commit 28ce041

Browse files
authored
Cover all cases for rpath generation (#4471)
**What type of PR is this?** Bug fix **What does this PR do? Why is it needed?** This is based on the corresponding logic in rules_cc. **Which issues(s) does this PR fix?** Fixes #4451 **Other notes for review**
1 parent 807c5de commit 28ce041

File tree

3 files changed

+162
-19
lines changed

3 files changed

+162
-19
lines changed

go/private/rpath.bzl

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,41 @@ load(
1717
"paths",
1818
)
1919

20-
# TODO: Replace with a Bazel-provided function when it's available.
21-
# https://github.com/bazelbuild/bazel/issues/14307
22-
def _rlocation_path(ctx, file):
23-
"""Returns the path relative to the runfiles directory."""
24-
if file.short_path.startswith("../"):
25-
return file.short_path[3:]
26-
else:
27-
return ctx.workspace_name + "/" + file.short_path
28-
2920
def _rpath(go, library, executable = None):
3021
"""Returns the potential rpaths of a library, possibly relative to another file."""
3122
if not executable:
3223
return [paths.dirname(library.short_path)]
3324

34-
origin = go.mode.goos == "darwin" and "@loader_path" or "$ORIGIN"
25+
origin = "@loader_path" if go.mode.goos == "darwin" else "$ORIGIN"
3526

36-
# Accomodate for two kinds of executable paths.
27+
# Accommodate for three kinds of executable paths.
3728
rpaths = []
29+
library_dir = paths.dirname(library.short_path)
30+
31+
# Based on the logic for Bazel's own C++ rules:
32+
# https://github.com/bazelbuild/bazel/blob/51a4b8e5de225ba163d19ddcc330aff8860a1520/src/main/starlark/builtins_bzl/common/cc/link/collect_solib_dirs.bzl#L301
33+
# with the bug fix https://github.com/bazelbuild/bazel/pull/27154.
34+
# We ignore the cases for --experimental_sibling_repository_layout.
3835

3936
# 1. Where the executable is inside its own .runfiles directory.
40-
# This is the case for generated libraries as well as remote builds.
41-
# a) go back to the runfiles root from the executable file in .runfiles
42-
depth = _rlocation_path(go._ctx, executable).count("/")
37+
# This covers the cases 1, 3, 4, 5, and 7 in the linked code above.
38+
# a) go back to the workspace root from the executable file in .runfiles
39+
depth = executable.short_path.count("/")
4340
back_to_root = paths.join(*([".."] * depth))
4441

45-
# b) then walk back to the library's dir within runfiles dir.
46-
rpaths.append(paths.join(origin, back_to_root, paths.dirname(_rlocation_path(go._ctx, library))))
42+
# b) then walk back to the library's short path
43+
rpaths.append(paths.join(origin, back_to_root, library_dir))
4744

4845
# 2. Where the executable is outside the .runfiles directory:
49-
# This is the case for local pre-built libraries, as well as local
50-
# generated libraries.
46+
# This covers the cases 2 and 6 in the linked code above.
5147
runfiles_dir = paths.basename(executable.short_path) + ".runfiles"
52-
rpaths.append(paths.join(origin, runfiles_dir, go._ctx.workspace_name, paths.dirname(library.short_path)))
48+
rpaths.append(paths.join(origin, runfiles_dir, go._ctx.workspace_name, library_dir))
49+
50+
# 3. Where the executable is from a different repo
51+
# This covers the case 8 in the linked code above.
52+
if executable.short_path.startswith("../"):
53+
back_to_repo_root = paths.join(*([".."] * (depth - 1)))
54+
rpaths.append(paths.join(origin, back_to_repo_root, go._ctx.workspace_name, library_dir))
5355

5456
return rpaths
5557

tests/core/cgo/BUILD.bazel

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,15 @@ go_test(
541541
srcs = ["cgo_required_test.go"],
542542
pure = "on",
543543
)
544+
545+
go_bazel_test(
546+
name = "wrapped_cgo_test",
547+
srcs = ["wrapped_cgo_test.go"],
548+
target_compatible_with = select({
549+
# TODO: Try to enable this test on macOS when the minimum version
550+
# version of Bazel sets the correct install_name.
551+
# "@platforms//os:macos": [],
552+
"@platforms//os:linux": [],
553+
"//conditions:default": ["@platforms//:incompatible"],
554+
}),
555+
)

tests/core/cgo/wrapped_cgo_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package wrapped_cgo_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/bazelbuild/rules_go/go/tools/bazel_testing"
7+
)
8+
9+
func TestMain(m *testing.M) {
10+
bazel_testing.TestMain(m, bazel_testing.Args{
11+
Main: `
12+
-- wrapped_test.bzl --
13+
CODE = """#!/usr/bin/env bash
14+
echo "Running wrapped test {0}"
15+
{0} $@
16+
"""
17+
def _wrapped_test_impl(ctx):
18+
out = ctx.actions.declare_file(ctx.label.name + ".sh")
19+
ctx.actions.write(out, CODE.format(ctx.executable.test.short_path))
20+
return [DefaultInfo(
21+
executable = out,
22+
default_runfiles = ctx.attr.test[DefaultInfo].default_runfiles.merge(ctx.runfiles(files=[out])),
23+
)]
24+
25+
wrapped_test = rule(
26+
implementation = _wrapped_test_impl,
27+
attrs = {
28+
"test": attr.label(mandatory = True, executable = True, cfg = "target"),
29+
# Required for Bazel to collect coverage of instrumented C/C++ binaries
30+
# executed by go_test.
31+
# This is just a shell script and thus cheap enough to depend on
32+
# unconditionally.
33+
"_collect_cc_coverage": attr.label(
34+
default = "@bazel_tools//tools/test:collect_cc_coverage",
35+
cfg = "exec",
36+
),
37+
# Required for Bazel to merge coverage reports for Go and other
38+
# languages into a single report per test.
39+
# Using configuration_field ensures that the tool is only built when
40+
# run with bazel coverage, not with bazel test.
41+
"_lcov_merger": attr.label(
42+
default = configuration_field(fragment = "coverage", name = "output_generator"),
43+
cfg = "exec",
44+
),
45+
},
46+
test = True,
47+
)
48+
-- BUILD.bazel --
49+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
50+
load("//:wrapped_test.bzl", "wrapped_test")
51+
52+
cc_library(
53+
name = "hello",
54+
srcs = ["hello.c"],
55+
hdrs = ["hello.h"],
56+
visibility = ["//visibility:public"],
57+
)
58+
59+
cc_shared_library(
60+
name = "hello_shared_lib",
61+
deps = [":hello"],
62+
)
63+
64+
# This weird dance is necessary to force rules_go to link against a shared library.
65+
cc_library(
66+
name = "hello_shared",
67+
srcs = [":hello_shared_lib"],
68+
hdrs = ["hello.h"],
69+
)
70+
71+
go_library(
72+
name = "go_hello",
73+
srcs = ["hello.go"],
74+
cgo = True,
75+
cdeps = [":hello_shared"],
76+
importpath = "example.com/hello",
77+
visibility = ["//visibility:public"],
78+
)
79+
80+
go_test(
81+
name = "go_hello_test",
82+
srcs = ["hello_test.go"],
83+
deps = [":go_hello"],
84+
pure = "off",
85+
static = "off",
86+
)
87+
88+
wrapped_test(
89+
name = "wrapped_go_hello_test",
90+
test = ":go_hello_test",
91+
)
92+
-- hello.h --
93+
void hello();
94+
-- hello.c --
95+
#include "hello.h"
96+
#include <stdio.h>
97+
void hello() {
98+
printf("Hello, World!\n");
99+
}
100+
-- hello_test.go --
101+
package hello_test
102+
import (
103+
"testing"
104+
105+
"example.com/hello"
106+
)
107+
func TestHello(t *testing.T) {
108+
hello.Hello()
109+
}
110+
111+
-- hello.go --
112+
package hello
113+
114+
// #include "hello.h"
115+
import "C"
116+
func Hello() {
117+
C.hello()
118+
}
119+
`,
120+
})
121+
}
122+
123+
func TestWrappedCgo(t *testing.T) {
124+
if o, err := bazel_testing.BazelOutput("test", "//:wrapped_go_hello_test", "--test_env=GO_TEST_WRAP_TESTV=1", "--test_output=all", "--experimental_cc_shared_library"); err != nil {
125+
t.Fatalf("bazel test //:wrapped_go_hello_test: %v\n%s", err, string(o))
126+
} else {
127+
t.Logf("bazel test //:wrapped_go_hello_test: %s", string(o))
128+
}
129+
}

0 commit comments

Comments
 (0)