Skip to content

Commit cd7f81a

Browse files
committed
cabal: add empty_lib to stack snapshot components
Empty haskell libraries have been a longstanding problem. Add support for an additional component type `empty_lib`, which is like `lib`, except that an additional `haskell_cabal_args` target will be created for the library, instructing Bazel not to look for any object files. Remove the longstanding package blacklist for empty packages. Replace it with the now possible correct entries for those libraries, indicating that the main library is empty.
1 parent 61d098d commit cd7f81a

File tree

1 file changed

+88
-37
lines changed

1 file changed

+88
-37
lines changed

haskell/cabal.bzl

Lines changed: 88 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,6 @@ main = defaultMain
126126

127127
_CABAL_TOOLS = ["alex", "c2hs", "cpphs", "doctest", "happy"]
128128

129-
# Some old packages are empty compatibility shims. Empty packages
130-
# cause Cabal to not produce the outputs it normally produces. Instead
131-
# of detecting that, we blacklist the offending packages, on the
132-
# assumption that such packages are old and rare.
133-
#
134-
# TODO: replace this with a more general solution.
135-
_EMPTY_PACKAGES_BLACKLIST = [
136-
"bytestring-builder",
137-
"fail",
138-
"ghc-byteorder",
139-
"haskell-gi-overloading",
140-
"mtl-compat",
141-
"nats",
142-
]
143-
144129
def _cabal_tool_flag(tool):
145130
"""Return a --with-PROG=PATH flag if input is a recognized Cabal tool. None otherwise."""
146131
if tool.basename in _CABAL_TOOLS:
@@ -1133,8 +1118,10 @@ def _parse_components(package, components, spec):
11331118
exe: list of string, List of executables.
11341119
"""
11351120
lib = False
1121+
empty_lib = False
11361122
exe = []
11371123
sublibs = []
1124+
empty_sublibs = []
11381125

11391126
for component in components:
11401127
if component == "lib":
@@ -1144,6 +1131,15 @@ def _parse_components(package, components, spec):
11441131
lib = True
11451132
else:
11461133
sublibs.append(component[4:])
1134+
elif component == "empty_lib":
1135+
lib = True
1136+
empty_lib = True
1137+
elif component.startswith("empty_lib:"):
1138+
if component == "empty_lib:%s" % package:
1139+
lib = True
1140+
empty_lib = True
1141+
else:
1142+
empty_sublibs.append(component[4:])
11471143

11481144
elif component == "exe":
11491145
exe.append(package)
@@ -1158,14 +1154,21 @@ def _parse_components(package, components, spec):
11581154
if not lib or exe != []:
11591155
fail("Invalid core package components: %s" % package, "components")
11601156

1161-
return struct(lib = lib, exe = exe, sublibs = sublibs)
1157+
return struct(lib = lib, empty_lib = empty_lib, exe = exe, sublibs = sublibs, empty_sublibs = empty_sublibs)
11621158

11631159
_default_components = {
1164-
"alex": struct(lib = False, exe = ["alex"], sublibs = []),
1165-
"c2hs": struct(lib = False, exe = ["c2hs"], sublibs = []),
1166-
"cpphs": struct(lib = True, exe = ["cpphs"], sublibs = []),
1167-
"doctest": struct(lib = True, exe = ["doctest"], sublibs = []),
1168-
"happy": struct(lib = False, exe = ["happy"], sublibs = []),
1160+
"alex": struct(lib = False, empty_lib = False, exe = ["alex"], sublibs = [], empty_sublibs = []),
1161+
"c2hs": struct(lib = False, empty_lib = False, exe = ["c2hs"], sublibs = [], empty_sublibs = []),
1162+
"cpphs": struct(lib = True, empty_lib = False, exe = ["cpphs"], sublibs = [], empty_sublibs = []),
1163+
"doctest": struct(lib = True, empty_lib = False, exe = ["doctest"], sublibs = [], empty_sublibs = []),
1164+
"happy": struct(lib = False, empty_lib = False, exe = ["happy"], sublibs = [], empty_sublibs = []),
1165+
# Below are compatibility libraries that produce an empty cabal library.
1166+
"bytestring-builder": struct(lib = True, empty_lib = True, exe = [], sublibs = [], empty_sublibs = []),
1167+
"fail": struct(lib = True, empty_lib = True, exe = [], sublibs = [], empty_sublibs = []),
1168+
"ghc-byteorder": struct(lib = True, empty_lib = True, exe = [], sublibs = [], empty_sublibs = []),
1169+
"haskell-gi-overloading": struct(lib = True, empty_lib = True, exe = [], sublibs = [], empty_sublibs = []),
1170+
"mtl-compat": struct(lib = True, empty_lib = True, exe = [], sublibs = [], empty_sublibs = []),
1171+
"nats": struct(lib = True, empty_lib = True, exe = [], sublibs = [], empty_sublibs = []),
11691172
}
11701173

11711174
def _get_components(components, package):
@@ -1175,7 +1178,7 @@ def _get_components(components, package):
11751178
will be taken from the `_default_components`. If it is not listed
11761179
there then it will default to a library and no executable components.
11771180
"""
1178-
return components.get(package, _default_components.get(package, struct(lib = True, exe = [], sublibs = [])))
1181+
return components.get(package, _default_components.get(package, struct(lib = True, empty_lib = False, exe = [], sublibs = [], empty_sublibs = [])))
11791182

