diff options
Diffstat (limited to 'third_party/bazel/rules_haskell/haskell/private/actions/repl.bzl')
-rw-r--r-- | third_party/bazel/rules_haskell/haskell/private/actions/repl.bzl | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/third_party/bazel/rules_haskell/haskell/private/actions/repl.bzl b/third_party/bazel/rules_haskell/haskell/private/actions/repl.bzl new file mode 100644 index 000000000000..5de64955d0af --- /dev/null +++ b/third_party/bazel/rules_haskell/haskell/private/actions/repl.bzl @@ -0,0 +1,175 @@ +"""GHCi REPL support""" + +load(":private/context.bzl", "render_env") +load(":private/packages.bzl", "expose_packages", "pkg_info_to_compile_flags") +load( + ":private/path_utils.bzl", + "get_lib_name", + "is_shared_library", + "link_libraries", + "ln", + "target_unique_name", +) +load(":providers.bzl", "get_libs_for_ghc_linker") +load( + ":private/set.bzl", + "set", +) +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@bazel_skylib//lib:shell.bzl", "shell") + +def build_haskell_repl( + hs, + ghci_script, + ghci_repl_wrapper, + user_compile_flags, + repl_ghci_args, + hs_info, + output, + package_databases, + version, + lib_info = None): + """Build REPL script. + + Args: + hs: Haskell context. + hs_info: HaskellInfo. + + package_databases: package caches excluding the cache file of the package + we're creating a REPL for. + lib_info: If we're building REPL for a library target, pass + HaskellLibraryInfo here, otherwise it should be None. + + Returns: + None. + """ + + # The base and directory packages are necessary for the GHCi script we use + # (loads source files and brings in scope the corresponding modules). + args = ["-package", "base", "-package", "directory"] + + pkg_ghc_info = expose_packages( + hs_info, + lib_info, + use_direct = False, + use_my_pkg_id = None, + custom_package_databases = package_databases, + version = version, + ) + args += pkg_info_to_compile_flags(pkg_ghc_info) + + lib_imports = [] + if lib_info != None: + for idir in set.to_list(hs_info.import_dirs): + args += ["-i{0}".format(idir)] + lib_imports.append(idir) + + link_ctx = hs_info.cc_dependencies.dynamic_linking + libs_to_link = link_ctx.dynamic_libraries_for_runtime.to_list() + + # External C libraries that we need to make available to the REPL. + libraries = link_libraries(libs_to_link, args) + + # Transitive library dependencies to have in runfiles. + (library_deps, ld_library_deps, ghc_env) = get_libs_for_ghc_linker( + hs, + hs_info.transitive_cc_dependencies, + path_prefix = "$RULES_HASKELL_EXEC_ROOT", + ) + library_path = [paths.dirname(lib.path) for lib in library_deps] + ld_library_path = [paths.dirname(lib.path) for lib in ld_library_deps] + + repl_file = hs.actions.declare_file(target_unique_name(hs, "repl")) + + add_sources = ["*" + f.path for f in set.to_list(hs_info.source_files)] + + ghci_repl_script = hs.actions.declare_file( + target_unique_name(hs, "ghci-repl-script"), + ) + hs.actions.expand_template( + template = ghci_script, + output = ghci_repl_script, + substitutions = { + "{ADD_SOURCES}": " ".join(add_sources), + "{COMMANDS}": "", + }, + ) + + # Extra arguments. + # `compiler flags` is the default set of arguments for the repl, + # augmented by `repl_ghci_args`. + # The ordering is important, first compiler flags (from toolchain + # and local rule), then from `repl_ghci_args`. This way the more + # specific arguments are listed last, and then have more priority in + # GHC. + # Note that most flags for GHCI do have their negative value, so a + # negative flag in `repl_ghci_args` can disable a positive flag set + # in `user_compile_flags`, such as `-XNoOverloadedStrings` will disable + # `-XOverloadedStrings`. + args += hs.toolchain.compiler_flags + user_compile_flags + hs.toolchain.repl_ghci_args + repl_ghci_args + + hs.actions.expand_template( + template = ghci_repl_wrapper, + output = repl_file, + substitutions = { + "{ENV}": render_env(ghc_env), + "{TOOL}": hs.tools.ghci.path, + "{ARGS}": " ".join( + [ + "-ghci-script", + paths.join("$RULES_HASKELL_EXEC_ROOT", ghci_repl_script.path), + ] + [ + shell.quote(a) + for a in args + ], + ), + }, + is_executable = True, + ) + + ghc_info = struct( + has_version = pkg_ghc_info.has_version, + library_path = library_path, + ld_library_path = ld_library_path, + packages = pkg_ghc_info.packages, + package_ids = pkg_ghc_info.package_ids, + package_dbs = pkg_ghc_info.package_dbs, + lib_imports = lib_imports, + libraries = libraries, + execs = struct( + ghc = hs.tools.ghc.path, + ghci = hs.tools.ghci.path, + runghc = hs.tools.runghc.path, + ), + flags = struct( + compiler = user_compile_flags, + toolchain_compiler = hs.toolchain.compiler_flags, + repl = repl_ghci_args, + toolchain_repl = hs.toolchain.repl_ghci_args, + ), + ) + ghc_info_file = hs.actions.declare_file( + target_unique_name(hs, "ghc-info"), + ) + hs.actions.write( + output = ghc_info_file, + content = ghc_info.to_json(), + ) + + # XXX We create a symlink here because we need to force + # hs.tools.ghci and ghci_script and the best way to do that is + # to use hs.actions.run. That action, in turn must produce + # a result, so using ln seems to be the only sane choice. + extra_inputs = depset(transitive = [ + depset([ + hs.tools.ghci, + ghci_repl_script, + repl_file, + ghc_info_file, + ]), + set.to_depset(package_databases), + depset(library_deps), + depset(ld_library_deps), + set.to_depset(hs_info.source_files), + ]) + ln(hs, repl_file, output, extra_inputs) |