Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi target haskell_repl #736

Merged
merged 16 commits into from
Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from 14 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
15 changes: 8 additions & 7 deletions haskell/assets/ghci_script
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import qualified System.Directory as Dir
rules_haskell_stdout_dupe <- Handle.hDuplicate IO.stdout
:{
(rules_haskell_stdout_copy_file, rules_haskell_stdout_copy_h) <- do
tmpDir <- Dir.getTemporaryDirectory Prelude.>>= Dir.canonicalizePath
(fn, h) <- IO.openTempFile tmpDir "rules-haskell-ghci-repl"
Handle.hDuplicateTo h IO.stdout
Prelude.return (fn, h)
rules_haskell_tmp_dir <- Dir.getTemporaryDirectory Prelude.>>= Dir.canonicalizePath
(rules_haskell_fn, rules_haskell_h) <- IO.openTempFile rules_haskell_tmp_dir "rules-haskell-ghci-repl"
Handle.hDuplicateTo rules_haskell_h IO.stdout
Prelude.return (rules_haskell_fn, rules_haskell_h)
:}
:show modules
:{
Expand All @@ -19,10 +19,10 @@ rules_haskell_loaded_modules <- do
-- stream at offset 0 did not work (no data is there, although the
-- corresponding file certainly contained it after flushing). Couldn't
-- figure this one out, so we first close the file and then read from it.
h <- IO.openFile rules_haskell_stdout_copy_file IO.ReadMode
xs <- Handle.hGetContents h
rules_haskell_h <- IO.openFile rules_haskell_stdout_copy_file IO.ReadMode
rules_haskell_xs <- Handle.hGetContents rules_haskell_h
Dir.removeFile rules_haskell_stdout_copy_file
Prelude.return Prelude.$ Prelude.takeWhile (Prelude./= ' ') Prelude.<$> Prelude.lines xs
Prelude.return Prelude.$ Prelude.takeWhile (Prelude./= ' ') Prelude.<$> Prelude.lines rules_haskell_xs
:}
hDuplicateTo rules_haskell_stdout_dupe IO.stdout
:{
Expand All @@ -36,3 +36,4 @@ let rules_haskell_add_loaded_modules _ =
:undef rules_haskell_add_loaded_modules
-- reload modules to drop the rules_haskell* definitions
:reload
{COMMANDS}
12 changes: 6 additions & 6 deletions haskell/doctest.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

load("@bazel_skylib//lib:dicts.bzl", "dicts")
load("@bazel_skylib//lib:paths.bzl", "paths")
load(":private/context.bzl", "haskell_context")
load(":private/context.bzl", "haskell_context", "render_env")
load(
":private/path_utils.bzl",
"get_lib_name",
Expand Down Expand Up @@ -129,7 +129,10 @@ def _haskell_doctest_single(target, ctx):
])

# Transitive library dependencies for runtime.
(library_deps, ld_library_deps, ghc_env) = get_libs_for_ghc_linker(hs, build_info)
(library_deps, ld_library_deps, ghc_env) = get_libs_for_ghc_linker(
hs,
build_info.transitive_cc_dependencies,
)

header_files = lib_info.header_files if lib_info != None else bin_info.header_files

Expand Down Expand Up @@ -173,10 +176,7 @@ def _haskell_doctest_single(target, ctx):
module_list = exposed_modules_file.path,
# XXX Workaround
# https://github.com/bazelbuild/bazel/issues/5980.
env = "\n".join([
"export {}={}".format(k, v)
for k, v in hs.env.items()
]),
env = render_env(hs.env),
),
arguments = [args],
# NOTE It looks like we must specify the paths here as well as via -L
Expand Down
7 changes: 2 additions & 5 deletions haskell/haddock.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ load(
"HaskellBuildInfo",
"HaskellLibraryInfo",
)
load(":private/context.bzl", "haskell_context")
load(":private/context.bzl", "haskell_context", "render_env")
load(":private/set.bzl", "set")

def _get_haddock_path(package_id):
Expand Down Expand Up @@ -102,10 +102,7 @@ def _haskell_doc_aspect_impl(target, ctx):
"%{haddock}": hs.tools.haddock.path,
# XXX Workaround
# https://github.com/bazelbuild/bazel/issues/5980.
"%{env}": "\n".join([
"export {}={}".format(k, v)
for k, v in hs.env.items()
]),
"%{env}": render_env(hs.env),
},
is_executable = True,
)
Expand Down
9 changes: 9 additions & 0 deletions haskell/haskell.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ load(
_haskell_library_impl = "haskell_library_impl",
_haskell_test_impl = "haskell_test_impl",
)
load(
":repl.bzl",
_haskell_repl = "haskell_repl",
_haskell_repl_aspect = "haskell_repl_aspect",
)

