Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions haskell/cc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,32 @@ def cc_interop_info(ctx):
if CcInfo in dep
]).linking_context.libraries_to_link.to_list(),
)

def ghc_cc_program_args(cc):
"""Returns the -pgm* flags required to override cc.

Args:
cc: string, path to the C compiler (cc_wrapper).

Returns:
list of string, GHC arguments.
"""
return [
# GHC uses C compiler for assemly, linking and preprocessing as well.
"-pgma",
cc,
"-pgmc",
cc,
"-pgml",
cc,
"-pgmP",
cc,
# Setting -pgm* flags explicitly has the unfortunate side effect
# of resetting any program flags in the GHC settings file. So we
# restore them here. See
# https://ghc.haskell.org/trac/ghc/ticket/7929.
"-optc-fno-stack-protector",
"-optP-E",
"-optP-undef",
"-optP-traditional",
]
22 changes: 2 additions & 20 deletions haskell/doctest.bzl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Doctest support"""

load("@bazel_skylib//lib:dicts.bzl", "dicts")
load(":cc.bzl", "cc_interop_info")
load(":cc.bzl", "cc_interop_info", "ghc_cc_program_args")
load(":private/context.bzl", "haskell_context", "render_env")
load(":private/set.bzl", "set")
load(
Expand Down Expand Up @@ -95,25 +95,7 @@ def _haskell_doctest_single(target, ctx):
args.add("--no-magic")

cc = cc_interop_info(ctx)
args.add_all([
# GHC uses C compiler for assemly, linking and preprocessing as well.
"-pgma",
cc.tools.cc,
"-pgmc",
cc.tools.cc,
"-pgml",
cc.tools.cc,
"-pgmP",
cc.tools.cc,
# Setting -pgm* flags explicitly has the unfortunate side effect
# of resetting any program flags in the GHC settings file. So we
# restore them here. See
# https://ghc.haskell.org/trac/ghc/ticket/7929.
"-optc-fno-stack-protector",
"-optP-E",
"-optP-undef",
"-optP-traditional",
])
args.add_all(ghc_cc_program_args(cc.tools.cc))

doctest_log = ctx.actions.declare_file(
"doctest-log-" + ctx.label.name + "-" + target.label.name,
Expand Down
7 changes: 1 addition & 6 deletions haskell/private/ghci_repl_wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ cd "$BUILD_WORKSPACE_DIRECTORY"

RULES_HASKELL_EXEC_ROOT=$(dirname $(readlink ${BUILD_WORKSPACE_DIRECTORY}/bazel-out))
TOOL_LOCATION="$RULES_HASKELL_EXEC_ROOT/{TOOL}"
# Setting -pgm* flags explicitly has the unfortunate side effect
# of resetting any program flags in the GHC settings file. So we
# restore them here. See
# https://ghc.haskell.org/trac/ghc/ticket/7929.
PGM_ARGS="-pgma {CC} -pgmc {CC} -pgml {CC} -pgmP {CC} -optc-fno-stack-protector -optP-E -optP-undef -optP-traditional"

{ENV}
"$TOOL_LOCATION" $PGM_ARGS {ARGS} "$@"
"$TOOL_LOCATION" {ARGS} "$@"
126 changes: 101 additions & 25 deletions haskell/repl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
load("@bazel_skylib//lib:dicts.bzl", "dicts")
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@bazel_skylib//lib:shell.bzl", "shell")
load(":cc.bzl", "ghc_cc_program_args")
load(":private/context.bzl", "haskell_context", "render_env")
load(
":private/path_utils.bzl",
"ln",
"match_label",
"parse_pattern",
"target_unique_name",
Expand Down Expand Up @@ -220,33 +222,37 @@ def _create_HaskellReplInfo(from_source, from_binary, collect_info):
dep_info = dep_info,
)

def _create_repl(hs, posix, ctx, repl_info, output):
"""Build a multi target REPL.
def _compiler_flags_and_inputs(hs, repl_info, path_prefix = ""):
"""Collect compiler flags and inputs.

Compiler flags:
- Package databases and identifiers.
- Linker flags for C library dependencies.
- Haskell include directory flags.

Inputs:
- Source files.
- Package databases.
- C library dependencies.
- Locale archive if required.

Args:
hs: Haskell context.
ctx: Rule context.
repl_info: HaskellReplInfo provider.
output: The output for the executable REPL script.
args: list of string, output, the arguments to extend.
path_prefix: string, optional, Prefix for package db paths.

Returns:
List of providers:
DefaultInfo provider for the executable REPL script.

(args, inputs):
args: list of string, the compiler flags.
inputs: depset of File, inputs required by the compiler.
"""

