diff --git a/haskell/haskell.bzl b/haskell/haskell.bzl index 0fff9080a..16aa099f5 100644 --- a/haskell/haskell.bzl +++ b/haskell/haskell.bzl @@ -110,8 +110,8 @@ def _mk_binary_rule(**kwargs): attrs = dict( _haskell_common_attrs, linkstatic = attr.bool( - default = False, - doc = "If enabled, this option tells GHC to link statically whenever possible. While this captures all Haskell code built, some system libraries may still be linked dynamically, as are libraries for which there is no static library. So the resulting executable will still be dynamically linked, hence only mostly static.", + default = True, + doc = "Link dependencies statically wherever possible. Some system libraries may still be linked dynamically, as are libraries for which there is no static library. So the resulting executable will still be dynamically linked, hence only mostly static.", ), generate_so = attr.bool( default = False, @@ -185,6 +185,10 @@ haskell_library = rule( exports = attr.label_keyed_string_dict( doc = "A dictionary mapping dependencies to module reexports that should be available for import by dependencies.", ), + linkstatic = attr.bool( + default = False, + doc = "Create a static library, not both a static and a shared library.", + ), version = attr.string( doc = """Library version. Not normally necessary unless to build a library originally defined as a Cabal package. If this is specified, CPP version macro will be generated.""", diff --git a/haskell/private/actions/compile.bzl b/haskell/private/actions/compile.bzl index 17faa1d14..59a80d956 100644 --- a/haskell/private/actions/compile.bzl +++ b/haskell/private/actions/compile.bzl @@ -191,15 +191,9 @@ def _compilation_defaults(hs, cc, java, dep_info, srcs, import_dir_map, extra_sr args.add("-O2") args.add(ghc_args) - args.add(["-static"]) - - # NOTE We can't have profiling and dynamic code at the same time, see: - # https://ghc.haskell.org/trac/ghc/ticket/15394 if with_profiling: args.add("-prof", "-fexternal-interpreter") - else: - args.add(["-dynamic-too"]) # Common flags args.add([ @@ -270,7 +264,7 @@ def _compilation_defaults(hs, cc, java, dep_info, srcs, import_dir_map, extra_sr ), ) -def compile_binary(hs, cc, java, dep_info, srcs, ls_modules, import_dir_map, extra_srcs, compiler_flags, with_profiling, main_function, version): +def compile_binary(hs, cc, java, dep_info, srcs, ls_modules, import_dir_map, extra_srcs, compiler_flags, dynamic, with_profiling, main_function, version): """Compile a Haskell target into object files suitable for linking. Returns: @@ -282,6 +276,12 @@ def compile_binary(hs, cc, java, dep_info, srcs, ls_modules, import_dir_map, ext """ c = _compilation_defaults(hs, cc, java, dep_info, srcs, import_dir_map, extra_srcs, compiler_flags, with_profiling, my_pkg_id = None, version = version) c.args.add(["-main-is", main_function]) + if dynamic: + # For binaries, GHC creates .o files even for code to be + # linked dynamically. So we have to force the object suffix to + # be consistent with the dynamic object suffix in the library + # case. + c.args.add(["-dynamic", "-osuf dyn_o"]) hs.toolchain.actions.run_ghc( hs, @@ -322,7 +322,7 @@ def compile_binary(hs, cc, java, dep_info, srcs, ls_modules, import_dir_map, ext exposed_modules_file = exposed_modules_file, ) -def compile_library(hs, cc, java, dep_info, srcs, ls_modules, other_modules, exposed_modules_reexports, import_dir_map, extra_srcs, compiler_flags, with_profiling, my_pkg_id): +def compile_library(hs, cc, java, dep_info, srcs, ls_modules, other_modules, exposed_modules_reexports, import_dir_map, extra_srcs, compiler_flags, with_shared, with_profiling, my_pkg_id): """Build arguments for Haskell package build. Returns: @@ -337,6 +337,8 @@ def compile_library(hs, cc, java, dep_info, srcs, ls_modules, other_modules, exp import_dirs: import directories that should make all modules visible (for GHCi) """ c = _compilation_defaults(hs, cc, java, dep_info, srcs, import_dir_map, extra_srcs, compiler_flags, with_profiling, my_pkg_id = my_pkg_id, version = my_pkg_id.version) + if with_shared: + c.args.add(["-dynamic-too"]) hs.toolchain.actions.run_ghc( hs, diff --git a/haskell/private/actions/link.bzl b/haskell/private/actions/link.bzl index cbf73871e..70e30c794 100644 --- a/haskell/private/actions/link.bzl +++ b/haskell/private/actions/link.bzl @@ -55,12 +55,19 @@ def _fix_linker_paths(hs, inp, out, external_libraries): ), ) -def _create_objects_dir_manifest(hs, objects_dir, with_profiling): +def _create_objects_dir_manifest(hs, objects_dir, dynamic, with_profiling): + suffix = ".dynamic.manifest" if dynamic else ".static.manifest" objects_dir_manifest = hs.actions.declare_file( - objects_dir.basename + ".manifest", + objects_dir.basename + suffix, sibling = objects_dir, ) + if with_profiling: + ext = "p_o" + elif dynamic: + ext = "dyn_o" + else: + ext = "o" hs.actions.run_shell( inputs = [objects_dir], outputs = [objects_dir_manifest], @@ -68,7 +75,7 @@ def _create_objects_dir_manifest(hs, objects_dir, with_profiling): find {dir} -name '*.{ext}' > {out} """.format( dir = objects_dir.path, - ext = "p_o" if with_profiling else "dyn_o", + ext = ext, out = objects_dir_manifest.path, ), use_default_shell_env = True, @@ -83,7 +90,7 @@ def link_binary( extra_srcs, compiler_flags, objects_dir, - linkstatic, + dynamic, with_profiling, version): """Link Haskell binary from static object files. @@ -118,10 +125,10 @@ def link_binary( # sure that GHC dynamically links Haskell code too. The one exception to # this is when we are compiling for profiling, which currently does not play # nicely with dynamic linking. - if not linkstatic: - if hs.toolchain.is_darwin: - args.add(["-optl-Wl,-headerpad_max_install_names"]) - elif not with_profiling: + if dynamic: + if with_profiling: + print("WARNING: dynamic linking and profiling don't mix. Omitting -dynamic.\nSee https://ghc.haskell.org/trac/ghc/ticket/15394") + else: args.add(["-pie", "-dynamic"]) # When compiling with `-threaded`, GHC needs to link against @@ -156,6 +163,8 @@ def link_binary( ) if hs.toolchain.is_darwin: + args.add(["-optl-Wl,-headerpad_max_install_names"]) + # Suppress a warning that Clang prints due to GHC automatically passing # "-pie" or "-no-pie" to the C compiler. # This particular invocation of GHC is a little unusual; e.g., we're @@ -177,7 +186,12 @@ def link_binary( for rpath in set.to_list(_infer_rpaths(executable, solibs)): args.add(["-optl-Wl,-rpath," + rpath]) - objects_dir_manifest = _create_objects_dir_manifest(hs, objects_dir, with_profiling) + objects_dir_manifest = _create_objects_dir_manifest( + hs, + objects_dir, + dynamic = dynamic, + with_profiling = with_profiling, + ) hs.toolchain.actions.run_ghc( hs, inputs = depset(transitive = [ @@ -259,7 +273,12 @@ def link_library_static(hs, cc, dep_info, objects_dir, my_pkg_id, with_profiling static_library = hs.actions.declare_file( "lib{0}.a".format(pkg_id.library_name(hs, my_pkg_id, prof_suffix = with_profiling)), ) - objects_dir_manifest = _create_objects_dir_manifest(hs, objects_dir, with_profiling) + objects_dir_manifest = _create_objects_dir_manifest( + hs, + objects_dir, + dynamic = False, + with_profiling = with_profiling, + ) args = hs.actions.args() inputs = ([objects_dir, objects_dir_manifest, hs.tools.ar] + hs.tools_runfiles.ar + hs.extra_binaries) @@ -317,7 +336,6 @@ def link_library_dynamic(hs, cc, dep_info, extra_srcs, objects_dir, my_pkg_id): args = hs.actions.args() args.add(["-optl" + f for f in cc.linker_flags]) - args.add(["-shared", "-dynamic"]) # Work around macOS linker limits. This fix has landed in GHC HEAD, but is @@ -359,7 +377,12 @@ def link_library_dynamic(hs, cc, dep_info, extra_srcs, objects_dir, my_pkg_id): args.add(["-o", dynamic_library_tmp.path]) # Profiling not supported for dynamic libraries. - objects_dir_manifest = _create_objects_dir_manifest(hs, objects_dir, False) + objects_dir_manifest = _create_objects_dir_manifest( + hs, + objects_dir, + dynamic = True, + with_profiling = False, + ) hs.toolchain.actions.run_ghc( hs, diff --git a/haskell/private/actions/package.bzl b/haskell/private/actions/package.bzl index 94d03fbe2..a9a5685ab 100644 --- a/haskell/private/actions/package.bzl +++ b/haskell/private/actions/package.bzl @@ -94,8 +94,16 @@ def package(hs, dep_info, interfaces_dir, interfaces_dir_prof, static_library, d set.to_depset(dep_info.package_confs), set.to_depset(dep_info.package_caches), depset(interfaces_dirs), - depset([static_library, conf_file, dynamic_library] + - ([static_library_prof] if static_library_prof != None else [])), + depset([ + input + for input in [ + static_library, + conf_file, + dynamic_library, + static_library_prof, + ] + if input + ]), ]), outputs = [cache_file], env = { diff --git a/haskell/private/haskell_impl.bzl b/haskell/private/haskell_impl.bzl index 70a89c108..c5073229d 100644 --- a/haskell/private/haskell_impl.bzl +++ b/haskell/private/haskell_impl.bzl @@ -65,9 +65,10 @@ use the 'haskell_import' rule instead. # Add any interop info for other languages. cc = cc_interop_info(ctx) java = java_interop_info(ctx) - with_profiling = is_profiling_enabled(hs) + with_profiling = is_profiling_enabled(hs) srcs_files, import_dir_map = _prepare_srcs(ctx.attr.srcs) + compiler_flags = ctx.attr.compiler_flags c = hs.toolchain.actions.compile_binary( hs, @@ -79,6 +80,7 @@ use the 'haskell_import' rule instead. import_dir_map = import_dir_map, extra_srcs = depset(ctx.files.extra_srcs), compiler_flags = ctx.attr.compiler_flags, + dynamic = not ctx.attr.linkstatic, with_profiling = False, main_function = ctx.attr.main_function, version = ctx.attr.version, @@ -103,6 +105,10 @@ use the 'haskell_import' rule instead. depset([c.objects_dir]), ]), compiler_flags = ctx.attr.compiler_flags, + # NOTE We can't have profiling and dynamic code at the + # same time, see: + # https://ghc.haskell.org/trac/ghc/ticket/15394 + dynamic = False, with_profiling = True, main_function = ctx.attr.main_function, version = ctx.attr.version, @@ -115,8 +121,8 @@ use the 'haskell_import' rule instead. ctx.files.extra_srcs, ctx.attr.compiler_flags, c_p.objects_dir if with_profiling else c.objects_dir, - ctx.attr.linkstatic, - with_profiling, + dynamic = not ctx.attr.linkstatic, + with_profiling = with_profiling, version = ctx.attr.version, ) @@ -177,6 +183,7 @@ use the 'haskell_import' rule instead. version = ctx.attr.version if ctx.attr.version else None my_pkg_id = pkg_id.new(ctx.label, version) with_profiling = is_profiling_enabled(hs) + with_shared = not ctx.attr.linkstatic # Add any interop info for other languages. cc = cc_interop_info(ctx) @@ -198,6 +205,7 @@ use the 'haskell_import' rule instead. import_dir_map = import_dir_map, extra_srcs = depset(ctx.files.extra_srcs), compiler_flags = ctx.attr.compiler_flags, + with_shared = with_shared, with_profiling = False, my_pkg_id = my_pkg_id, ) @@ -223,6 +231,10 @@ use the 'haskell_import' rule instead. depset([c.objects_dir]), ]), compiler_flags = ctx.attr.compiler_flags, + # NOTE We can't have profiling and dynamic code at the + # same time, see: + # https://ghc.haskell.org/trac/ghc/ticket/15394 + with_shared = False, with_profiling = True, my_pkg_id = my_pkg_id, ) @@ -235,14 +247,23 @@ use the 'haskell_import' rule instead. my_pkg_id, with_profiling = False, ) - dynamic_library = link_library_dynamic( - hs, - cc, - dep_info, - depset(ctx.files.extra_srcs), - c.objects_dir, - my_pkg_id, - ) + + if with_shared: + dynamic_library = link_library_dynamic( + hs, + cc, + dep_info, + depset(ctx.files.extra_srcs), + c.objects_dir, + my_pkg_id, + ) + dynamic_libraries = set.insert( + dep_info.dynamic_libraries, + dynamic_library, + ) + else: + dynamic_library = None + dynamic_libraries = dep_info.dynamic_libraries static_library_prof = None if with_profiling: @@ -294,7 +315,7 @@ use the 'haskell_import' rule instead. # then you feed the library which resolves the symbols. static_libraries = [static_library] + dep_info.static_libraries, static_libraries_prof = static_libraries_prof, - dynamic_libraries = set.insert(dep_info.dynamic_libraries, dynamic_library), + dynamic_libraries = dynamic_libraries, interface_dirs = interface_dirs, prebuilt_dependencies = dep_info.prebuilt_dependencies, external_libraries = dep_info.external_libraries, diff --git a/haskell/protobuf.bzl b/haskell/protobuf.bzl index d1380f2ca..0fdf5c676 100644 --- a/haskell/protobuf.bzl +++ b/haskell/protobuf.bzl @@ -137,6 +137,7 @@ def _haskell_proto_aspect_impl(target, ctx): "repl_interpreted": True, "repl_ghci_args": [], "version": "", + "linkstatic": False, "_ghci_script": ctx.attr._ghci_script, "_ghci_repl_wrapper": ctx.attr._ghci_repl_wrapper, "hidden_modules": [], diff --git a/tests/binary-mostly-static/BUILD b/tests/binary-dynamic/BUILD similarity index 91% rename from tests/binary-mostly-static/BUILD rename to tests/binary-dynamic/BUILD index 3ed069d3e..1dbaae318 100644 --- a/tests/binary-mostly-static/BUILD +++ b/tests/binary-dynamic/BUILD @@ -10,5 +10,5 @@ haskell_binary( srcs = ["Main.hs"], visibility = ["//visibility:public"], deps = ["@hackage//:base"], - linkstatic = True, + linkstatic = False, ) diff --git a/tests/binary-mostly-static/Main.hs b/tests/binary-dynamic/Main.hs similarity index 100% rename from tests/binary-mostly-static/Main.hs rename to tests/binary-dynamic/Main.hs diff --git a/tests/library-static-only/BUILD b/tests/library-static-only/BUILD new file mode 100644 index 000000000..830f8358b --- /dev/null +++ b/tests/library-static-only/BUILD @@ -0,0 +1,17 @@ +package(default_testonly = 1) + +load( + "@io_tweag_rules_haskell//haskell:haskell.bzl", + "haskell_library", +) + +haskell_library( + name = "library-static-only", + srcs = ["Lib.hs"], + visibility = ["//visibility:public"], + deps = [ + "@hackage//:base", + "@hackage//:bytestring", + ], + linkstatic = True, +) diff --git a/tests/library-static-only/Lib.hs b/tests/library-static-only/Lib.hs new file mode 100644 index 000000000..66a2161a1 --- /dev/null +++ b/tests/library-static-only/Lib.hs @@ -0,0 +1,5 @@ +module Lib (message) where + +import qualified Data.ByteString.Char8 as B + +message = B.pack "hello, world" diff --git a/tests/library-static-only/Main.hs b/tests/library-static-only/Main.hs new file mode 100644 index 000000000..2048dbdec --- /dev/null +++ b/tests/library-static-only/Main.hs @@ -0,0 +1,3 @@ +module Main where + +main = putStrLn "hello world"