Skip to content

Commit

Permalink
Make ‘cc_haskell_import’ work with ‘haskell_binary’ targets
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkkrp committed Mar 21, 2018
1 parent 525eba2 commit 905f30a
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 75 deletions.
5 changes: 4 additions & 1 deletion WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ filegroup(
cc_library(
name = "threaded-rts",
srcs = glob(["lib/ghc-*/rts/libHSrts_thr-ghc*.so"]),
srcs = glob([
"lib/ghc-*/rts/libHSrts_thr-ghc*.so",
"lib/ghc-*/rts/libffi.so.6",
]),
hdrs = glob(["lib/ghc-*/include/**/*.h"]),
strip_include_prefix = glob(["lib/ghc-*/include"], exclude_directories=0)[0],
)
Expand Down
169 changes: 103 additions & 66 deletions haskell/actions.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ _DefaultCompileInfo = provider(
"objects_dir": "Object files directory.",
"interfaces_dir": "Interface files directory.",
"object_files": "Object files.",
"object_dyn_files": "Dynamic object files.",
"interface_files": "Interface files.",
"env": "Default env vars."
},
Expand Down Expand Up @@ -155,7 +156,9 @@ def compile_haskell_bin(ctx):
ctx: Rule context.
Returns:
list of File: Compiled object files.
(list of File, list of File):
* Object files
* Dynamic object files
"""
c = _compilation_defaults(ctx)
c.args.add(["-main-is", ctx.attr.main_function])
Expand All @@ -169,38 +172,38 @@ def compile_haskell_bin(ctx):
arguments = [c.args]
)

return c.object_files
return c.object_files, c.object_dyn_files

def link_haskell_bin(ctx, object_files):
"""Link Haskell binary.
def _create_dummy_archive(ctx):
"""Create empty archive so that GHC has some input files to work on during
linking.
See: https://github.com/facebook/buck/blob/126d576d5c07ce382e447533b57794ae1a358cc2/src/com/facebook/buck/haskell/HaskellDescriptionUtils.java#L295
Args:
ctx: Rule context.
object_files: Object files to include during linking.
Returns:
File: Built Haskell executable.
File, the created dummy archive.
"""
# Create empty archive so that GHC has some input files to work on during linking
#
# https://github.com/facebook/buck/blob/126d576d5c07ce382e447533b57794ae1a358cc2/src/com/facebook/buck/haskell/HaskellDescriptionUtils.java#L295
dummy_input = ctx.actions.declare_file("BazelDummy.hs")
dummy_object = ctx.actions.declare_file(paths.replace_extension("BazelDummy", ".o"))

ctx.actions.write(output=dummy_input, content="\n".join([
"{-# LANGUAGE NoImplicitPrelude #-}",
"module BazelDummy () where"
]))
dummy_raw = "BazelDummy.hs"
dummy_input = ctx.actions.declare_file(dummy_raw)
dummy_object = ctx.actions.declare_file(paths.replace_extension(dummy_raw, ".o"))

ctx.actions.write(output=dummy_input, content="""
{-# LANGUAGE NoImplicitPrelude #-}
module BazelDummy () where
""")

dummy_static_lib = ctx.actions.declare_file("libempty.a")
dummy_args = ctx.actions.args()
dummy_args.add(["-no-link", dummy_input])
ctx.actions.run(
inputs = [dummy_input],
outputs = [dummy_object],
executable = tools(ctx).ghc,
arguments = [dummy_args]
arguments = ["-c", dummy_input.path],
)

ar_args = ctx.actions.args()
ar_args.add(["qc", dummy_static_lib, dummy_object])

Expand All @@ -211,34 +214,72 @@ def link_haskell_bin(ctx, object_files):
arguments = [ar_args]
)

args = ctx.actions.args()
return dummy_static_lib

_add_mode_options(ctx, args)
def link_haskell_bin(ctx, object_files, dynamic):
"""Link Haskell binary from static object files.
Args:
ctx: Rule context.
object_files: Static or dynamic object files.
dynamic: Whether to generate a dynamic library instead of executable.
Returns:
(File, String):
* Executable
* So symlink prefix, relative path where the executable will search
for .so files at runtime
"""

dummy_static_lib = _create_dummy_archive(ctx)

args = ctx.actions.args()
_add_mode_options(ctx, args)
args.add(ctx.attr.compiler_flags)
args.add(["-o", ctx.outputs.executable.path, dummy_static_lib.path])
output_exe = ctx.actions.declare_file(
"lib{0}.so".format(ctx.attr.name)
) if dynamic else ctx.outputs.executable

for o in object_files:
args.add(["-optl", o.path])
if dynamic:
args.add(["-shared", "-dynamic", "-pie"])

args.add(["-o", output_exe.path, dummy_static_lib.path])