# The base and directory packages are necessary for the GHCi script we use
# (loads source files and brings in scope the corresponding modules).
args = ["-hide-all-packages", "-package", "base", "-package", "directory"]
args = []

# Load built dependencies (-package-id, -package-db)
for package_id in repl_info.dep_info.package_ids:
args.extend(["-package-id", package_id])
for package_cache in repl_info.dep_info.package_databases.to_list():
args.extend([
"-package-db",
paths.join("$RULES_HASKELL_EXEC_ROOT", package_cache.dirname),
])
args.extend(["-package-db", paths.join(path_prefix, package_cache.dirname)])

# Load C library dependencies
cc_libraries_info = merge_HaskellCcLibrariesInfo(infos = [
Expand All @@ -258,17 +264,51 @@ def _create_repl(hs, posix, ctx, repl_info, output):
repl_info.dep_info.cc_info,
])
all_libraries = cc_info.linking_context.libraries_to_link.to_list()
input_libraries = get_ghci_library_files(hs, cc_libraries_info, all_libraries)
cc_libraries = get_cc_libraries(cc_libraries_info, all_libraries)
link_libraries(
get_ghci_library_files(hs, cc_libraries_info, cc_libraries),
args,
)

args.extend(ghc_cc_program_args(
paths.join(path_prefix, hs.toolchain.cc_wrapper.executable.path),
))

# Add import directories
for import_dir in repl_info.load_info.import_dirs.to_list():
args.append("-i" + (import_dir if import_dir else "."))

inputs = depset(transitive = [
repl_info.load_info.source_files,
repl_info.dep_info.package_databases,
depset(get_ghci_library_files(hs, cc_libraries_info, all_libraries)),
depset([hs.toolchain.locale_archive] if hs.toolchain.locale_archive else []),
])

return (args, inputs)

def _create_repl(hs, posix, ctx, repl_info, output):
"""Build a multi target REPL.

Args:
hs: Haskell context.
ctx: Rule context.
repl_info: HaskellReplInfo provider.
output: The output for the executable REPL script.

Returns:
List of providers:
DefaultInfo provider for the executable REPL script.

"""

# The base and directory packages are necessary for the GHCi script we use
# (loads source files and brings in scope the corresponding modules).
args = ["-hide-all-packages", "-package", "base", "-package", "directory"]

compiler_flags, inputs = _compiler_flags_and_inputs(hs, repl_info, path_prefix = "$RULES_HASKELL_EXEC_ROOT")
args.extend(compiler_flags)

# Load source files
# Force loading by source with `:add *...`.
# See https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/ghci.html#ghci-cmd-:add
Expand Down Expand Up @@ -318,7 +358,6 @@ def _create_repl(hs, posix, ctx, repl_info, output):
substitutions = {
"{ENV}": render_env(hs.env),
"{TOOL}": hs.tools.ghci.path,
"{CC}": hs.toolchain.cc_wrapper.executable.path,
"{ARGS}": " ".join(
args + [
shell.quote(a)
Expand All @@ -335,18 +374,37 @@ def _create_repl(hs, posix, ctx, repl_info, output):
hs.tools.ghci,
ghci_repl_script,
],
transitive_files = depset(transitive = [
repl_info.load_info.source_files,
repl_info.dep_info.package_databases,
depset(input_libraries),
depset([hs.toolchain.locale_archive] if hs.toolchain.locale_archive else []),
]),
transitive_files = inputs,
collect_data = ctx.attr.collect_data,
).merge(
hs.toolchain.cc_wrapper.runfiles,
),
)]