# For re-exports:
load(
Expand Down Expand Up @@ -304,6 +309,10 @@ haskell_register_ghc_bindists = _haskell_register_ghc_bindists

haskell_register_ghc_nixpkgs = _haskell_register_ghc_nixpkgs

haskell_repl = _haskell_repl

haskell_repl_aspect = _haskell_repl_aspect

haskell_toolchain = _haskell_toolchain

haskell_proto_library = _haskell_proto_library
Expand Down
12 changes: 6 additions & 6 deletions haskell/lint.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ load(
"HaskellLibraryInfo",
"HaskellLintInfo",
)
load(":private/context.bzl", "haskell_context")
load(":private/context.bzl", "haskell_context", "render_env")
load(":private/packages.bzl", "expose_packages", "pkg_info_to_ghc_args")
load(
":private/path_utils.bzl",
Expand Down Expand Up @@ -74,7 +74,10 @@ def _haskell_lint_aspect_impl(target, ctx):
)

# Transitive library dependencies for runtime.
(library_deps, ld_library_deps, _ghc_env) = get_libs_for_ghc_linker(hs, build_info)
(library_deps, ld_library_deps, _ghc_env) = get_libs_for_ghc_linker(
hs,
build_info.transitive_cc_dependencies,
)

ctx.actions.run_shell(
inputs = depset(transitive = [
Expand All @@ -98,10 +101,7 @@ def _haskell_lint_aspect_impl(target, ctx):
output = lint_log.path,
# XXX Workaround
# https://github.com/bazelbuild/bazel/issues/5980.
env = "\n".join([
"export {}={}".format(k, v)
for k, v in hs.env.items()
]),
env = render_env(hs.env),
),
arguments = [args],
use_default_shell_env = True,
Expand Down
5 changes: 4 additions & 1 deletion haskell/private/actions/compile.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,10 @@ def _compilation_defaults(hs, cc, java, dep_info, srcs, import_dir_map, extra_sr
args.add(f)

# Transitive library dependencies for runtime.
(library_deps, ld_library_deps, ghc_env) = get_libs_for_ghc_linker(hs, dep_info)
(library_deps, ld_library_deps, ghc_env) = get_libs_for_ghc_linker(
hs,
dep_info.transitive_cc_dependencies,
)

return DefaultCompileInfo(
args = args,
Expand Down
20 changes: 13 additions & 7 deletions haskell/private/actions/repl.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""GHCi REPL support"""

load(":private/context.bzl", "render_env")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be possible to factor all this file out with the new haskell_repl rule?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite at the moment. haskell_repl is its own rule and can return a DefaultInto and thereby properly use runfiles. haskell/private/actions/repl.bzl creates an extra output in the regular haskell_* rules and cannot return a dedicated DefaultInfo with runfiles. Instead it has to use the ln trick to force it's dependencies and refer to them in bazel-out.

I agree that there is duplication that we don't want to keep around in the long term. One possible way forward could be to drop haskell/private/actions/repl.bzl completely and replace it by the haskell/repl.bzl aspect, similar to haskell/haddock.bzl. But, I'd prefer to do that in a separate PR.

load(":private/packages.bzl", "expose_packages", "pkg_info_to_ghc_args")
load(
":private/path_utils.bzl",
Expand Down Expand Up @@ -84,7 +85,7 @@ def build_haskell_repl(
# Transitive library dependencies to have in runfiles.
(library_deps, ld_library_deps, ghc_env) = get_libs_for_ghc_linker(
hs,
build_info,
build_info.transitive_cc_dependencies,
path_prefix = "$RULES_HASKELL_EXEC_ROOT",
)
library_path = [paths.dirname(lib.path) for lib in library_deps]
Expand All @@ -108,13 +109,12 @@ def build_haskell_repl(
output = ghci_repl_script,
substitutions = {
"{ADD_SOURCES}": " ".join(add_sources),
"{COMMANDS}": "",
},
)

source_files = lib_info.source_files if lib_info != None else bin_info.source_files

args += ["-ghci-script", ghci_repl_script.path]

# Extra arguments.
# `compiler flags` is the default set of arguments for the repl,
# augmented by `repl_ghci_args`.
Expand All @@ -132,11 +132,17 @@ def build_haskell_repl(
template = ghci_repl_wrapper,
output = repl_file,
substitutions = {
"{LIBPATH}": ghc_env["LIBRARY_PATH"],
"{LDLIBPATH}": ghc_env["LD_LIBRARY_PATH"],
"{ENV}": render_env(ghc_env),
"{TOOL}": hs.tools.ghci.path,
"{SCRIPT_LOCATION}": output.path,
"{ARGS}": " ".join([shell.quote(a) for a in args]),
"{ARGS}": " ".join(
[
"-ghci-script",
paths.join("$RULES_HASKELL_EXEC_ROOT", ghci_repl_script.path),
] + [
shell.quote(a)
for a in args
],
),
},
is_executable = True,
)
Expand Down
7 changes: 3 additions & 4 deletions haskell/private/actions/runghc.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""runghc support"""

load(":private/context.bzl", "render_env")
load(":private/packages.bzl", "expose_packages", "pkg_info_to_ghc_args")
load(
":private/path_utils.bzl",
Expand Down Expand Up @@ -73,7 +74,7 @@ def build_haskell_runghc(
# Transitive library dependencies to have in runfiles.
(library_deps, ld_library_deps, ghc_env) = get_libs_for_ghc_linker(
hs,
build_info,
build_info.transitive_cc_dependencies,
path_prefix = "$RULES_HASKELL_EXEC_ROOT",
)

Expand Down Expand Up @@ -102,10 +103,8 @@ def build_haskell_runghc(
template = runghc_wrapper,
output = runghc_file,
substitutions = {
"{LIBPATH}": ghc_env["LIBRARY_PATH"],
"{LDLIBPATH}": ghc_env["LD_LIBRARY_PATH"],
"{ENV}": render_env(ghc_env),
"{TOOL}": hs.tools.runghc.path,
"{SCRIPT_LOCATION}": output.path,
"{ARGS}": " ".join([shell.quote(a) for a in runghc_args]),
},
is_executable = True,
Expand Down
15 changes: 15 additions & 0 deletions haskell/private/context.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,18 @@ def haskell_context(ctx, attr = None):
genfiles_dir = ctx.genfiles_dir,
coverage_enabled = coverage_enabled,
)

def render_env(env):
"""Render environment dict to shell exports.

Example:

>>> render_env({"PATH": "foo:bar", "LANG": "lang"})
export PATH=foo:bar
export LANG=lang

"""
return "\n".join([
"export {}={}".format(k, v)
for k, v in env.items()
])
24 changes: 21 additions & 3 deletions haskell/private/ghci_repl_wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@ EOF
exit 1
fi

# Derived from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash).
if [[ -z "$RUNFILES_DIR" ]]; then
if [[ -d "$0.runfiles" ]]; then
export RUNFILES_DIR="$0.runfiles"
fi
fi
if [[ -z "$RUNFILES_MANIFEST_FILE" ]]; then
if [[ -f "$0.runfiles_manifest" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
elif [[ -f "$0.runfiles/MANIFEST" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
fi
fi

# GHCi script and libraries are loaded relative to workspace directory.
# bazel run //some:target@repl will be executed from the workspace directory.
# bazel run //some:haskell_repl will be executed from its execroot.
# Explicitly change into the workspace root in that case.
cd "$BUILD_WORKSPACE_DIRECTORY"

# This is a workaround for https://github.com/bazelbuild/bazel/issues/5506
# and also for the fact that REPL script relies on so-called “convenience
# links” and the names of those links are controlled by the --symlink_prefix
Expand All @@ -34,8 +54,6 @@ fi

RULES_HASKELL_EXEC_ROOT=$(dirname $(readlink ${BUILD_WORKSPACE_DIRECTORY}/bazel-out))
TOOL_LOCATION="$RULES_HASKELL_EXEC_ROOT/{TOOL}"
SCRIPT_LOCATION="$RULES_HASKELL_EXEC_ROOT/{SCRIPT_LOCATION}"

export LIBRARY_PATH={LIBPATH}
export LD_LIBRARY_PATH={LDLIBPATH}
{ENV}
"$TOOL_LOCATION" {ARGS} "$@"
6 changes: 3 additions & 3 deletions haskell/private/path_utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,16 @@ def make_path(libs, prefix = None):
Returns:
String: paths to the given library directories separated by ":".
"""
r = []
r = set.empty()

for lib in libs:
lib_dir = paths.dirname(lib.path)
if prefix:
lib_dir = paths.join(prefix, lib_dir)

r.append(lib_dir)
set.mutable_insert(r, lib_dir)

return ":".join(r)
return ":".join(set.to_list(r))

def darwin_convert_to_dylibs(hs, libs):
"""Convert .so dynamic libraries to .dylib.
Expand Down
6 changes: 3 additions & 3 deletions haskell/private/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ HaskellBuildInfo = provider(
},
)

def get_libs_for_ghc_linker(hs, build_info, path_prefix = None):
def get_libs_for_ghc_linker(hs, transitive_cc_dependencies, path_prefix = None):
"""Return all C library dependencies for GHC's linker.

GHC has it's own builtin linker. It is used for Template Haskell, for GHCi,
Expand All @@ -120,7 +120,7 @@ def get_libs_for_ghc_linker(hs, build_info, path_prefix = None):

Args:
hs: Haskell context.
build_info: HaskellBinaryInfo provider.
transitive_cc_dependencies: HaskellCcInfo provider.
path_prefix: Prefix for paths in GHC environment variables.

Returns:
Expand All @@ -131,7 +131,7 @@ def get_libs_for_ghc_linker(hs, build_info, path_prefix = None):
env: A mapping environment variables LIBRARY_PATH and LD_LIBRARY_PATH,
to the corresponding values as expected by GHC.
"""
trans_link_ctx = build_info.transitive_cc_dependencies.dynamic_linking
trans_link_ctx = transitive_cc_dependencies.dynamic_linking

libs_to_link = trans_link_ctx.libraries_to_link.to_list()
libs_for_runtime = trans_link_ctx.dynamic_libraries_for_runtime.to_list()
Expand Down
Loading