Skip to content

Commit 50e37a9

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 50e37a9

File tree

1 file changed

+52
-29
lines changed

1 file changed

+52
-29
lines changed

haskell/cabal.bzl

Lines changed: 52 additions & 29 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:
@@ -1166,6 +1151,16 @@ _default_components = {
11661151
"cpphs": struct(lib = True, exe = ["cpphs"], sublibs = []),
11671152
"doctest": struct(lib = True, exe = ["doctest"], sublibs = []),
11681153
"happy": struct(lib = False, exe = ["happy"], sublibs = []),
1154+
# Below are compatibility libraries that produce an empty cabal library.
1155+
}
1156+
1157+
_default_components_args = {
1158+
"bytestring-builder:lib:bytestring-builder": "@rules_haskell//tools/cabal_args:empty_library",
1159+
"fail:lib:fail": "@rules_haskell//tools/cabal_args:empty_library",
1160+
"ghc-byteorder:lib:ghc-byteorder": "@rules_haskell//tools/cabal_args:empty_library",
1161+
"haskell-gi-overloading:lib:haskell-gi-overloading": "@rules_haskell//tools/cabal_args:empty_library",
1162+
"mtl-compat:lib:mtl-compat": "@rules_haskell//tools/cabal_args:empty_library",
1163+
"nats:lib:nats": "@rules_haskell//tools/cabal_args:empty_library",
11691164
}
11701165

11711166
def _get_components(components, package):
@@ -1177,6 +1172,9 @@ def _get_components(components, package):
11771172
"""
11781173
return components.get(package, _default_components.get(package, struct(lib = True, exe = [], sublibs = [])))
11791174

1175+
def _get_components_args(components_args, component):
1176+
return components_args.get(component, _default_components_args.get(component, None))
1177+
11801178
def _parse_json_field(json, field, ty, errmsg):
11811179
"""Read and type-check a field from a JSON object.
11821180
@@ -1202,6 +1200,17 @@ def _parse_json_field(json, field, ty, errmsg):
12021200
)))
12031201
return json[field]
12041202