def _create_hie_bios(hs, posix, ctx, repl_info):
"""Build a hie-bios argument file.

Args:
hs: Haskell context.
ctx: Rule context.
repl_info: HaskellReplInfo provider.
output: The output for the executable REPL script.

Returns:
List of providers:
OutputGroupInfo provider for the hie-bios argument file.
"""
args, inputs = _compiler_flags_and_inputs(hs, repl_info)
args.extend(hs.toolchain.compiler_flags)
args.extend(repl_info.load_info.compiler_flags)

args_file = ctx.actions.declare_file(".%s.hie-bios" % ctx.label.name)
args_link = ctx.actions.declare_file("%s@hie-bios" % ctx.label.name)
ctx.actions.write(args_file, "\n".join(args))
ln(hs, posix, args_file, args_link, extra_inputs = inputs)

return [OutputGroupInfo(hie_bios = [args_link])]

def _haskell_repl_aspect_impl(target, ctx):
if HaskellInfo not in target:
return []
Expand Down Expand Up @@ -391,7 +449,8 @@ def _haskell_repl_impl(ctx):
repl_info = _create_HaskellReplInfo(from_source, from_binary, collect_info)
hs = haskell_context(ctx)
posix = ctx.toolchains["@rules_sh//sh/posix:toolchain_type"]
return _create_repl(hs, posix, ctx, repl_info, ctx.outputs.repl)
return _create_repl(hs, posix, ctx, repl_info, ctx.outputs.repl) + \
_create_hie_bios(hs, posix, ctx, repl_info)

haskell_repl = rule(
implementation = _haskell_repl_impl,
Expand Down Expand Up @@ -490,5 +549,22 @@ Build a REPL for multiple targets.
$ bazel run //:repl
```

### IDE Support (Experimental)

`haskell_repl` targets provide the `hie_bios` output group to optionally
generate GHCi flags for [hie-bios](https://github.com/mpickering/hie-bios)'s
`bios` cradle. You can use this for IDE support with
[ghcide](https://github.com/digital-asset/ghcide).

Given a `haskell_repl` target `//:repl` an example `.hie-bios` script could
look as follows. Please refer to the `hie-bios` documentation for further
information.

```shell
#!/usr/bin/env bash
set -euo pipefail
bazel build //:repl --output_groups=hie_bios
cat bazel-bin/repl@hie-bios >"$HIE_BIOS_OUTPUT"
```
""",
)
21 changes: 2 additions & 19 deletions haskell/toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ load(
"merge_parameter_files",
)
load(":private/actions/package.bzl", "package")
load(":cc.bzl", "ghc_cc_program_args")

_GHC_BINARIES = ["ghc", "ghc-pkg", "hsc2hs", "haddock", "ghci", "runghc", "hpc"]

Expand All @@ -40,25 +41,7 @@ def _run_ghc(hs, cc, inputs, outputs, mnemonic, arguments, params_file = None, e

# XXX: We should also tether Bazel's CC toolchain to GHC's, so that we can properly mix Bazel-compiled
# C libraries with Haskell targets.
args.add_all([
# GHC uses C compiler for assemly, linking and preprocessing as well.
"-pgma",
cc.tools.cc,
"-pgmc",
cc.tools.cc,
"-pgml",
cc.tools.cc,
"-pgmP",
cc.tools.cc,
# Setting -pgm* flags explicitly has the unfortunate side effect
# of resetting any program flags in the GHC settings file. So we
# restore them here. See
# https://ghc.haskell.org/trac/ghc/ticket/7929.
"-optc-fno-stack-protector",
"-optP-E",
"-optP-undef",
"-optP-traditional",
])
args.add_all(ghc_cc_program_args(cc.tools.cc))

compile_flags_file = hs.actions.declare_file("compile_flags_%s_%s" % (hs.name, mnemonic))
extra_args_file = hs.actions.declare_file("extra_args_%s_%s" % (hs.name, mnemonic))
Expand Down