Skip to content

Commit

Permalink
Update to Bazel 0.22 C++ API
Browse files Browse the repository at this point in the history
Bazel 0.22 contains further updates to the new C++ API:
bazelbuild/bazel#4570

It seems these updates removed access to the unmangled library names for
C library dependencies. On MacOS, dynamic libraries contain the path to
the unmangled library relative to the working directory in their
"library name", which means that targets linking against these dynamic
libraries will reference the unmangled target.

These references are invalid if a binary is executed in a different
working directory, and are unnecessarily long, which makes it easy to
exceed the MACH-O header size limit on MacOS.

Previously, rules_haskell would patch these load commands based on the
unmangled libraries to point to the mangled libraries under a single
RPATH entry. This shortened the load commands. Now that the unmangled
libraries are no longer available, this patching is performed in a
[linker wrapper script][osx_wrapper], similar to how Bazel itself
handles this.

Furthermore, GHC generates intermediate dynamic libraries which will try
to load the unmangled libraries. The wrapper script approach allows to
patch the intermediate library with additional RPATHs to avoid load
failures during GHC compilation steps. The Hazel target for the swagger2
library, for example, triggers this issue.

[osx_wrapper]: https://github.com/bazelbuild/bazel/blob/6aab2719d678057c4ef8edc3dfcad56803454732/tools/cpp/osx_cc_wrapper.sh.tpl.
  • Loading branch information
aherrmann committed Feb 18, 2019
1 parent e1cf39c commit 072bbba
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 236 deletions.
1 change: 1 addition & 0 deletions haskell/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ exports_files(
"private/ghci_repl_wrapper.sh",
"private/haddock_wrapper.sh.tpl",
"private/coverage_wrapper.sh.tpl",
"private/osx_cc_wrapper.sh.tpl",
],
)

Expand Down
23 changes: 21 additions & 2 deletions haskell/cc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,31 @@ def cc_interop_info(ctx):
variables = link_variables,
)

# Generate cc wrapper script on Darwin that adjusts load commands.
hs_toolchain = ctx.toolchains["@io_tweag_rules_haskell//haskell:toolchain"]
if hs_toolchain.is_darwin:
cc_wrapper = ctx.actions.declare_file("osx_cc_wrapper")
cc = cc_wrapper.path
ctx.actions.expand_template(
template = hs_toolchain.osx_cc_wrapper_tpl,
output = cc_wrapper,
substitutions = {
"%{cc}": cc_toolchain.compiler_executable(),
},
)
cc_files = ctx.files._cc_toolchain + [
cc_wrapper,
]
else:
cc = cc_toolchain.compiler_executable()
cc_files = ctx.files._cc_toolchain

# XXX Workaround https://github.com/bazelbuild/bazel/issues/6876.
linker_flags = [flag for flag in linker_flags if flag not in ["-shared"]]