1203+
def _parse_components_args_key(component):
1204+
pieces = component.split(':')
1205+
if len(pieces) == 1:
1206+
component = '{}:lib:{}'.format(component, component)
1207+
elif len(pieces) == 2 or (len(pieces) == 3 and pieces[2] == ''):
1208+
if pieces[1] == 'lib' or pieces[1] == 'exe':
1209+
component = '{}:{}:{}'.format(pieces[0], pieces[1], pieces[0])
1210+
else:
1211+
component = '{}:lib:{}'.format(pieces[0], pieces[1])
1212+
return component
1213+
12051214
def _parse_package_spec(package_spec, enable_custom_toolchain_libraries, custom_toolchain_libraries):
12061215
"""Parse a package description from `stack ls dependencies json`.
12071216
@@ -1997,6 +2006,10 @@ def _stack_snapshot_impl(repository_ctx):
19972006
for (name, components) in repository_ctx.attr.components.items()
19982007
}
19992008
all_components = {}
2009+
user_components_args = {
2010+
_parse_components_args_key(component): args
2011+
for (component, args) in repository_ctx.attr.components_args.items()
2012+
}
20002013
for (name, spec) in resolved.items():
20012014
all_components[name] = _get_components(user_components, name)
20022015
user_components.pop(name, None)
@@ -2075,20 +2088,6 @@ alias(name = "{name}", actual = "{actual}", visibility = {visibility})
20752088
haskell_toolchain_library(name = "{name}", visibility = {visibility})
20762089
""".format(name = name, visibility = visibility),
20772090
)
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-
)
20922091
else:
20932092
library_deps = [
20942093
dep
@@ -2119,6 +2118,12 @@ haskell_library(
21192118
)).relative(label))
21202119
for label in repository_ctx.attr.setup_deps.get(name, [])
21212120
]
2121+
2122+
lib_args = _get_components_args(user_components_args, '{}:lib:{}'.format(name, name))
2123+
cabal_args = ""
2124+
if lib_args != None:
2125+
cabal_args = "cabal_args = \"{}\",".format(lib_args)
2126+
21222127
if all_components[name].lib:
21232128
build_file_builder.append(
21242129
"""
@@ -2134,6 +2139,7 @@ haskell_cabal_library(
21342139
visibility = {visibility},
21352140
cabalopts = ["--ghc-option=-w", "--ghc-option=-optF=-w"],
21362141
verbose = {verbose},
2142+
{cabal_args}
21372143
unique_name = True,
21382144
)
21392145
""".format(
@@ -2147,6 +2153,7 @@ haskell_cabal_library(
21472153
tools = library_tools,
21482154
visibility = visibility,
21492155
verbose = repr(repository_ctx.attr.verbose),
2156+
cabal_args = cabal_args
21502157
),
21512158
)
21522159
build_file_builder.append(
@@ -2162,6 +2169,10 @@ haskell_cabal_library(
21622169
for comp in ["exe:{}".format(exe)] + (["exe"] if exe == name else [])
21632170
for comp_dep in package_components_dependencies.get(comp, [])
21642171
]
2172+
exe_args = _get_components_args(user_components_args, '{}:exe:{}'.format(name, exe))
2173+
cabal_args = ""
2174+
if exe_args != None:
2175+
cabal_args = "cabal_args = \"{}\",".format(lib_args)
21652176
build_file_builder.append(
21662177
"""
21672178
haskell_cabal_binary(
@@ -2174,6 +2185,7 @@ haskell_cabal_binary(
21742185
tools = {tools},
21752186
visibility = ["@{workspace}-exe//{name}:__pkg__"],
21762187
cabalopts = ["--ghc-option=-w", "--ghc-option=-optF=-w", "--ghc-option=-static"],
2188+
{cabal_args}
21772189
verbose = {verbose},
21782190
)
21792191
""".format(
@@ -2185,6 +2197,7 @@ haskell_cabal_binary(
21852197
deps = library_deps + exe_component_deps + ([name] if all_components[name].lib else []),
21862198
setup_deps = setup_deps,
21872199
tools = library_tools,
2200+
cabal_args = cabal_args,
21882201
verbose = repr(repository_ctx.attr.verbose),
21892202
),
21902203
)
@@ -2193,6 +2206,10 @@ haskell_cabal_binary(
21932206
_resolve_component_target_name(name, c)
21942207
for c in package_components_dependencies.get("lib:{}".format(sublib), [])
21952208
]
2209+
lib_args = _get_components_args(user_components_args, '{}:lib:{}'.format(name, sublib))
2210+
cabal_args = ""
2211+
if lib_args != None:
2212+
cabal_args = "cabal_args = \"{}\",".format(lib_args)
21962213
build_file_builder.append(
21972214
"""
21982215
haskell_cabal_library(
@@ -2208,6 +2225,7 @@ haskell_cabal_library(
22082225
tools = {tools},
22092226
visibility = {visibility},
22102227
cabalopts = ["--ghc-option=-w", "--ghc-option=-optF=-w"],
2228+
{cabal_args}
22112229
verbose = {verbose},
22122230
)
22132231
""".format(
@@ -2222,8 +2240,10 @@ haskell_cabal_library(
22222240
tools = library_tools,
22232241
verbose = repr(repository_ctx.attr.verbose),
22242242
visibility = visibility,
2243+
cabal_args = cabal_args,
22252244
),
22262245
)
2246+
22272247
build_file_content = "\n".join(build_file_builder)
22282248
repository_ctx.file("BUILD.bazel", build_file_content, executable = False)
22292249

@@ -2273,6 +2293,7 @@ _stack_snapshot = repository_rule(
22732293
"verbose": attr.bool(default = False),
22742294
"custom_toolchain_libraries": attr.string_list(default = []),
22752295
"enable_custom_toolchain_libraries": attr.bool(default = False),
2296+
"components_args": attr.string_dict(),
22762297
},
22772298
)
22782299

@@ -2484,6 +2505,7 @@ def stack_snapshot(
24842505
netrc = "",
24852506
toolchain_libraries = None,
24862507
setup_stack = True,
2508+
components_args = {},
24872509
label_builder = lambda l: Label(l),
24882510
**kwargs):
24892511
"""Use Stack to download and extract Cabal source distributions.
@@ -2743,6 +2765,7 @@ def stack_snapshot(
27432765
tools = tools,
27442766
components = components,
27452767
components_dependencies = components_dependencies,
2768+
components_args = components_args,
27462769
verbose = verbose,
27472770
custom_toolchain_libraries = toolchain_libraries,
27482771
enable_custom_toolchain_libraries = toolchain_libraries != None,

0 commit comments

Comments
 (0)