diff --git a/haskell/BUILD.bazel b/haskell/BUILD.bazel index 621f93a13..18096933f 100644 --- a/haskell/BUILD.bazel +++ b/haskell/BUILD.bazel @@ -176,6 +176,13 @@ config_setting( }, ) +config_setting( + name = "use_deps_on_empty_th_deps", + flag_values = { + "@rules_haskell//flags:incompatible_use_deps_on_empty_th_deps": "true" + }, +) + haskell_toolchain_info( name = "toolchain_info", ) diff --git a/haskell/cc.bzl b/haskell/cc.bzl index 3726133c0..7b3667814 100644 --- a/haskell/cc.bzl +++ b/haskell/cc.bzl @@ -33,6 +33,8 @@ CcInteropInfo = provider( "cc_libraries_info": "HaskellCcLibrariesInfo", "cc_libraries": "depset, C libraries from direct linking dependencies.", "transitive_libraries": "depset, C and Haskell libraries from transitive linking dependencies.", + "cc_th_libraries": "depset, C libraries from TemplateHaskell dependencies.", + "transitive_th_libraries": "depset, C and Haskell libraries from transitive TemplateHaskell dependencies.", "plugin_libraries": "depset, C and Haskell libraries from transitive plugin dependencies.", "setup_libraries": "depset, C and Haskell libraries from Cabal setup dependencies.", }, @@ -51,6 +53,15 @@ def cc_interop_info(ctx, override_cc_toolchain = None): """ ccs = [dep[CcInfo] for dep in ctx.attr.deps if CcInfo in dep and HaskellInfo not in dep] + if hasattr(ctx.attr, "th_deps"): + th_deps = ctx.attr.th_deps + if not th_deps and ctx.attr.use_deps_on_empty_th_deps: + th_deps = ctx.attr.deps + else: + th_deps = ctx.attr.deps + + th_ccs = [dep[CcInfo] for dep in th_deps if CcInfo in dep and HaskellInfo not in dep] + hdrs = [] include_args = [] cpp_flags = [] @@ -151,7 +162,7 @@ def cc_interop_info(ctx, override_cc_toolchain = None): env["CC_WRAPPER_CPU"] = cc_toolchain.cpu cc_libraries_info = deps_HaskellCcLibrariesInfo( - ctx.attr.deps + getattr(ctx.attr, "plugins", []) + getattr(ctx.attr, "setup_deps", []), + ctx.attr.deps + th_deps + getattr(ctx.attr, "plugins", []) + getattr(ctx.attr, "setup_deps", []), ) return CcInteropInfo( tools = struct(**tools), @@ -168,11 +179,21 @@ def cc_interop_info(ctx, override_cc_toolchain = None): linker_flags = linker_flags, cc_libraries_info = cc_libraries_info, cc_libraries = get_cc_libraries(cc_libraries_info, [lib for li in cc_common.merge_cc_infos(cc_infos = ccs).linking_context.linker_inputs.to_list() for lib in li.libraries]), + cc_th_libraries = get_cc_libraries(cc_libraries_info, [ + lib + for li in cc_common.merge_cc_infos(cc_infos = th_ccs).linking_context.linker_inputs.to_list() + for lib in li.libraries + ]), transitive_libraries = [lib for li in cc_common.merge_cc_infos(cc_infos = [ dep[CcInfo] for dep in ctx.attr.deps if CcInfo in dep ]).linking_context.linker_inputs.to_list() for lib in li.libraries], + transitive_th_libraries = [lib for li in cc_common.merge_cc_infos(cc_infos = [ + dep[CcInfo] + for dep in th_deps + if CcInfo in dep + ]).linking_context.linker_inputs.to_list() for lib in li.libraries], plugin_libraries = [lib for li in cc_common.merge_cc_infos(cc_infos = [ dep[CcInfo] for plugin in getattr(ctx.attr, "plugins", []) diff --git a/haskell/defs.bzl b/haskell/defs.bzl index f553f2aa8..f6e9c4644 100644 --- a/haskell/defs.bzl +++ b/haskell/defs.bzl @@ -61,6 +61,9 @@ _haskell_common_attrs = { "modules": attr.label_list( providers = [HaskellModuleInfo], ), + "th_deps": attr.label_list( + aspects = [haskell_cc_libraries_aspect], + ), # a proxy for ctx.label so that the transition can access it "label_string": attr.string(), "data": attr.label_list( @@ -100,6 +103,10 @@ _haskell_common_attrs = { cfg = "host", default = Label("@rules_haskell//haskell:ghc_wrapper"), ), + "use_deps_on_empty_th_deps": attr.bool( + # Should be provided by the wrappers, not the user + mandatory=True, + ), "worker": attr.label( default = None, executable = True, @@ -233,6 +240,10 @@ def _haskell_worker_wrapper(rule_type, **kwargs): kwargs.pop("compiler_flags") defaults = dict( + use_deps_on_empty_th_deps = select({ + "@rules_haskell//haskell:use_deps_on_empty_th_deps": True, + "//conditions:default": False, + }), worker = select({ "@rules_haskell//haskell:use_worker": Label("@rules_haskell//tools/worker:bin"), "//conditions:default": None, @@ -254,6 +265,7 @@ def haskell_binary( extra_srcs = [], deps = [], narrowed_deps = [], + th_deps = [], data = [], compiler_flags = [], ghcopts = [], @@ -322,6 +334,8 @@ def haskell_binary( Note: This attribute is experimental and not ready for production, yet. modules: List of extra haskell_module() dependencies to be linked into this binary. Note: This attribute is experimental and not ready for production, yet. + th_deps: List of libraries needed to run TemplateHaskell code. These are needed + only at compile time. data: See [Bazel documentation](https://docs.bazel.build/versions/master/be/common-definitions.html#common.data)., compiler_flags: DEPRECATED. Use new name ghcopts. ghcopts: Flags to pass to Haskell compiler. Subject to Make variable substitution. @@ -345,6 +359,7 @@ def haskell_binary( extra_srcs = extra_srcs, deps = deps, narrowed_deps = narrowed_deps, + th_deps = th_deps, data = data, compiler_flags = compiler_flags, ghcopts = ghcopts, @@ -405,6 +420,7 @@ def haskell_test( extra_srcs = [], deps = [], narrowed_deps = [], + th_deps = [], data = [], compiler_flags = [], ghcopts = [], @@ -463,6 +479,8 @@ def haskell_test( Note: This attribute is experimental and not ready for production, yet. modules: List of extra haskell_module() dependencies to be linked into this test. Note: This attribute is experimental and not ready for production, yet. + th_deps: List of libraries needed to run TemplateHaskell code. These are needed + only at compile time. data: See [Bazel documentation](https://docs.bazel.build/versions/master/be/common-definitions.html#common.data)., compiler_flags: DEPRECATED. Use new name ghcopts. ghcopts: Flags to pass to Haskell compiler. Subject to Make variable substitution. @@ -499,6 +517,7 @@ def haskell_test( extra_srcs = extra_srcs, deps = deps, narrowed_deps = narrowed_deps, + th_deps = th_deps, data = data, compiler_flags = compiler_flags, ghcopts = ghcopts, @@ -546,6 +565,7 @@ def haskell_library( deps = [], narrowed_deps = [], modules = [], + th_deps = [], data = [], compiler_flags = [], ghcopts = [], @@ -606,6 +626,8 @@ def haskell_library( Note: This attribute is experimental and not ready for production, yet. modules: List of extra haskell_module() dependencies to be linked into this library. Note: This attribute is experimental and not ready for production, yet. + th_deps: List of libraries needed to run TemplateHaskell code. These are needed + only at compile time. data: See [Bazel documentation](https://docs.bazel.build/versions/master/be/common-definitions.html#common.data)., compiler_flags: DEPRECATED. Use new name ghcopts. ghcopts: Flags to pass to Haskell compiler. Subject to Make variable substitution. @@ -636,6 +658,7 @@ def haskell_library( deps = deps, narrowed_deps = narrowed_deps, modules = modules, + th_deps = th_deps, data = data, compiler_flags = compiler_flags, ghcopts = ghcopts, diff --git a/haskell/private/actions/compile.bzl b/haskell/private/actions/compile.bzl index ee5f3d027..b27be66fc 100644 --- a/haskell/private/actions/compile.bzl +++ b/haskell/private/actions/compile.bzl @@ -104,7 +104,28 @@ def _process_hsc_file(hs, cc, hsc_flags, hsc_inputs, hsc_file): return hs_out, idir -def _compilation_defaults(hs, cc, java, posix, dep_info, plugin_dep_info, srcs, module_map, import_dir_map, extra_srcs, user_compile_flags, output_mode, with_profiling, interfaces_dir, objects_dir, my_pkg_id, version, plugins, non_default_plugins, preprocessors): +def _compilation_defaults( + hs, + cc, + java, + posix, + dep_info, + th_dep_info, + plugin_dep_info, + srcs, + module_map, + import_dir_map, + extra_srcs, + user_compile_flags, + output_mode, + with_profiling, + interfaces_dir, + objects_dir, + my_pkg_id, + version, + plugins, + non_default_plugins, + preprocessors): """Compute variables common to all compilation targets (binary and library). Returns: @@ -192,8 +213,15 @@ def _compilation_defaults(hs, cc, java, posix, dep_info, plugin_dep_info, srcs, (pkg_info_inputs, pkg_info_args) = pkg_info_to_compile_flags( hs, pkg_info = expose_packages( - package_ids = hs.package_ids, - package_databases = dep_info.package_databases, + package_ids = set.to_list( + set.from_list( + hs.package_ids + hs.th_package_ids + ) + ), + package_databases = depset(transitive=[ + dep_info.package_databases, + th_dep_info.package_databases, + ]), version = version, ), plugin_pkg_info = expose_packages( @@ -212,9 +240,10 @@ def _compilation_defaults(hs, cc, java, posix, dep_info, plugin_dep_info, srcs, hsc_inputs = [] if version: - (version_macro_headers, version_macro_flags) = version_macro_includes(dep_info) - hsc_flags += ["--cflag=" + x for x in version_macro_flags] - hsc_inputs += set.to_list(version_macro_headers) + for info in [dep_info, th_dep_info]: + (version_macro_headers, version_macro_flags) = version_macro_includes(info) + hsc_flags += ["--cflag=" + x for x in version_macro_flags] + hsc_inputs += set.to_list(version_macro_headers) # Add import hierarchy root. # Note that this is not perfect, since GHC requires hs-boot files @@ -349,7 +378,7 @@ def _compilation_defaults(hs, cc, java, posix, dep_info, plugin_dep_info, srcs, # Transitive library dependencies for runtime. link_libraries( - get_ghci_library_files(hs, cc.cc_libraries_info, cc.cc_libraries, for_th_only = True), + get_ghci_library_files(hs, cc.cc_libraries_info, cc.cc_th_libraries, for_th_only = True), args, ) @@ -363,11 +392,13 @@ def _compilation_defaults(hs, cc, java, posix, dep_info, plugin_dep_info, srcs, depset(cc.hdrs), dep_info.package_databases, dep_info.interface_dirs, - dep_info.hs_libraries, + th_dep_info.package_databases, + th_dep_info.interface_dirs, + th_dep_info.hs_libraries, plugin_dep_info.package_databases, plugin_dep_info.interface_dirs, plugin_dep_info.hs_libraries, - depset(get_ghci_library_files(hs, cc.cc_libraries_info, cc.transitive_libraries + cc.plugin_libraries)), + depset(get_ghci_library_files(hs, cc.cc_libraries_info, cc.transitive_th_libraries + cc.plugin_libraries)), java.inputs, preprocessors.inputs, plugin_tool_inputs, @@ -405,6 +436,7 @@ def compile_binary( java, posix, dep_info, + th_dep_info, plugin_dep_info, srcs, module_map, @@ -431,7 +463,29 @@ def compile_binary( source_files: set of Haskell source files boot_files: set of Haskell boot files """ - c = _compilation_defaults(hs, cc, java, posix, dep_info, plugin_dep_info, srcs, module_map, import_dir_map, extra_srcs, user_compile_flags, "dynamic" if dynamic else "static", with_profiling, interfaces_dir, objects_dir, my_pkg_id = None, version = version, plugins = plugins, non_default_plugins = non_default_plugins, preprocessors = preprocessors) + c = _compilation_defaults( + hs, + cc, + java, + posix, + dep_info, + th_dep_info, + plugin_dep_info, + srcs, + module_map, + import_dir_map, + extra_srcs, + user_compile_flags, + "dynamic" if dynamic else "static", + with_profiling, + interfaces_dir, + objects_dir, + my_pkg_id = None, + version = version, + plugins = plugins, + non_default_plugins = non_default_plugins, + preprocessors = preprocessors, + ) c.args.add_all(["-main-is", main_function]) if dynamic: # For binaries, GHC creates .o files even for code to be @@ -477,6 +531,7 @@ def compile_library( java, posix, dep_info, + th_dep_info, plugin_dep_info, srcs, module_map, @@ -505,7 +560,29 @@ def compile_library( boot_files: set of Haskell boot files import_dirs: import directories that should make all modules visible (for GHCi) """ - c = _compilation_defaults(hs, cc, java, posix, dep_info, plugin_dep_info, srcs, module_map, import_dir_map, extra_srcs, user_compile_flags, "dynamic-too" if with_shared else "static", with_profiling, interfaces_dir, objects_dir, my_pkg_id = my_pkg_id, version = my_pkg_id.version, plugins = plugins, non_default_plugins = non_default_plugins, preprocessors = preprocessors) + c = _compilation_defaults( + hs, + cc, + java, + posix, + dep_info, + th_dep_info, + plugin_dep_info, + srcs, + module_map, + import_dir_map, + extra_srcs, + user_compile_flags, + "dynamic-too" if with_shared else "static", + with_profiling, + interfaces_dir, + objects_dir, + my_pkg_id = my_pkg_id, + version = my_pkg_id.version, + plugins = plugins, + non_default_plugins = non_default_plugins, + preprocessors = preprocessors, + ) if with_shared: c.args.add("-dynamic-too") diff --git a/haskell/private/context.bzl b/haskell/private/context.bzl index a0284e628..f8080b47e 100644 --- a/haskell/private/context.bzl +++ b/haskell/private/context.bzl @@ -19,8 +19,21 @@ def haskell_context(ctx, attr = None): if not attr: attr = ctx.attr - deps = (attr.deps if hasattr(attr, "deps") else []) + (attr.exports if hasattr(attr, "exports") else []) + (attr.narrowed_deps if hasattr(attr, "narrowed_deps") else []) - package_ids = all_dependencies_package_ids(deps) + deps = getattr(attr, "deps", []) + + if hasattr(attr, "th_deps"): + th_deps = attr.th_deps + if not th_deps and attr.use_deps_on_empty_th_deps: + th_deps = deps + else: + th_deps = deps + + package_ids = all_dependencies_package_ids( + deps + + getattr(attr, "exports", []) + + getattr(attr, "narrowed_deps", []) + ) + th_package_ids = all_dependencies_package_ids(th_deps) if hasattr(attr, "src_strip_prefix"): src_strip_prefix = attr.src_strip_prefix @@ -63,6 +76,7 @@ def haskell_context(ctx, attr = None): ghc_wrapper = ghc_wrapper, worker = worker, package_ids = package_ids, + th_package_ids = th_package_ids, src_root = src_root, package_root = paths.join(ctx.label.workspace_root, ctx.label.package), env = env, diff --git a/haskell/private/haskell_impl.bzl b/haskell/private/haskell_impl.bzl index 02578730b..91eaeaf4f 100644 --- a/haskell/private/haskell_impl.bzl +++ b/haskell/private/haskell_impl.bzl @@ -173,6 +173,12 @@ def _haskell_binary_common_impl(ctx, is_test): dep_info = gather_dep_info(ctx.attr.name, ctx.attr.deps) all_deps_info = gather_dep_info(ctx.attr.name, deps) + th_deps = getattr(ctx.attr, "th_deps", []) + if not th_deps and ctx.attr.use_deps_on_empty_th_deps: + th_deps = deps + + th_dep_info = gather_dep_info(ctx.attr.name, th_deps) + modules = ctx.attr.modules if modules and ctx.files.srcs: fail("""Only one of "srcs" or "modules" attributes must be specified in {}""".format(ctx.label)) @@ -231,6 +237,7 @@ def _haskell_binary_common_impl(ctx, is_test): java, posix, dep_info, + th_dep_info, plugin_dep_info, srcs = srcs_files, module_map = module_map, @@ -423,6 +430,12 @@ def haskell_library_impl(ctx): dep_info = gather_dep_info(ctx.attr.name, ctx.attr.deps + ctx.attr.exports) narrowed_deps_info = gather_dep_info(ctx.attr.name, ctx.attr.narrowed_deps) all_deps_info = gather_dep_info(ctx.attr.name, deps) + + th_deps = getattr(ctx.attr, "th_deps", []) + if not th_deps and ctx.attr.use_deps_on_empty_th_deps: + th_deps = deps + th_dep_info = gather_dep_info(ctx.attr.name, th_deps) + all_plugins = ctx.attr.plugins + ctx.attr.non_default_plugins plugin_dep_info = gather_dep_info( ctx.attr.name, @@ -482,6 +495,7 @@ def haskell_library_impl(ctx): java, posix, dep_info, + th_dep_info, plugin_dep_info, srcs = srcs_files, module_map = module_map, diff --git a/tests/th_deps/BUILD.bazel b/tests/th_deps/BUILD.bazel new file mode 100644 index 000000000..9cd047758 --- /dev/null +++ b/tests/th_deps/BUILD.bazel @@ -0,0 +1,55 @@ +load( + "@rules_haskell//haskell:defs.bzl", + "haskell_library", + "haskell_test", +) + +package(default_testonly = 1) + +test_suite( + name = "th_deps", + tests = [ + ":use-lib-b" + ], + ) + + +haskell_library( + name = "lib-a", + srcs = [ "LibA.hs" ], + deps = [ + "//tests/hackage:base", + ], +) + +haskell_library( + name = "lib-b", + srcs = [ "LibB.hs", "TH.hs" ], + hidden_modules = ["TH"], + th_deps = [ + ":lib-a", + "//tests/data:ourclibrary", + "//tests/hackage:template-haskell", + ], + deps = [ + "//tests/hackage:base", + ], +) + +haskell_test( + name = "use-lib-b", + srcs = [ + "Main.hs", + ], + deps = [ + ":lib-b", + "//tests/hackage:base", + ], +) + +filegroup( + name = "all_files", + testonly = True, + srcs = glob(["**"]), + visibility = ["//visibility:public"], +) diff --git a/tests/th_deps/LibA.hs b/tests/th_deps/LibA.hs new file mode 100644 index 000000000..32987af4c --- /dev/null +++ b/tests/th_deps/LibA.hs @@ -0,0 +1,4 @@ +module LibA(hype) where + +hype :: String -> String +hype x = x ++ "!" diff --git a/tests/th_deps/LibB.hs b/tests/th_deps/LibB.hs new file mode 100644 index 000000000..b15ff3225 --- /dev/null +++ b/tests/th_deps/LibB.hs @@ -0,0 +1,8 @@ +{-# LANGUAGE TemplateHaskell #-} +module LibB(value) where + +import Language.Haskell.TH +import TH(valueQ) + +value :: String +value = $valueQ diff --git a/tests/th_deps/Main.hs b/tests/th_deps/Main.hs new file mode 100644 index 000000000..f18e3e13d --- /dev/null +++ b/tests/th_deps/Main.hs @@ -0,0 +1,12 @@ +module Main where + +import Control.Monad (unless) +import System.Exit (exitFailure) +import System.IO (hPutStrLn, stderr) +import LibB(value) + +main :: IO () +main = + unless (value == "42!") $ do + hPutStrLn stderr "Expected 42!" + exitFailure diff --git a/tests/th_deps/TH.hs b/tests/th_deps/TH.hs new file mode 100644 index 000000000..e023e7ac8 --- /dev/null +++ b/tests/th_deps/TH.hs @@ -0,0 +1,10 @@ +{-# LANGUAGE TemplateHaskell #-} +module TH(valueQ) where + +import Language.Haskell.TH +import LibA(hype) + +foreign import ccall "c_add_one" c_add_one' :: Int -> Int + +valueQ :: Q Exp +valueQ = [| hype (show (c_add_one' 41)) |]