Skip to content

Commit

Permalink
Regression tests binary-linkstatic-flag
Browse files Browse the repository at this point in the history
Test that a Haskell binary compiled with `linkstatic = True`, will only
link to static library dependencies (where available), and that a
Haskell binary compiled with `linkstatic = False`, will link all its
library dependencies dynamically (where available).

The test cases assume that for each library dependency both a dynamic
and a static version are available, i.e. the default of `linkstatic =
False` for library dependencies.

Uses `haskell_test` in place of `haskell_binary` to ensure that the
resulting binaries can be executed without any runtime linker errors.
  • Loading branch information
aherrmann committed Jan 17, 2019
1 parent f1cb907 commit b64d064
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 0 deletions.
168 changes: 168 additions & 0 deletions tests/binary-linkstatic-flag/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
load(
"@io_tweag_rules_haskell//haskell:haskell.bzl",
"haskell_library",
"haskell_test",
)
load("//tests:sh_inline_test.bzl", "sh_inline_test")
load(":get_library_files.bzl", "get_libraries_as_runfiles")

# test whether `linkstatic` works as expected
package(default_testonly = 1)

cc_library(
name = "c-lib",
srcs = ["c-lib.c"],
)

haskell_library(
name = "CLib",
srcs = ["CLib.hsc"],
deps = [
":c-lib",
"@hackage//:base",
],
)

haskell_library(
name = "HsLib",
srcs = ["HsLib.hs"],
deps = [
"@hackage//:base",
],
)

haskell_test(
name = "binary-static",
srcs = ["Main.hs"],
linkstatic = True,
deps = [
":CLib",
":HsLib",
"@hackage//:base",
],
)

haskell_test(
name = "binary-dynamic",
srcs = ["Main.hs"],
linkstatic = False,
deps = [
":CLib",
":HsLib",
"@hackage//:base",
],
)

# extract all libraries from the Haskell binary
get_libraries_as_runfiles(
name = "binary-static-libraries",
binary = ":binary-static",
)

get_libraries_as_runfiles(
name = "binary-dynamic-libraries",
binary = ":binary-dynamic",
)

# sh_test’s `data` doesn’t add stuff to runfiles :(
# sh_library can bundle different targets as runfiles for sh_test
# TODO(Profpatsch): add functionality to sh_inline_test by default?
sh_library(
name = "bundled-dependency-files-static",
data = [":binary-static-libraries"],
)

sh_library(
name = "bundled-dependency-files-dynamic",
data = [":binary-dynamic-libraries"],
)

# The dynamic libraries that the binary references
genrule(
name = "binary-static-ldd",
srcs = [":binary-static"],
outs = ["binary-static-ldd.txt"],
cmd = """
objdump -x $(SRCS) | sed -ne 's/^ NEEDED\s*//p' > $(OUTS)
""",
)

genrule(
name = "binary-dynamic-ldd",
srcs = [":binary-dynamic"],
outs = ["binary-dynamic-ldd.txt"],
cmd = """
objdump -x $(SRCS) | sed -ne 's/^ NEEDED\s*//p' > $(OUTS)
""",
)

# ensure that linkstatic=True only links to static library targets.
sh_inline_test(
name = "test-binary-static-only",
size = "small",
args = [
"$(rootpath :binary-static-ldd)",
# pass the file names as arguments
"$(rootpaths :binary-static-libraries)",
],
data = [
# for rootpaths
":binary-static-ldd",
":binary-static-libraries",
# to actually get the files …
":bundled-dependency-files-static",
],
script = """
set -euo pipefail
ldd=$1
shift 1
for f in "$@"; do
if [[ "$f" =~ .so$ || "$f" =~ .dylib$ ]]; then
if grep -q "$(basename "$f")" "$ldd"; then
echo "static binary should not depend on dynamic library $f" >&2
exit 1
fi
fi
done
""",
)

# ensure that linkstatic=False links to dynamic library targets.
sh_inline_test(
name = "test-binary-dynamic-only",
size = "small",
args = [
"$(rootpath :binary-dynamic-ldd)",
# pass the file names as arguments
"$(rootpaths :binary-dynamic-libraries)",
],
data = [
# for rootpaths
":binary-dynamic-ldd",
":binary-dynamic-libraries",
# to actually get the files …
":bundled-dependency-files-dynamic",
],
script = """
set -euo pipefail
ldd=$1
shift 1
for f in "$@"; do
if [[ "$f" =~ .so$ || "$f" =~ .dylib$ ]]; then
if ! grep -q "$(basename "$f")" "$ldd"; then
echo "dynamic binary should depend on dynamic library $f" >&2
exit 1
fi
fi
done
""",
)

test_suite(
name = "binary-linkstatic-flag",
tests = [
":test-binary-dynamic-only",
":test-binary-static-only",
],
visibility = ["//visibility:public"],
)
3 changes: 3 additions & 0 deletions tests/binary-linkstatic-flag/CLib.hsc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module CLib (value) where

foreign import ccall "value" value :: Int
4 changes: 4 additions & 0 deletions tests/binary-linkstatic-flag/HsLib.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module HsLib (value) where

value :: Int
value = 13
6 changes: 6 additions & 0 deletions tests/binary-linkstatic-flag/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Main (main) where

import qualified CLib
import qualified HsLib

main = print $ HsLib.value + CLib.value
1 change: 1 addition & 0 deletions tests/binary-linkstatic-flag/c-lib.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int value() { return 29; }
32 changes: 32 additions & 0 deletions tests/binary-linkstatic-flag/get_library_files.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
load(
"@io_tweag_rules_haskell//haskell:private/providers.bzl",
"HaskellBinaryInfo",
"HaskellBuildInfo",
)
load("//haskell:private/set.bzl", "set")

def _get_libraries_as_runfiles_impl(ctx):
"""Extract all library files from a Haskell binary target
and put them in this target’s files"""
bi = ctx.attr.binary[HaskellBuildInfo]
external_libs = [
lib.mangled_lib
for lib in set.to_list(bi.external_libraries)
]
return [DefaultInfo(
# not necessarily complete
files = depset(
direct = external_libs + bi.static_libraries,
transitive = [set.to_depset(bi.dynamic_libraries)],
),
)]

get_libraries_as_runfiles = rule(
_get_libraries_as_runfiles_impl,
attrs = {
"binary": attr.label(
mandatory = True,
providers = [HaskellBuildInfo, HaskellBinaryInfo],
),
},
)

0 comments on commit b64d064

Please sign in to comment.