diff options
Diffstat (limited to 'third_party/bazel/rules_haskell/haskell/haddock.bzl')
-rw-r--r-- | third_party/bazel/rules_haskell/haskell/haddock.bzl | 312 |
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/ +""" |