dep_info = gather_dep_info(ctx)

# De-duplicate optl calls while preserving ordering: we want last
# invocation of an object to remain last. That is `-optl foo -optl
# bar -optl foo` becomes `-optl bar -optl foo`. Do this by counting
# number of occurrences. That way we only build dict and add to args
# directly rather than doing multiple reversals with temporary
# lists.
link_paths = {}

for lib in dep_info.static_libraries:
link_paths[lib] = link_paths.get(lib, 0) + 1
if dynamic:

args.add([f.path for f in object_files])

for package in set.to_list(dep_info.package_names):
args.add(["-package", package])

for cache in set.to_list(dep_info.package_caches):
args.add(["-package-db", cache.dirname])

for lib in dep_info.static_libraries:
occ = link_paths.get(lib, 0)
# This is the last occurrence of the lib, insert it.
if occ == 1:
args.add(["-optl", lib.path])
link_paths[lib] = occ - 1
else:

for o in object_files:
args.add(["-optl", o.path])

link_paths = {}

for lib in dep_info.static_libraries:
link_paths[lib] = link_paths.get(lib, 0) + 1

for lib in dep_info.static_libraries:
occ = link_paths.get(lib, 0)
# This is the last occurrence of the lib, insert it.
if occ == 1:
args.add(["-optl", lib.path])
link_paths[lib] = occ - 1

# We have to remember to specify all (transitive) wired-in
# dependencies or we can't find objects for linking.
Expand All @@ -247,45 +288,34 @@ def link_haskell_bin(ctx, object_files):

_add_external_libraries(args, dep_info.external_libraries)

so_symlink_prefix = paths.relativize(
paths.dirname(ctx.outputs.executable.path),
ctx.bin_dir.path,
)

# The resulting test executable should be able to find all external
# libraries when it is run by Bazel. That is achieved by setting RPATH to
# a relative path which when joined with working directory points to
# symlinks which in turn point to shared libraries. This is quite similar
# to the approach taken by cc_binary, cc_test, etc.:
#
# https://github.com/bazelbuild/bazel/blob/f98a7a2fedb3e714cef1038dcb85f83731150246/src/main/java/com/google/devtools/build/lib/rules/cpp/CppActionConfigs.java#L587-L605
so_symlink_prefix = paths.relativize(
paths.dirname(output_exe.path),
ctx.bin_dir.path,
)
args.add(["-optl-Wl,-rpath," + so_symlink_prefix])

ctx.actions.run(
inputs = depset(transitive = [
depset(dep_info.static_libraries),
set.to_depset(dep_info.package_caches) if dynamic else depset(dep_info.static_libraries),
set.to_depset(dep_info.dynamic_libraries) if dynamic else depset(),
depset(object_files),
depset([dummy_static_lib]),
set.to_depset(dep_info.external_libraries),
]),
outputs = [ctx.outputs.executable],
progress_message = "Linking {0}".format(ctx.outputs.executable.basename),
outputs = [output_exe],
progress_message = "Linking {0}".format(output_exe.basename),
executable = tools(ctx).ghc,
arguments = [args]
)

# New we have to figure out symlinks to shared libraries to create for
# running tests.
so_symlinks = {}

for lib in set.to_list(dep_info.external_libraries):
so_symlinks[paths.join(so_symlink_prefix, paths.basename(lib.path))] = lib

return DefaultInfo(
executable = ctx.outputs.executable,
files = depset([ctx.outputs.executable]),
runfiles = ctx.runfiles(symlinks=so_symlinks, collect_data = True),
)
return output_exe, so_symlink_prefix

def compile_haskell_lib(ctx):
"""Build arguments for Haskell package build.
Expand All @@ -306,7 +336,6 @@ def compile_haskell_lib(ctx):
c = _compilation_defaults(ctx)
c.args.add([
"-package-name", _get_pkg_id(ctx),
"-static", "-dynamic-too"
])

# This is absolutely required otherwise GHC doesn't know what package it's
Expand All @@ -318,21 +347,16 @@ def compile_haskell_lib(ctx):
c.args.add(unit_id_args)
c.haddock_args.add(unit_id_args, before_each="--optghc")

object_dyn_files = [
declare_compiled(ctx, s, ".dyn_o", directory=c.objects_dir)
for s in _hs_srcs(ctx)
]

ctx.actions.run(
inputs = c.inputs,
outputs = c.outputs + object_dyn_files,
outputs = c.outputs,
progress_message = "Compiling {0}".format(ctx.attr.name),
env = c.env,
executable = tools(ctx).ghc,
arguments = [c.args],
)

return c.interfaces_dir, c.interface_files, c.object_files, object_dyn_files, c.haddock_args
return c.interfaces_dir, c.interface_files, c.object_files, c.object_dyn_files, c.haddock_args

def link_static_lib(ctx, object_files):
"""Link a static library for the package using given object files.
Expand Down Expand Up @@ -383,15 +407,15 @@ def link_dynamic_lib(ctx, object_files):