tools = {
"ar": cc_toolchain.ar_executable(),
"cc": cc_toolchain.compiler_executable(),
"cc": cc,
"ld": cc_toolchain.ld_executable(),
"cpp": cc_toolchain.preprocessor_executable(),
"nm": cc_toolchain.nm_executable(),
Expand All @@ -143,7 +162,7 @@ def cc_interop_info(ctx):

return CcInteropInfo(
tools = struct(**tools),
files = ctx.files._cc_toolchain,
files = cc_files,
hdrs = hdrs.to_list(),
cpp_flags = cpp_flags,
include_args = include_args,
Expand Down
4 changes: 2 additions & 2 deletions haskell/doctest.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ load(
":private/path_utils.bzl",
"get_lib_name",
)
load(":private/providers.bzl", "get_libs_for_ghc_linker", "get_mangled_libs")
load(":private/providers.bzl", "get_libs_for_ghc_linker")
load(":private/set.bzl", "set")
load(
"@io_tweag_rules_haskell//haskell:private/providers.bzl",
Expand Down Expand Up @@ -109,7 +109,7 @@ def _haskell_doctest_single(target, ctx):

# Direct C library dependencies to link against.
link_ctx = build_info.cc_dependencies.dynamic_linking
libs_to_link = get_mangled_libs(link_ctx.libraries_to_link.to_list())
libs_to_link = link_ctx.libraries_to_link.to_list()
import_libs_to_link = set.to_list(build_info.import_dependencies)

# External libraries.
Expand Down
3 changes: 1 addition & 2 deletions haskell/haddock.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ load(
"HaskellLibraryInfo",
)
load(":private/context.bzl", "haskell_context")
load(":private/providers.bzl", "get_mangled_libs")
load(":private/set.bzl", "set")

def _get_haddock_path(package_id):
Expand Down Expand Up @@ -113,7 +112,7 @@ def _haskell_doc_aspect_impl(target, ctx):

# Transitive library dependencies for runtime.
trans_link_ctx = target[HaskellBuildInfo].transitive_cc_dependencies.dynamic_linking
trans_libs = get_mangled_libs(trans_link_ctx.libraries_to_link.to_list())
trans_libs = trans_link_ctx.libraries_to_link.to_list()
trans_import_libs = set.to_list(target[HaskellBuildInfo].transitive_import_dependencies)

ctx.actions.run(
Expand Down
107 changes: 9 additions & 98 deletions haskell/private/actions/link.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ load(
"ln",
)
load(":private/pkg_id.bzl", "pkg_id")
load(":private/providers.bzl", "get_mangled_libs")
load(":private/set.bzl", "set")
load(":private/list.bzl", "list")

Expand Down Expand Up @@ -250,69 +249,6 @@ def _darwin_create_extra_linker_flags_file(hs, cc, objects_dir, executable, dyna
)
return linker_flags_file

def _fix_darwin_linker_paths(hs, inp, out, external_libraries):
"""Postprocess a macOS binary to make shared library references relative.
On macOS, in order to simulate the linker "rpath" behavior and make the
binary load shared libraries from relative paths, (or dynamic libraries
load other libraries) we need to postprocess it with install_name_tool.
(This is what the Bazel-provided `cc_wrapper.sh` does for cc rules.)
For details: https://blogs.oracle.com/dipol/entry/dynamic_libraries_rpath_and_mac
Args:
hs: Haskell context.
inp: An input file.
out: An output file.
external_libraries: List of C libraries that inp depends on.
These can be plain File for haskell_cc_import dependencies, or
struct(lib, mangled_lib) for regular cc_library dependencies.
"""
hs.actions.run_shell(
inputs = [inp],
outputs = [out],
mnemonic = "HaskellFixupLoaderPath",
progress_message = "Fixing install paths for {0}".format(out.basename),
command = " &&\n ".join(
[
"cp {} {}".format(inp.path, out.path),
"chmod +w {}".format(out.path),
# Patch the "install name" or "library identifaction name".
# The "install name" informs targets that link against `out`
# where `out` can be found during runtime. Here we update this
# "install name" to the new filename of the fixed binary.
# Refer to the Oracle blog post linked above for details.
"/usr/bin/install_name_tool -id @rpath/{} {}".format(
out.basename,
out.path,
),
] +
[
# Make external library references relative to rpath instead of
# relative to the working directory at link time.
# Handles cc_library dependencies.
"/usr/bin/install_name_tool -change {} {} {}".format(
f.lib.path,
paths.join("@rpath", f.mangled_lib.basename),
out.path,
)
for f in external_libraries
if hasattr(f, "mangled_lib")
] +
[
# Make external library references relative to rpath instead of
# relative to the working directory at link time.
# Handles haskell_cc_import dependencies.
"/usr/bin/install_name_tool -change {} {} {}".format(
f.path,
paths.join("@rpath", f.basename),
out.path,
)
for f in external_libraries
if not hasattr(f, "mangled_lib")
],
),
)

def _create_objects_dir_manifest(hs, objects_dir, dynamic, with_profiling):
suffix = ".dynamic.manifest" if dynamic else ".static.manifest"
objects_dir_manifest = hs.actions.declare_file(
Expand Down Expand Up @@ -341,7 +277,7 @@ def _create_objects_dir_manifest(hs, objects_dir, dynamic, with_profiling):

return objects_dir_manifest

def _link_dependencies(hs, dep_info, dynamic, binary_tmp, binary, args):
def _link_dependencies(hs, dep_info, dynamic, binary, args):
"""Configure linker flags and inputs.
Configure linker flags for C library dependencies and runtime dynamic
Expand All @@ -352,7 +288,6 @@ def _link_dependencies(hs, dep_info, dynamic, binary_tmp, binary, args):
hs: Haskell context.
dep_info: HaskellBuildInfo provider.
dynamic: Bool: Whether to link dynamically, or statically.
binary_tmp: Intermediate compilation output.
binary: Final linked binary.
args: Arguments to the linking action.
Expand All @@ -372,12 +307,12 @@ def _link_dependencies(hs, dep_info, dynamic, binary_tmp, binary, args):
# I.e. not indirect through another Haskell dependency.
# Such indirect dependencies are linked by GHC based on the extra-libraries
# fields in the dependency's package configuration file.
libs_to_link = get_mangled_libs(link_ctx.libraries_to_link.to_list())
libs_to_link = link_ctx.libraries_to_link.to_list()
import_libs_to_link = set.to_list(dep_info.import_dependencies)
_add_external_libraries(args, libs_to_link + import_libs_to_link)

# Transitive library dependencies to have in scope for linking.
trans_libs_to_link = get_mangled_libs(trans_link_ctx.libraries_to_link.to_list())
trans_libs_to_link = trans_link_ctx.libraries_to_link.to_list()
trans_import_libs = set.to_list(dep_info.transitive_import_dependencies)

# Libraries to pass as inputs to linking action.
Expand All @@ -386,14 +321,6 @@ def _link_dependencies(hs, dep_info, dynamic, binary_tmp, binary, args):
depset(trans_import_libs),
])

if hs.toolchain.is_darwin:
_fix_darwin_linker_paths(
hs,
binary_tmp,
binary,
trans_link_ctx.libraries_to_link.to_list() + trans_import_libs,
)

# Transitive dynamic library dependencies to have in RUNPATH.
cc_solibs = (
trans_link_ctx.dynamic_libraries_for_runtime.to_list() +
Expand All @@ -418,14 +345,14 @@ def _link_dependencies(hs, dep_info, dynamic, binary_tmp, binary, args):
rpaths = _infer_rpaths(
hs.toolchain.is_darwin,
False, # Libraries not coming from haskell_cc_import.
binary_tmp,
binary,
trans_link_ctx.dynamic_libraries_for_runtime.to_list() +
hs_solibs,
)
set.mutable_union(rpaths, _infer_rpaths(
hs.toolchain.is_darwin,
True, # Libraries coming from haskell_cc_import.
binary_tmp,
binary,
trans_import_libs,
))
for rpath in set.to_list(rpaths):
Expand All @@ -451,11 +378,6 @@ def link_binary(

exe_name = hs.name + (".exe" if hs.toolchain.is_windows else "")
executable = hs.actions.declare_file(exe_name)
if not hs.toolchain.is_darwin:
compile_output = executable
else:
# See _fix_darwin_linker_paths below.
compile_output = hs.actions.declare_file(hs.name + ".temp")

args = hs.actions.args()
args.add_all(["-optl" + f for f in cc.linker_flags])
Expand Down Expand Up @@ -483,7 +405,7 @@ def link_binary(
# so we just default to passing it.
args.add("-optl-pthread")

args.add_all(["-o", compile_output.path])
args.add_all(["-o", executable.path])

# De-duplicate optl calls while preserving ordering: we want last
# invocation of an object to remain last. That is `-optl foo -optl
Expand All @@ -505,7 +427,6 @@ def link_binary(
hs = hs,
dep_info = dep_info,
dynamic = dynamic,
binary_tmp = compile_output,
binary = executable,
args = args,
)
Expand Down Expand Up @@ -562,7 +483,7 @@ def link_binary(
depset([objects_dir]),
cc_link_libs,
]),
outputs = [compile_output],
outputs = [executable],
mnemonic = "HaskellLinkBinary",
arguments = args,
params_file = params_file,
Expand Down Expand Up @@ -736,25 +657,15 @@ def link_library_dynamic(hs, cc, dep_info, extra_srcs, objects_dir, my_pkg_id):
version = my_pkg_id.version if my_pkg_id else None,
)))

if hs.toolchain.is_darwin:
# Keep space to override install name with mangled name.
args.add("-optl-Wl,-headerpad_max_install_names")

# See _fix_darwin_linker_paths below.
dynamic_library_tmp = hs.actions.declare_file(dynamic_library.basename + ".temp")
else:
dynamic_library_tmp = dynamic_library

(cc_link_libs, _cc_solibs, _hs_solibs) = _link_dependencies(
hs = hs,
dep_info = dep_info,
dynamic = True,
binary_tmp = dynamic_library_tmp,
binary = dynamic_library,
args = args,
)

args.add_all(["-o", dynamic_library_tmp.path])
args.add_all(["-o", dynamic_library.path])

# Profiling not supported for dynamic libraries.
objects_dir_manifest = _create_objects_dir_manifest(
Expand All @@ -773,7 +684,7 @@ def link_library_dynamic(hs, cc, dep_info, extra_srcs, objects_dir, my_pkg_id):
set.to_depset(dep_info.dynamic_libraries),
cc_link_libs,
]),
outputs = [dynamic_library_tmp],
outputs = [dynamic_library],
mnemonic = "HaskellLinkDynamicLibrary",
arguments = args,
params_file = objects_dir_manifest,
Expand Down
5 changes: 1 addition & 4 deletions haskell/private/actions/package.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
load("@bazel_skylib//lib:paths.bzl", "paths")
load(":private/path_utils.bzl", "target_unique_name")
load(":private/pkg_id.bzl", "pkg_id")
load(":private/providers.bzl", "get_mangled_libs")
load(":private/set.bzl", "set")
load(":private/path_utils.bzl", "get_lib_name")

Expand All @@ -18,9 +17,7 @@ def _get_extra_libraries(dep_info):
dirs: list: Library search directories for extra library dependencies.
libs: list: Extra library dependencies.
"""
cc_libs = get_mangled_libs(
dep_info.cc_dependencies.dynamic_linking.libraries_to_link.to_list(),
)
cc_libs = dep_info.cc_dependencies.dynamic_linking.libraries_to_link.to_list()
import_libs = set.to_list(dep_info.import_dependencies)

# The order in which library dependencies are listed is relevant when
Expand Down
Loading

0 comments on commit 072bbba

Please sign in to comment.