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 removed access to the unmangled library names for C
library dependencies. On MacOS dynamic libraries contain the path to the
unmangled library in their "library name", which means that targets
linking against these dynamic libraries will reference the unmangled
target. We need to patch them to instead refer to the mangled target
relative to `@rpath`. As we no longer have access to the unmangled
targets, we can no longer easily patch the targets.

Furthermore, GHC generates intermediate dynamic libraries which will try
to load the unmangled libraries. Without them available, we cannot make
this step pass sucessfully.

We work around this by copying and patching the link library
dependencies to change their "library name" to the mangled name relative
to `@rpath`. Then we no longer need to patch the targets depending on
these libraries, as the linker will write the correct linking commands
into them.
  • Loading branch information
aherrmann committed Feb 13, 2019
1 parent 300cc70 commit 5f53b20
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 136 deletions.
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
31 changes: 11 additions & 20 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 @@ -259,13 +258,17 @@ def _fix_darwin_linker_paths(hs, inp, out, external_libraries):
(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
Regular C library dependencies are patched so that their install name is
relative to @rpath and the libraries are thereby linked relative to @rpath.
For haskell_cc_import dependencies this is not the case. This function
patches haskell_cc_import library dependencies to be linked relative to
@rpath as well.
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.
external_libraries: List of haskell_cc_import dependencies.
"""
hs.actions.run_shell(
inputs = [inp],
Expand All @@ -286,29 +289,17 @@ def _fix_darwin_linker_paths(hs, inp, out, external_libraries):
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.
# XXX: Remove this when haskell_cc_import is removed.
"/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")
],
),
)
Expand Down Expand Up @@ -372,12 +363,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 @@ -391,7 +382,7 @@ def _link_dependencies(hs, dep_info, dynamic, binary_tmp, binary, args):
hs,
binary_tmp,
binary,
trans_link_ctx.libraries_to_link.to_list() + trans_import_libs,
trans_import_libs,
)

# Transitive dynamic library dependencies to have in RUNPATH.
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
190 changes: 143 additions & 47 deletions haskell/private/dependencies.bzl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
load("@bazel_skylib//lib:dicts.bzl", "dicts")
load("@bazel_skylib//lib:paths.bzl", "paths")
load(
"@io_tweag_rules_haskell//haskell:private/providers.bzl",
"CcSkylarkApiProviderHacked",
Expand All @@ -19,73 +20,168 @@ load(
)
load(":private/set.bzl", "set")

def _HaskellCcInfo_from_CcInfo(ctx, cc_info):
static_linking = cc_info.linking_context.static_mode_params_for_executable
dynamic_linking = cc_info.linking_context.dynamic_mode_params_for_executable
manglings = {
get_lib_name(l.original_artifact()): get_lib_name(l.artifact())
for l in dynamic_linking.libraries_to_link.to_list()
}
def _cc_get_static_lib(lib_info):
"""Return the library to use in static linking mode.
This returns the first available library artifact in the following order:
- static_library
- pic_static_library
- dynamic_library
- interface_library
Args:
lib_info: LibraryToLink provider.
Returns:
File: The library to link against in static mode.
"""
if lib_info.static_library:
return lib_info.static_library
elif lib_info.pic_static_library:
return lib_info.pic_static_library
elif lib_info.dynamic_library:
return lib_info.dynamic_library
else:
return lib_info.interface_library

def _cc_get_dynamic_lib(lib_info):
"""Return the library to use in dynamic linking mode.
This returns the first available library artifact in the following order:
- dynamic_library
- interface_library
- pic_static_library
- static_library
Args:
lib_info: LibraryToLink provider.
Returns:
File: The library to link against in dynamic mode.
"""
if lib_info.dynamic_library:
return lib_info.dynamic_library
elif lib_info.interface_library:
return lib_info.interface_library
elif lib_info.pic_static_library:
return lib_info.pic_static_library
else:
return lib_info.static_library

def _cc_add_lib(hs, lib, libs_to_link, libs_for_runtime):
"""Add library to libs_to_link and libs_for_runtime.
This adds the given library dependency to the list for libraries to link
and runtime library dependencies.
On MacOS we apply two patches to the libraries for linking.
1) GHC's own loader expects dynamic libraries to end on `.dylib`, but Bazel
produces `.so` files. Here we copy the given library to a `.dylib` file.
2) MacOS adds load instructions based on the "install name" of the given
library. The dynamic libraries that Bazel produces maintain their
package path in their install name. E.g.
`bazel-out/darwin-fastbuild/bin/some/package/libsomelib.so`.
Here we modify the install name on a writable copy to be relative to
@rpath. E.g. `@rpath/libsomelib.so`.
Args:
hs: Haskell context.
lib: The library dependency.
libs_to_link: (output) list of libraries to link against.
libs_for_runtime: (output) list of libraries to add to runfiles.
"""
link_lib = lib
if is_shared_library(lib):
libs_for_runtime.append(lib)
if hs.toolchain.is_darwin:
link_lib = hs.actions.declare_file(paths.join(
"_darwin_dylib",
paths.replace_extension(lib.basename, ".dylib"),
))
hs.actions.run_shell(
inputs = [lib],
outputs = [link_lib],
mnemonic = "HaskellFixupCcInstallName",
progress_message = "Fixing install name for {0}".format(link_lib.basename),
command = " &&\n ".join([
"cp {} {}".format(lib.path, link_lib.path),
"chmod +w {}".format(link_lib.path),
"/usr/bin/install_name_tool -id @rpath/{} {}".format(
lib.basename,
link_lib.path,
),
]),
)
libs_to_link.append(link_lib)

def _HaskellCcInfo_from_CcInfo(hs, ctx, cc_info):
libs_to_link = cc_info.linking_context.libraries_to_link
static_libs_to_link = []
for l in static_linking.libraries_to_link.to_list():
dynamic_libs_to_link = []
static_libs_for_runtime = []
dynamic_libs_for_runtime = []
for l in libs_to_link:
_static_lib = _cc_get_static_lib(l)
dynamic_lib = _cc_get_dynamic_lib(l)

# Bazel itself only mangles dynamic libraries, not static libraries.
# However, we need the library name of the static and dynamic version
# of a library to match so that we can refer to both with one entry in
# the package configuration file. Here we rename any static archives
# with mismatching mangled dynamic library name.
orig_lib = l.original_artifact()
mangled_lib = l.artifact()
orig_name = get_lib_name(orig_lib)
mangled_name = get_lib_name(mangled_lib)
if mangled_name != manglings[orig_name]:
ext = orig_lib.extension
link_lib = ctx.actions.declare_file(
"lib%s.%s" % (manglings[orig_name], ext),
static_name = get_lib_name(_static_lib)
dynamic_name = get_lib_name(dynamic_lib)
if static_name != dynamic_name:
ext = _static_lib.extension
static_lib = ctx.actions.declare_file(
"lib%s.%s" % (dynamic_name, ext),
)
ln(ctx, orig_lib, link_lib)
static_libs_to_link.append(struct(
lib = orig_lib,
mangled_lib = link_lib,
))
ln(ctx, _static_lib, static_lib)
else:
static_libs_to_link.append(struct(
lib = orig_lib,
mangled_lib = mangled_lib,
))
static_libs_to_link = depset(
direct = static_libs_to_link,
order = "topological",
)
dynamic_libs_to_link = depset(
direct = [
struct(
lib = l.original_artifact(),
mangled_lib = l.artifact(),
)
for l in dynamic_linking.libraries_to_link.to_list()
],
order = "topological",
)
static_lib = _static_lib

_cc_add_lib(hs, static_lib, static_libs_to_link, static_libs_for_runtime)
_cc_add_lib(hs, dynamic_lib, dynamic_libs_to_link, dynamic_libs_for_runtime)

return HaskellCcInfo(
static_linking = struct(
libraries_to_link = static_libs_to_link,
dynamic_libraries_for_runtime = static_linking.dynamic_libraries_for_runtime,
user_link_flags = static_linking.user_link_flags,
libraries_to_link = depset(
direct = static_libs_to_link,
order = "topological",
),
dynamic_libraries_for_runtime = depset(
direct = static_libs_for_runtime,
order = "topological",
),
user_link_flags = depset(
direct = cc_info.linking_context.user_link_flags,
order = "topological",
)
),
dynamic_linking = struct(
libraries_to_link = dynamic_libs_to_link,
dynamic_libraries_for_runtime = dynamic_linking.dynamic_libraries_for_runtime,
user_link_flags = dynamic_linking.user_link_flags,
libraries_to_link = depset(
direct = dynamic_libs_to_link,
order = "topological",
),
dynamic_libraries_for_runtime = depset(
direct = dynamic_libs_for_runtime,
order = "topological",
),
user_link_flags = depset(
direct = cc_info.linking_context.user_link_flags,
order = "topological",
)
),
)

def gather_dep_info(ctx):
def gather_dep_info(hs, ctx):
"""Collapse dependencies into a single `HaskellBuildInfo`.
Note that the field `prebuilt_dependencies` also includes
prebuilt_dependencies of current target.
Args:
ctx: Haskell context.
ctx: Rule context.
Returns:
Expand Down Expand Up @@ -152,7 +248,7 @@ def gather_dep_info(ctx):
# The final link of a binary must include all static libraries we
# depend on, including transitives ones. Theses libs are provided
# in the `CcInfo` provider.
hs_cc_info = _HaskellCcInfo_from_CcInfo(ctx, dep[CcInfo])
hs_cc_info = _HaskellCcInfo_from_CcInfo(hs, ctx, dep[CcInfo])
acc = HaskellBuildInfo(
package_ids = acc.package_ids,
package_confs = acc.package_confs,
Expand Down
4 changes: 2 additions & 2 deletions haskell/private/haskell_impl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _get_hpc_outputs(ctx, srcs_files):

def _haskell_binary_common_impl(ctx, is_test):
hs = haskell_context(ctx)
dep_info = gather_dep_info(ctx)
dep_info = gather_dep_info(hs, ctx)

# Add any interop info for other languages.
cc = cc_interop_info(ctx)
Expand Down Expand Up @@ -194,7 +194,7 @@ def _haskell_binary_common_impl(ctx, is_test):

def haskell_library_impl(ctx):
hs = haskell_context(ctx)
dep_info = gather_dep_info(ctx)
dep_info = gather_dep_info(hs, ctx)
version = ctx.attr.version if ctx.attr.version else None
my_pkg_id = pkg_id.new(ctx.label, version)
with_profiling = is_profiling_enabled(hs)
Expand Down
Loading

0 comments on commit 5f53b20

Please sign in to comment.