summary refs log tree commit diff
path: root/third_party/bazel/rules_haskell/haskell/haddock.bzl
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/bazel/rules_haskell/haskell/haddock.bzl')
-rw-r--r--third_party/bazel/rules_haskell/haskell/haddock.bzl312
1 files changed, 312 insertions, 0 deletions
diff --git a/third_party/bazel/rules_haskell/haskell/haddock.bzl b/third_party/bazel/rules_haskell/haskell/haddock.bzl
new file mode 100644
index 000000000000..2e9d5709ac39
--- /dev/null
+++ b/third_party/bazel/rules_haskell/haskell/haddock.bzl
@@ -0,0 +1,312 @@
+"""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/
+"""