Skip to content

Commit

Permalink
Merge pull request #487 from tweag/mostly-static-by-default
Browse files Browse the repository at this point in the history
Support linkstatic attribute in libraries
  • Loading branch information
mboes authored Nov 22, 2018
2 parents df304be + c2e8bc9 commit 9e363eb
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 37 deletions.
8 changes: 6 additions & 2 deletions haskell/haskell.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.""",
Expand Down
18 changes: 10 additions & 8 deletions haskell/private/actions/compile.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down Expand Up @@ -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:
Expand All @@ -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,
Expand Down Expand Up @@ -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:
Expand All @@ -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,
Expand Down
47 changes: 35 additions & 12 deletions haskell/private/actions/link.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,27 @@ 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],
command = """
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,
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 = [
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 10 additions & 2 deletions haskell/private/actions/package.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
45 changes: 33 additions & 12 deletions haskell/private/haskell_impl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
)

Expand Down Expand Up @@ -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)
Expand All @@ -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,
)
Expand All @@ -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,
)
Expand All @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions haskell/protobuf.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ haskell_binary(
srcs = ["Main.hs"],
visibility = ["//visibility:public"],
deps = ["@hackage//:base"],
linkstatic = True,
linkstatic = False,
)
File renamed without changes.
17 changes: 17 additions & 0 deletions tests/library-static-only/BUILD
Original file line number Diff line number Diff line change
@@ -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,
)
5 changes: 5 additions & 0 deletions tests/library-static-only/Lib.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Lib (message) where

import qualified Data.ByteString.Char8 as B

message = B.pack "hello, world"
3 changes: 3 additions & 0 deletions tests/library-static-only/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Main where

main = putStrLn "hello world"

0 comments on commit 9e363eb

Please sign in to comment.