11801183
def _parse_json_field(json, field, ty, errmsg):
11811184
"""Read and type-check a field from a JSON object.
@@ -2054,7 +2057,7 @@ packages = {
20542057
# Write out the dependency graph as a BUILD file.
20552058
build_file_builder = []
20562059
build_file_builder.append("""
2057-
load("@rules_haskell//haskell:cabal.bzl", "haskell_cabal_binary", "haskell_cabal_library")
2060+
load("@rules_haskell//haskell:cabal.bzl", "haskell_cabal_args", "haskell_cabal_binary", "haskell_cabal_library")
20582061
load("@rules_haskell//haskell:defs.bzl", "haskell_library", "haskell_toolchain_library")
20592062
""")
20602063
for (name, spec) in resolved.items():
@@ -2075,20 +2078,6 @@ alias(name = "{name}", actual = "{actual}", visibility = {visibility})
20752078
haskell_toolchain_library(name = "{name}", visibility = {visibility})
20762079
""".format(name = name, visibility = visibility),
20772080
)
2078-
elif name in _EMPTY_PACKAGES_BLACKLIST:
2079-
build_file_builder.append(
2080-
"""
2081-
haskell_library(
2082-
name = "{name}",
2083-
version = "{version}",
2084-
visibility = {visibility},
2085-
)
2086-
""".format(
2087-
name = name,
2088-
version = version,
2089-
visibility = visibility,
2090-
),
2091-
)
20922081
else:
20932082
library_deps = [
20942083
dep
@@ -2119,6 +2108,26 @@ haskell_library(
21192108
)).relative(label))
21202109
for label in repository_ctx.attr.setup_deps.get(name, [])
21212110
]
2111+
2112+
wrote_cabal_args = False
2113+
cabal_args = "cabal_args = \"_{name}_cabal_args\",".format(name = name)
2114+
def add_cabal_args(written):
2115+
if not written:
2116+
build_file_builder.append(
2117+
"""
2118+
haskell_cabal_args(
2119+
name = "_{name}_cabal_args",
2120+
is_empty = True,
2121+
)
2122+
""".format(name = name)
2123+
)
2124+
return True
2125+
2126+
lib_cabal_args = ""
2127+
if all_components[name].empty_lib:
2128+
wrote_cabal_args = add_cabal_args(wrote_cabal_args)
2129+
lib_cabal_args = cabal_args
2130+
21222131
if all_components[name].lib:
21232132
build_file_builder.append(
21242133
"""
@@ -2134,6 +2143,7 @@ haskell_cabal_library(
21342143
visibility = {visibility},
21352144
cabalopts = ["--ghc-option=-w", "--ghc-option=-optF=-w"],
21362145
verbose = {verbose},
2146+
{lib_cabal_args}
21372147
unique_name = True,
21382148
)
21392149
""".format(
@@ -2147,6 +2157,7 @@ haskell_cabal_library(
21472157
tools = library_tools,
21482158
visibility = visibility,
21492159
verbose = repr(repository_ctx.attr.verbose),
2160+
lib_cabal_args = lib_cabal_args
21502161
),
21512162
)
21522163
build_file_builder.append(
@@ -2224,6 +2235,46 @@ haskell_cabal_library(
22242235
visibility = visibility,
22252236
),
22262237
)
2238+
for sublib in all_components[name].empty_sublibs:
2239+
sublib_component_deps = [
2240+
_resolve_component_target_name(name, c)
2241+
for c in package_components_dependencies.get("lib:{}".format(sublib), [])
2242+
]
2243+
wrote_cabal_args = add_cabal_args(wrote_cabal_args)
2244+
build_file_builder.append(
2245+
"""
2246+
haskell_cabal_library(
2247+
name = "_{name}_lib_{sublib}",
2248+
package_name = "{name}",
2249+
version = "{version}",
2250+
haddock = {haddock},
2251+
sublibrary_name = "{sublib}",
2252+
flags = {flags},
2253+
srcs = glob(["{dir}/**"]),
2254+
deps = {deps},
2255+
setup_deps = {setup_deps},
2256+
tools = {tools},
2257+
visibility = {visibility},
2258+
cabalopts = ["--ghc-option=-w", "--ghc-option=-optF=-w"],
2259+
verbose = {verbose},
2260+
{cabal_args},
2261+
)
2262+
""".format(
2263+
name = name,
2264+
version = version,
2265+
haddock = repr(repository_ctx.attr.haddock),
2266+
sublib = sublib,
2267+
flags = repository_ctx.attr.flags.get(name, []),
2268+
dir = package,
2269+
deps = library_deps + sublib_component_deps,
2270+
setup_deps = setup_deps,
2271+
tools = library_tools,
2272+
verbose = repr(repository_ctx.attr.verbose),
2273+
visibility = visibility,
2274+
cabal_args = cabal_args,
2275+
),
2276+
)
2277+
22272278
build_file_content = "\n".join(build_file_builder)
22282279
repository_ctx.file("BUILD.bazel", build_file_content, executable = False)
22292280

0 commit comments

Comments
 (0)