From 5d39195a6b52fb4e380132626a2e3d22b930ae10 Mon Sep 17 00:00:00 2001 From: Kyle Butt Date: Thu, 13 Feb 2025 10:38:27 -0700 Subject: [PATCH] cabal: Add support for empty libraries Previously, an empty library that only exported modules from another library failed to build. Bazel would error out when the .a file wasn't created. These libraries are becoming more common in the Haskell ecosystem, so we need to support them. There is a straightforward way to handle these libraries. We make the package database the default output. We avoid creating an output file for any libraries if the library is marked as empty. This is all straightforward bazel dependency/output management. Adjust the cabal wrapper to fix up the package database even in the case that there is no library. Otherwise we end up with an empty package database, which isn't what we're looking for. --- haskell/cabal.bzl | 65 ++++++++++++++++++++------------ haskell/private/cabal_wrapper.py | 2 +- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/haskell/cabal.bzl b/haskell/cabal.bzl index 2f485b2ab..1e010d014 100644 --- a/haskell/cabal.bzl +++ b/haskell/cabal.bzl @@ -483,7 +483,7 @@ def _haskell_cabal_library_impl(ctx): "_install/{}_data".format(package_id), sibling = cabal, ) - with_haddock = ctx.attr.haddock and hs.tools_config.supports_haddock + with_haddock = ctx.attr.haddock and hs.tools_config.supports_haddock and not ctx.attr.empty_library if with_haddock: haddock_file = hs.actions.declare_file( "_install/{}_haddock/{}.haddock".format(package_id, package_name), @@ -496,30 +496,36 @@ def _haskell_cabal_library_impl(ctx): else: haddock_file = None haddock_html_dir = None - vanilla_library = hs.actions.declare_file( - "_install/lib/libHS{}.a".format(package_id), - sibling = cabal, - ) - if with_profiling: - profiling_library = hs.actions.declare_file( - "_install/lib/libHS{}_p.a".format(package_id), - sibling = cabal, - ) - static_library = profiling_library - else: + if ctx.attr.empty_library: + vanilla_library = None + static_library = None profiling_library = None - static_library = vanilla_library - if hs.toolchain.static_runtime: dynamic_library = None else: - dynamic_library = hs.actions.declare_file( - "_install/lib/libHS{}-ghc{}.{}".format( - package_id, - hs.toolchain.version, - _so_extension(hs), - ), + vanilla_library = hs.actions.declare_file( + "_install/lib/libHS{}.a".format(package_id), sibling = cabal, ) + if with_profiling: + profiling_library = hs.actions.declare_file( + "_install/lib/libHS{}_p.a".format(package_id), + sibling = cabal, + ) + static_library = profiling_library + else: + profiling_library = None + static_library = vanilla_library + if hs.toolchain.static_runtime: + dynamic_library = None + else: + dynamic_library = hs.actions.declare_file( + "_install/lib/libHS{}-ghc{}.{}".format( + package_id, + hs.toolchain.version, + _so_extension(hs), + ), + sibling = cabal, + ) (tool_inputs, tool_input_manifests) = ctx.resolve_tools(tools = ctx.attr.tools) c = _prepare_cabal_inputs( hs, @@ -553,11 +559,12 @@ def _haskell_cabal_library_impl(ctx): outputs = [ package_database, interfaces_dir, - vanilla_library, data_dir, ] if with_haddock: outputs.extend([haddock_file, haddock_html_dir]) + if vanilla_library != None: + outputs.append(vanilla_library) if dynamic_library != None: outputs.append(dynamic_library) if with_profiling: @@ -578,8 +585,13 @@ def _haskell_cabal_library_impl(ctx): progress_message = "HaskellCabalLibrary {}".format(hs.label), ) + if not ctx.attr.empty_library: + default_info_libs = depset([static_library] + ([dynamic_library] if dynamic_library != None else [])) + else: + default_info_libs = depset([package_database]) + default_info = DefaultInfo( - files = depset([static_library] + ([dynamic_library] if dynamic_library != None else [])), + files = default_info_libs, runfiles = ctx.runfiles( files = [data_dir], collect_default = True, @@ -628,7 +640,7 @@ def _haskell_cabal_library_impl(ctx): ) linker_input = cc_common.create_linker_input( owner = ctx.label, - libraries = depset(direct = [ + libraries = depset(direct = ([] if ctx.attr.empty_library else [ cc_common.create_library_to_link( actions = ctx.actions, feature_configuration = feature_configuration, @@ -638,7 +650,7 @@ def _haskell_cabal_library_impl(ctx): static_library = static_library, cc_toolchain = cc_toolchain, ), - ]), + ])), ) compilation_context = cc_common.create_compilation_context() linking_context = cc_common.create_linking_context( @@ -747,6 +759,11 @@ haskell_cabal_library = rule( library symlink underneath `_solib_` will be shortened to avoid exceeding the MACH-O header size limit on MacOS.""", ), + "empty_library": attr.bool( + default = False, + doc = """Whether the main cabal library is empty and merely re-exports from other sub libraries. + It is necessary to set this, otherwise bazel will complain about missing "*libHS.a" files.""", + ), }, toolchains = use_cc_toolchain() + [ "@rules_haskell//haskell:toolchain", diff --git a/haskell/private/cabal_wrapper.py b/haskell/private/cabal_wrapper.py index 09e34efef..94261d68e 100755 --- a/haskell/private/cabal_wrapper.py +++ b/haskell/private/cabal_wrapper.py @@ -392,7 +392,7 @@ def make_relative_to_pkgroot(matchobj): line = re.sub(re.escape(cfg_execroot) + r'\S*', make_relative_to_pkgroot, line) return line -if libraries != [] and os.path.isfile(package_conf_file): +if os.path.isfile(package_conf_file): for lib in libraries: os.rename(lib, os.path.join(dynlibdir, os.path.basename(lib)))