Skip to content

Commit 134cd1e

Browse files
committed
Add a regression test
Adds a regression test to check that haskell_cabal_library will generate a relative `RUNPATH` entry for the nixpkgs provided zlib.
1 parent 2d85306 commit 134cd1e

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed
+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
load(
2+
"//tests:inline_tests.bzl",
3+
"py_inline_test",
4+
)
5+
load("dynamic_libraries.bzl", "dynamic_libraries")
6+
7+
dynamic_libraries(
8+
name = "libz",
9+
srcs = ["@zlib.dev//:zlib"],
10+
filter = "libz",
11+
solib_names = "libz_soname",
12+
)
13+
14+
dynamic_libraries(
15+
name = "libHSzlib",
16+
srcs = ["@stackage-zlib//:zlib"],
17+
filter = "libHSz",
18+
)
19+
20+
# This test case tests that haskell_cabal_library will generate a relative
21+
# RUNPATH entry for the dependency on the nixpkgs provided libz. Relative
22+
# meaning an entry that starts with $ORIGIN (Linux) or @loader_path (MacOS).
23+
# The alternative is an absolute path, which would be wrong for the nixpkgs
24+
# provided libz, as we want the RUNPATH entry to point to Bazel's _solib_<cpu>
25+
# directory and its absolute path depends on the output root or execroot.
26+
#
27+
# It uses :libz_soname generated above to determine the expected RUNPATH entry
28+
# for the libz dependency. The :libz_soname file will contain the file names of
29+
# the libz library files underneath the `_solib_<cpu>` directory.
30+
#
31+
# It uses :libHSzlib to access the dynamic library output of
32+
# haskell_cabal_library and read the RUNPATH entries.
33+
#
34+
# Note, ideally we would test that haskell_cabal_library _only_ generates a
35+
# relative RUNPATH entry and no absolute entries that leak the execroot into
36+
# the cache. Unfortunately, haskell_cabal_library generates such an entry at
37+
# the moment. See https://github.com/tweag/rules_haskell/issues/1130.
38+
py_inline_test(
39+
name = "stackage_zlib_runpath",
40+
args = [
41+
"$(rootpath :libz_soname)",
42+
"$(rootpath :libHSzlib)",
43+
],
44+
data = [
45+
":libHSzlib",
46+
":libz_soname",
47+
],
48+
script = """\
49+
from bazel_tools.tools.python.runfiles import runfiles as bazel_runfiles
50+
import itertools
51+
import os
52+
import platform
53+
import subprocess
54+
import sys
55+
r = bazel_runfiles.Create()
56+
57+
# Determine libz solib directory
58+
libz_soname = r.Rlocation(os.path.join(
59+
os.environ["TEST_WORKSPACE"],
60+
sys.argv[1],
61+
))
62+
with open(libz_soname) as fh:
63+
sofile = fh.read().splitlines()[1]
64+
sodir = os.path.dirname(sofile)
65+
66+
# Determine libHSzlib RUNPATH
67+
libHSzlib = r.Rlocation(os.path.join(
68+
os.environ["TEST_WORKSPACE"],
69+
sys.argv[2],
70+
))
71+
runpaths = []
72+
if platform.system() == "Darwin":
73+
dynamic_section = iter(subprocess.check_output(["otool", "-l", libHSzlib]).decode().splitlines())
74+
# otool produces lines of the form
75+
#
76+
# Load command ...
77+
# cmd LC_RPATH
78+
# cmdsize ...
79+
# path ...
80+
#
81+
for line in dynamic_section:
82+
# Find LC_RPATH entry
83+
if line.find("cmd LC_RPATH") != -1:
84+
break
85+
# Skip until path field
86+
for line in dynamic_section:
87+
if line.strip().startswith("path"):
88+
break
89+
runpaths.append(line.split()[1])
90+
else:
91+
dynamic_section = subprocess.check_output(["objdump", "--private-headers", libHSzlib]).decode().splitlines()
92+
# objdump produces lines of the form
93+
#
94+
# Dynamic Section:
95+
# ...
96+
# RUNPATH ...
97+
# ...
98+
for line in dynamic_section:
99+
if not line.strip().startswith("RUNPATH"):
100+
continue
101+
runpaths.extend(line.split()[1].split(":"))
102+
103+
# Check that the binary contains a relative RUNPATH for sodir.
104+
found = False
105+
for runpath in runpaths:
106+
if runpath.find(sodir) == -1:
107+
continue
108+
if runpath.startswith("$ORIGIN") or runpath.startswith("@loader_path"):
109+
found = True
110+
# XXX: Enable once #1130 is fixed.
111+
#if os.path.isabs(runpath):
112+
# print("Absolute RUNPATH entry discovered for %s: %s" % (sodir, runpath))
113+
# sys.exit(1)
114+
115+
if not found:
116+
print("Did not find a relative RUNPATH entry for %s among %s." % (sodir, runpaths))
117+
sys.exit(1)
118+
""",
119+
tags = ["requires_zlib"],
120+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
def _dynamic_libraries_impl(ctx):
2+
outputs = []
3+
solib_names = []
4+
for target in ctx.attr.srcs:
5+
cc_info = target[CcInfo]
6+
for library_to_link in cc_info.linking_context.libraries_to_link.to_list():
7+
library = library_to_link.resolved_symlink_dynamic_library
8+
if not library or library.basename.find(ctx.attr.filter) == -1:
9+
continue
10+
outputs.append(library)
11+
if library_to_link.dynamic_library:
12+
solib_names.append(library_to_link.dynamic_library.short_path)
13+
if ctx.attr.solib_names:
14+
ctx.actions.write(
15+
ctx.outputs.solib_names,
16+
"\n".join(solib_names),
17+
)
18+
return [DefaultInfo(
19+
files = depset(outputs),
20+
runfiles = ctx.runfiles(files = outputs),
21+
)]
22+
23+
dynamic_libraries = rule(
24+
_dynamic_libraries_impl,
25+
attrs = {
26+
"filter": attr.string(
27+
doc = "Skip libraries that do not contain this string in their name.",
28+
),
29+
"srcs": attr.label_list(
30+
doc = "Extract dynamic libraries from these targets",
31+
providers = [CcInfo],
32+
),
33+
"solib_names": attr.output(
34+
doc = "Write the `_solib_<cpu>` paths of the dynamic libraries to this file.",
35+
),
36+
},
37+
doc = "Extract the dynamic libraries from cc_library targets.",
38+
)

0 commit comments

Comments
 (0)