dep_info = gather_dep_info(ctx)

for n in set.to_list(
for package in set.to_list(
set.union(
dep_info.package_names,
set.from_list(ctx.attr.prebuilt_dependencies)
)):
args.add(["-package", n])
args.add(["-package", package])

for c in set.to_list(dep_info.package_caches):
args.add(["-package-db", c.dirname])
for cache in set.to_list(dep_info.package_caches):
args.add(["-package-db", cache.dirname])

_add_external_libraries(args, dep_info.external_libraries)

Expand Down Expand Up @@ -504,6 +528,9 @@ def _compilation_defaults(ctx):
args.add(ctx.attr.compiler_flags)
haddock_args.add(ctx.attr.compiler_flags, before_each="--optghc")

# Output static and dynamic object files.
args.add(["-static", "-dynamic-too"])

# Common flags
args.add([
"-c",
Expand Down Expand Up @@ -549,6 +576,7 @@ def _compilation_defaults(ctx):

# We want object and dynamic objects from all inputs.
object_files = []
object_dyn_files = []

# We need to keep interface files we produce so we can import
# modules cross-package.
Expand All @@ -569,13 +597,19 @@ def _compilation_defaults(ctx):
object_files.append(
declare_compiled(ctx, s, ".o", directory=objects_dir)
)
object_dyn_files.append(
declare_compiled(ctx, s, ".dyn_o", directory=objects_dir)
)
interface_files.append(
declare_compiled(ctx, s, ".hi", directory=interfaces_dir)
)
else:
object_files.append(
ctx.actions.declare_file(paths.join(objects_dir_raw, "Main.o"))
)
object_dyn_files.append(
ctx.actions.declare_file(paths.join(objects_dir_raw, "Main.dyn_o"))
)
interface_files.append(
ctx.actions.declare_file(paths.join(interfaces_dir_raw, "Main.hi"))
)
Expand Down Expand Up @@ -608,10 +642,11 @@ def _compilation_defaults(ctx):
depset(textual_headers),
depset([tools(ctx).gcc]),
]),
outputs = [objects_dir, interfaces_dir] + object_files + interface_files,
outputs = [objects_dir, interfaces_dir] + object_files + object_dyn_files + interface_files,
objects_dir = objects_dir,
interfaces_dir = interfaces_dir,
object_files = object_files,
object_dyn_files = object_dyn_files,
interface_files = interface_files,
env = dicts.add({
"LD_LIBRARY_PATH": get_external_libs_path(dep_info.external_libraries),
Expand Down Expand Up @@ -738,11 +773,12 @@ def infer_lib_info(ctx, haddock_args=[]):
haddock_args = haddock_args
)

def infer_bin_info(ctx):
def infer_bin_info(ctx, dynamic_bin=None):
"""Return populated `HaskellBinaryInfo` provider.
Args:
ctx: Rule context.
dynamic_bin: File, binary compiled from dynamic object files.
Returns:
HaskellBinaryInfo: binary-specific information.
Expand All @@ -759,4 +795,5 @@ def infer_bin_info(ctx):
return HaskellBinaryInfo(
source_files = set.from_list(ctx.files.srcs),
modules = set.from_list(modules),
dynamic_bin = dynamic_bin,
)
17 changes: 14 additions & 3 deletions haskell/cc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ These rules are temporary and will be deprecated in the future.

load(":providers.bzl",
"HaskellBuildInfo",
"HaskellBinaryInfo",
"CcSkylarkApiProviderHacked",
)

Expand Down Expand Up @@ -94,13 +95,23 @@ Example:
"""

def _cc_haskell_import(ctx):
dyn_libs = set.empty()
if HaskellBuildInfo in ctx.attr.dep:
return [DefaultInfo(
files = set.to_depset(ctx.attr.dep[HaskellBuildInfo].dynamic_libraries)
)]
set.mutable_union(dyn_libs, ctx.attr.dep[HaskellBuildInfo].dynamic_libraries)
else:
fail("{0} has to provide HaskellBuildInfo".format(ctx.attr.dep.label.name))

if HaskellBinaryInfo in ctx.attr.dep:
dbin = ctx.attr.dep[HaskellBinaryInfo].dynamic_bin
if dbin != None:
set.mutable_insert(dyn_libs, dbin)

return [
DefaultInfo(
files = set.to_depset(dyn_libs)
)
]

cc_haskell_import = rule(
_cc_haskell_import,
attrs = {
Expand Down
Loading

0 comments on commit 905f30a

Please sign in to comment.