Skip to content

Commit 063e8a4

Browse files
committed
Add Cabal binary RUNPATH test case
Regression test for Cabal binary targets not including RUNPATH entries for transitive C library dependencies.
1 parent 75ca83a commit 063e8a4

File tree

3 files changed

+101
-52
lines changed

3 files changed

+101
-52
lines changed

tests/stackage_zlib_runpath/BUILD.bazel

+86-52
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
load(
2+
"@rules_haskell//haskell:cabal.bzl",
3+
"haskell_cabal_binary",
4+
)
15
load(
26
"//tests:inline_tests.bzl",
37
"py_inline_test",
@@ -19,12 +23,23 @@ dynamic_libraries(
1923
tags = ["requires_zlib"],
2024
)
2125

22-
# Tests that haskell_cabal_library will generate a relative RUNPATH entry for
23-
# the dependency on the nixpkgs provided libz. Relative meaning an entry that
24-
# starts with $ORIGIN (Linux) or @loader_path (MacOS). The alternative is an
25-
# absolute path, which would be wrong for the nixpkgs provided libz, as we want
26-
# the RUNPATH entry to point to Bazel's _solib_<cpu> directory and its absolute
27-
# path depends on the output root or execroot.
26+
haskell_cabal_binary(
27+
name = "cabal-binary",
28+
srcs = glob(["cabal-binary/**"]),
29+
tags = ["requires_zlib"],
30+
deps = [
31+
"//tests/hackage:base",
32+
# Depend transitively on libz.
33+
"@stackage-zlib//:zlib",
34+
],
35+
)
36+
37+
# Tests that haskell_cabal_library|binary will generate a relative RUNPATH
38+
# entry for the dependency on the nixpkgs provided libz. Relative meaning an
39+
# entry that starts with $ORIGIN (Linux) or @loader_path (MacOS). The
40+
# alternative is an absolute path, which would be wrong for the nixpkgs
41+
# provided libz, as we want the RUNPATH entry to point to Bazel's _solib_<cpu>
42+
# directory and its absolute path depends on the output root or execroot.
2843
#
2944
# It uses :libz_soname generated above to determine the expected RUNPATH entry
3045
# for the libz dependency. The :libz_soname file will contain the file names of
@@ -33,17 +48,22 @@ dynamic_libraries(
3348
# It uses :libHSzlib to access the dynamic library output of
3449
# haskell_cabal_library and read the RUNPATH entries.
3550
#
36-
# Note, ideally we would test that haskell_cabal_library _only_ generates a
37-
# relative RUNPATH entry and no absolute entries that leak the execroot into
38-
# the cache. Unfortunately, haskell_cabal_library generates such an entry at
39-
# the moment. See https://github.com/tweag/rules_haskell/issues/1130.
51+
# It uses :cabal-binary to access a binary that transitively depends on libz.
52+
#
53+
# Note, ideally we would test that haskell_cabal_library|binary _only_
54+
# generates a relative RUNPATH entry and no absolute entries that leak the
55+
# execroot into the cache. Unfortunately, haskell_cabal_library|binary
56+
# generates such an entry at the moment. See
57+
# https://github.com/tweag/rules_haskell/issues/1130.
4058
py_inline_test(
4159
name = "stackage_zlib_runpath",
4260
args = [
4361
"$(rootpath :libz_soname)",
4462
"$(rootpath :libHSzlib)",
63+
"$(rootpath :cabal-binary)",
4564
],
4665
data = [
66+
":cabal-binary",
4767
":libHSzlib",
4868
":libz_soname",
4969
],
@@ -65,57 +85,71 @@ with open(libz_soname) as fh:
6585
sofile = fh.read().splitlines()[1]
6686
sodir = os.path.dirname(sofile)
6787
68-
# Determine libHSzlib RUNPATH
88+
# Locate test artifacts.
6989
libHSzlib = r.Rlocation(os.path.join(
7090
os.environ["TEST_WORKSPACE"],
7191
sys.argv[2],
7292
))
73-
runpaths = []
74-
if platform.system() == "Darwin":
75-
dynamic_section = iter(subprocess.check_output(["otool", "-l", libHSzlib]).decode().splitlines())
76-
# otool produces lines of the form
77-
#
78-
# Load command ...
79-
# cmd LC_RPATH
80-
# cmdsize ...
81-
# path ...
82-
#
83-
for line in dynamic_section:
84-
# Find LC_RPATH entry
85-
if line.find("cmd LC_RPATH") != -1:
86-
break
87-
# Skip until path field
93+
cabal_binary = r.Rlocation(os.path.join(
94+
os.environ["TEST_WORKSPACE"],
95+
sys.argv[3],
96+
))
97+
98+
def read_runpaths(binary):
99+
runpaths = []
100+
if platform.system() == "Darwin":
101+
dynamic_section = iter(subprocess.check_output(["otool", "-l", binary]).decode().splitlines())
102+
# otool produces lines of the form
103+
#
104+
# Load command ...
105+
# cmd LC_RPATH
106+
# cmdsize ...
107+
# path ...
108+
#
88109
for line in dynamic_section:
89-
if line.strip().startswith("path"):
110+
# Find LC_RPATH entry
111+
if line.find("cmd LC_RPATH") != -1:
90112
break
91-
runpaths.append(line.split()[1])
92-
else:
93-
dynamic_section = subprocess.check_output(["objdump", "--private-headers", libHSzlib]).decode().splitlines()
94-
# objdump produces lines of the form
95-
#
96-
# Dynamic Section:
97-
# ...
98-
# RUNPATH ...
99-
# ...
100-
for line in dynamic_section:
101-
if not line.strip().startswith("RUNPATH"):
113+
# Skip until path field
114+
for line in dynamic_section:
115+
if line.strip().startswith("path"):
116+
break
117+
runpaths.append(line.split()[1])
118+
else:
119+
dynamic_section = subprocess.check_output(["objdump", "--private-headers", binary]).decode().splitlines()
120+
# objdump produces lines of the form
121+
#
122+
# Dynamic Section:
123+
# ...
124+
# RUNPATH ...
125+
# ...
126+
for line in dynamic_section:
127+
if not line.strip().startswith("RUNPATH"):
128+
continue
129+
runpaths.extend(line.split()[1].split(":"))
130+
131+
return runpaths
132+
133+
def test_binary(binary, sodir):
134+
runpaths = read_runpaths(binary)
135+
# Check that the binary contains a relative RUNPATH for sodir.
136+
found = False
137+
for runpath in runpaths:
138+
if runpath.find(sodir) == -1:
102139
continue
103-
runpaths.extend(line.split()[1].split(":"))
140+
if runpath.startswith("$ORIGIN") or runpath.startswith("@loader_path"):
141+
found = True
142+
# XXX: Enable once #1130 is fixed.
143+
#if os.path.isabs(runpath):
144+
# print("Absolute RUNPATH entry discovered for %s: %s" % (sodir, runpath))
145+
# sys.exit(1)
146+
147+
if not found:
148+
print("Did not find a relative RUNPATH entry for %s among %s." % (sodir, runpaths))
104149
105-
# Check that the binary contains a relative RUNPATH for sodir.
106-
found = False
107-
for runpath in runpaths:
108-
if runpath.find(sodir) == -1:
109-
continue
110-
if runpath.startswith("$ORIGIN") or runpath.startswith("@loader_path"):
111-
found = True
112-
# XXX: Enable once #1130 is fixed.
113-
#if os.path.isabs(runpath):
114-
# print("Absolute RUNPATH entry discovered for %s: %s" % (sodir, runpath))
115-
# sys.exit(1)
150+
return found
116151
117-
if not found:
118-
print("Did not find a relative RUNPATH entry for %s among %s." % (sodir, runpaths))
152+
if not all(test_binary(binary, sodir) for binary in [libHSzlib, cabal_binary]):
119153
sys.exit(1)
120154
""",
121155
tags = ["requires_zlib"],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module Main where
2+
3+
main :: IO ()
4+
main = pure ()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cabal-version: >=1.10
2+
name: cabal-binary
3+
version: 0.1.0.0
4+
build-type: Simple
5+
6+
executable cabal-binary
7+
build-depends:
8+
base,
9+
zlib
10+
default-language: Haskell2010
11+
main-is: Main.hs

0 commit comments

Comments
 (0)