312 lines
9.6 KiB
Python
312 lines
9.6 KiB
Python
"""Haddock support"""
|
|
|
|
load("@bazel_skylib//lib:paths.bzl", "paths")
|
|
load(
|
|
"@io_tweag_rules_haskell//haskell:providers.bzl",
|
|
"HaddockInfo",
|
|
"HaskellInfo",
|
|
"HaskellLibraryInfo",
|
|
)
|
|
load(":private/context.bzl", "haskell_context", "render_env")
|
|
load(":private/set.bzl", "set")
|
|
|
|
def _get_haddock_path(package_id):
|
|
"""Get path to Haddock file of a package given its id.
|
|
|
|
Args:
|
|
package_id: string, package id.
|
|
|
|
Returns:
|
|
string: relative path to haddock file.
|
|
"""
|
|
return package_id + ".haddock"
|
|
|
|
def _haskell_doc_aspect_impl(target, ctx):
|
|
if HaskellInfo not in target or HaskellLibraryInfo not in target:
|
|
return []
|
|
|
|
# Packages imported via `//haskell:import.bzl%haskell_import` already
|
|
# contain an `HaddockInfo` provider, so we just forward it
|
|
if HaddockInfo in target:
|
|
return []
|
|
|
|
hs = haskell_context(ctx, ctx.rule.attr)
|
|
|
|
package_id = target[HaskellLibraryInfo].package_id
|
|
html_dir_raw = "doc-{0}".format(package_id)
|
|
html_dir = ctx.actions.declare_directory(html_dir_raw)
|
|
haddock_file = ctx.actions.declare_file(_get_haddock_path(package_id))
|
|
|
|
# XXX Haddock really wants a version number, so invent one from
|
|
# thin air. See https://github.com/haskell/haddock/issues/898.
|
|
if target[HaskellLibraryInfo].version:
|
|
version = target[HaskellLibraryInfo].version
|
|
else:
|
|
version = "0"
|
|
|
|
args = ctx.actions.args()
|
|
args.add("--package-name={0}".format(package_id))
|
|
args.add("--package-version={0}".format(version))
|
|
args.add_all([
|
|
"-D",
|
|
haddock_file.path,
|
|
"-o",
|
|
html_dir.path,
|
|
"--html",
|
|
"--hoogle",
|
|
"--title={0}".format(package_id),
|
|
"--hyperlinked-source",
|
|
])
|
|
|
|
transitive_haddocks = {}
|
|
transitive_html = {}
|
|
|
|
for dep in ctx.rule.attr.deps:
|
|
if HaddockInfo in dep:
|
|
transitive_haddocks.update(dep[HaddockInfo].transitive_haddocks)
|
|
transitive_html.update(dep[HaddockInfo].transitive_html)
|
|
|
|
for pid in transitive_haddocks:
|
|
args.add("--read-interface=../{0},{1}".format(
|
|
pid,
|
|
transitive_haddocks[pid].path,
|
|
))
|
|
|
|
prebuilt_deps = ctx.actions.args()
|
|
for dep in set.to_list(target[HaskellInfo].prebuilt_dependencies):
|
|
prebuilt_deps.add(dep.package)
|
|
prebuilt_deps.use_param_file(param_file_arg = "%s", use_always = True)
|
|
|
|
compile_flags = ctx.actions.args()
|
|
for x in target[HaskellInfo].compile_flags:
|
|
compile_flags.add_all(["--optghc", x])
|
|
compile_flags.add_all([x.path for x in set.to_list(target[HaskellInfo].source_files)])
|
|
compile_flags.add("-v0")
|
|
|
|
# haddock flags should take precedence over ghc args, hence are in
|
|
# last position
|
|
compile_flags.add_all(hs.toolchain.haddock_flags)
|
|
|
|
locale_archive_depset = (
|
|
depset([hs.toolchain.locale_archive]) if hs.toolchain.locale_archive != None else depset()
|
|
)
|
|
|
|
# TODO(mboes): we should be able to instantiate this template only
|
|
# once per toolchain instance, rather than here.
|
|
haddock_wrapper = ctx.actions.declare_file("haddock_wrapper-{}".format(hs.name))
|
|
ctx.actions.expand_template(
|
|
template = ctx.file._haddock_wrapper_tpl,
|
|
output = haddock_wrapper,
|
|
substitutions = {
|
|
"%{ghc-pkg}": hs.tools.ghc_pkg.path,
|
|
"%{haddock}": hs.tools.haddock.path,
|
|
# XXX Workaround
|
|
# https://github.com/bazelbuild/bazel/issues/5980.
|
|
"%{env}": render_env(hs.env),
|
|
},
|
|
is_executable = True,
|
|
)
|
|
|
|
# Transitive library dependencies for runtime.
|
|
trans_link_ctx = target[HaskellInfo].transitive_cc_dependencies.dynamic_linking
|
|
trans_libs = trans_link_ctx.libraries_to_link.to_list()
|
|
|
|
ctx.actions.run(
|
|
inputs = depset(transitive = [
|
|
set.to_depset(target[HaskellInfo].package_databases),
|
|
set.to_depset(target[HaskellInfo].interface_dirs),
|
|
set.to_depset(target[HaskellInfo].source_files),
|
|
target[HaskellInfo].extra_source_files,
|
|
set.to_depset(target[HaskellInfo].dynamic_libraries),
|
|
depset(trans_libs),
|
|
depset(transitive_haddocks.values()),
|
|
depset(transitive_html.values()),
|
|
target[CcInfo].compilation_context.headers,
|
|
depset([
|
|
hs.tools.ghc_pkg,
|
|
hs.tools.haddock,
|
|
]),
|
|
locale_archive_depset,
|
|
]),
|
|
outputs = [haddock_file, html_dir],
|
|
mnemonic = "HaskellHaddock",
|
|
progress_message = "HaskellHaddock {}".format(ctx.label),
|
|
executable = haddock_wrapper,
|
|
arguments = [
|
|
prebuilt_deps,
|
|
args,
|
|
compile_flags,
|
|
],
|
|
use_default_shell_env = True,
|
|
)
|
|
|
|
transitive_html.update({package_id: html_dir})
|
|
transitive_haddocks.update({package_id: haddock_file})
|
|
|
|
haddock_info = HaddockInfo(
|
|
package_id = package_id,
|
|
transitive_html = transitive_html,
|
|
transitive_haddocks = transitive_haddocks,
|
|
)
|
|
output_files = OutputGroupInfo(default = transitive_html.values())
|
|
|
|
return [haddock_info, output_files]
|
|
|
|
haskell_doc_aspect = aspect(
|
|
_haskell_doc_aspect_impl,
|
|
attrs = {
|
|
"_haddock_wrapper_tpl": attr.label(
|
|
allow_single_file = True,
|
|
default = Label("@io_tweag_rules_haskell//haskell:private/haddock_wrapper.sh.tpl"),
|
|
),
|
|
},
|
|
attr_aspects = ["deps"],
|
|
toolchains = ["@io_tweag_rules_haskell//haskell:toolchain"],
|
|
)
|
|
|
|
def _haskell_doc_rule_impl(ctx):
|
|
hs = haskell_context(ctx)
|
|
|
|
# Reject cases when number of dependencies is 0.
|
|
|
|
if not ctx.attr.deps:
|
|
fail("haskell_doc needs at least one haskell_library component in deps")
|
|
|
|
doc_root_raw = ctx.attr.name
|
|
haddock_dict = {}
|
|
html_dict_original = {}
|
|
all_caches = set.empty()
|
|
|
|
for dep in ctx.attr.deps:
|
|
if HaddockInfo in dep:
|
|
html_dict_original.update(dep[HaddockInfo].transitive_html)
|
|
haddock_dict.update(dep[HaddockInfo].transitive_haddocks)
|
|
if HaskellInfo in dep:
|
|
set.mutable_union(
|
|
all_caches,
|
|
dep[HaskellInfo].package_databases,
|
|
)
|
|
|
|
# Copy docs of Bazel deps into predefined locations under the root doc
|
|
# directory.
|
|
|
|
html_dict_copied = {}
|
|
doc_root_path = ""
|
|
|
|
for package_id in html_dict_original:
|
|
html_dir = html_dict_original[package_id]
|
|
output_dir = ctx.actions.declare_directory(
|
|
paths.join(
|
|
doc_root_raw,
|
|
package_id,
|
|
),
|
|
)
|
|
doc_root_path = paths.dirname(output_dir.path)
|
|
|
|
html_dict_copied[package_id] = output_dir
|
|
|
|
ctx.actions.run_shell(
|
|
inputs = [html_dir],
|
|
outputs = [output_dir],
|
|
command = """
|
|
mkdir -p "{doc_dir}"
|
|
# Copy Haddocks of a dependency.
|
|
cp -R -L "{html_dir}/." "{target_dir}"
|
|
""".format(
|
|
doc_dir = doc_root_path,
|
|
html_dir = html_dir.path,
|
|
target_dir = output_dir.path,
|
|
),
|
|
)
|
|
|
|
# Do one more Haddock call to generate the unified index
|
|
|
|
index_root_raw = paths.join(doc_root_raw, "index")
|
|
index_root = ctx.actions.declare_directory(index_root_raw)
|
|
|
|
args = ctx.actions.args()
|
|
args.add_all([
|
|
"-o",
|
|
index_root.path,
|
|
"--title={0}".format(ctx.attr.name),
|
|
"--gen-index",
|
|
"--gen-contents",
|
|
])
|
|
|
|
if ctx.attr.index_transitive_deps:
|
|
# Include all packages in the unified index.
|
|
for package_id in html_dict_copied:
|
|
args.add("--read-interface=../{0},{1}".format(
|
|
package_id,
|
|
haddock_dict[package_id].path,
|
|
))
|
|
else:
|
|
# Include only direct dependencies.
|
|
for dep in ctx.attr.deps:
|
|
if HaddockInfo in dep:
|
|
package_id = dep[HaddockInfo].package_id
|
|
args.add("--read-interface=../{0},{1}".format(
|
|
package_id,
|
|
haddock_dict[package_id].path,
|
|
))
|
|
|
|
for cache in set.to_list(all_caches):
|
|
args.add("--optghc=-package-db={0}".format(cache.dirname))
|
|
|
|
locale_archive_depset = (
|
|
depset([hs.toolchain.locale_archive]) if hs.toolchain.locale_archive != None else depset()
|
|
)
|
|
|
|
ctx.actions.run(
|
|
inputs = depset(transitive = [
|
|
set.to_depset(all_caches),
|
|
depset(html_dict_copied.values()),
|
|
depset(haddock_dict.values()),
|
|
locale_archive_depset,
|
|
]),
|
|
outputs = [index_root],
|
|
mnemonic = "HaskellHaddockIndex",
|
|
executable = hs.tools.haddock,
|
|
arguments = [args],
|
|
)
|
|
|
|
return [DefaultInfo(
|
|
files = depset(html_dict_copied.values() + [index_root]),
|
|
)]
|
|
|
|
haskell_doc = rule(
|
|
_haskell_doc_rule_impl,
|
|
attrs = {
|
|
"deps": attr.label_list(
|
|
aspects = [haskell_doc_aspect],
|
|
doc = "List of Haskell libraries to generate documentation for.",
|
|
),
|
|
"index_transitive_deps": attr.bool(
|
|
default = False,
|
|
doc = "Whether to include documentation of transitive dependencies in index.",
|
|
),
|
|
},
|
|
toolchains = ["@io_tweag_rules_haskell//haskell:toolchain"],
|
|
)
|
|
"""Create API documentation.
|
|
|
|
Builds API documentation (using [Haddock][haddock]) for the given
|
|
Haskell libraries. It will automatically build documentation for any
|
|
transitive dependencies to allow for cross-package documentation
|
|
linking.
|
|
|
|
Example:
|
|
```bzl
|
|
haskell_library(
|
|
name = "my-lib",
|
|
...
|
|
)
|
|
|
|
haskell_doc(
|
|
name = "my-lib-doc",
|
|
deps = [":my-lib"],
|
|
)
|
|
```
|
|
|
|
[haddock]: http://haskell-haddock.readthedocs.io/en/latest/
|
|
"""
|