From f3408ea7739e397d460c2ee832edd91dadc50a97 Mon Sep 17 00:00:00 2001 From: mrkkrp Date: Tue, 15 May 2018 00:59:44 +0700 Subject: [PATCH] Self-contained haddocks with unified index --- haskell/BUILD | 1 + haskell/copy-dep-haddock.sh | 20 ++++ haskell/haddock.bzl | 184 ++++++++++++++++++++++++++++-------- haskell/providers.bzl | 4 +- haskell/toolchain.bzl | 2 + tests/BUILD | 25 +++-- tests/haddock/BUILD | 7 ++ 7 files changed, 187 insertions(+), 56 deletions(-) create mode 100755 haskell/copy-dep-haddock.sh diff --git a/haskell/BUILD b/haskell/BUILD index f873e658e..3ac4dedd7 100644 --- a/haskell/BUILD +++ b/haskell/BUILD @@ -12,6 +12,7 @@ exports_files([ "ghc_bindist.bzl", "ghci-repl.bzl", + "copy-dep-haddock.sh", "ghc-defs-cleanup.sh", "ghc-lint-wrapper.sh", "ghc-version-check.sh", diff --git a/haskell/copy-dep-haddock.sh b/haskell/copy-dep-haddock.sh new file mode 100755 index 000000000..4dac11b55 --- /dev/null +++ b/haskell/copy-dep-haddock.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# +# Usage: copy-dep-haddock.sh +# +# Environment variables: +# * RULES_HASKELL_MKDIR -- location of mkdir +# * RULES_HASKELL_CP -- location of cp +# * RULES_HASKELL_DOC_DIR -- root of doc directory +# * RULES_HASKELL_HTML_DIR -- html directory with Haddocks to copy +# * RULES_HASKELL_TARGET_DIR -- directory where to copy contents of html dir + +set -o pipefail + +# Ensure that top-level doc directory exists. + +"$RULES_HASKELL_MKDIR" -p "$RULES_HASKELL_DOC_DIR" + +# Copy Haddocks of a dependency. + +"$RULES_HASKELL_CP" -r "$RULES_HASKELL_HTML_DIR" "$RULES_HASKELL_TARGET_DIR" diff --git a/haskell/haddock.bzl b/haskell/haddock.bzl index a77271c52..db22e1c55 100644 --- a/haskell/haddock.bzl +++ b/haskell/haddock.bzl @@ -20,47 +20,19 @@ load(":providers.bzl", load("@bazel_skylib//:lib.bzl", "paths") -def _truly_relativize(target, relative_to): - """Return a relative path to `target` from `relative_to`. - - Args: - target: File, path to directory we want to get relative path to. - relative_to: File, path to directory from which we are starting. - - Returns: - string: relative path to `target`. - """ - t_pieces = target.path.split('/') - r_pieces = relative_to.path.split('/') - common_part_len = 0 - - for tp, rp in zip(t_pieces, r_pieces): - if tp == rp: - common_part_len += 1 - else: - break - - result = [".."] * (len(r_pieces) - common_part_len) - result += t_pieces[common_part_len:] - - return "/".join(result) - def _haskell_doc_aspect_impl(target, ctx): if HaskellBuildInfo not in target or HaskellLibraryInfo not in target: return [] pkg_id = target[HaskellLibraryInfo].package_id - - doc_dir_raw = "doc-{0}".format(pkg_id) - - doc_dir = ctx.actions.declare_directory(doc_dir_raw) + html_dir_raw = "doc-{0}".format(pkg_id) + html_dir = ctx.actions.declare_directory(html_dir_raw) haddock_interface = ctx.actions.declare_file( - paths.join(doc_dir_raw, "{0}.haddock".format(pkg_id)), - + paths.join(html_dir_raw, "{0}.haddock".format(pkg_id)), ) hoogle_file = ctx.actions.declare_file( pkg_id + ".txt", - sibling = haddock_interface + sibling = haddock_interface, ) args = ctx.actions.args() @@ -68,27 +40,28 @@ def _haskell_doc_aspect_impl(target, ctx): "-D", haddock_interface, "--package-name={0}".format(pkg_id), "--package-version={0}".format(ctx.rule.attr.version), - "-o", doc_dir, + "-o", html_dir, "--html", "--hoogle", "--title={0}".format(pkg_id), "--hyperlinked-source", ]) dep_interfaces = set.empty() + transitive_deps = { + pkg_id: html_dir, + } for dep in ctx.rule.attr.deps: if HaddockInfo in dep: - args.add("--read-interface={0},{1}".format( - _truly_relativize( - dep[HaddockInfo].doc_dir, - doc_dir, - ), + args.add("--read-interface=../{0},{1}".format( + dep[HaddockInfo].package_id, dep[HaddockInfo].interface_file.path )) dep_interfaces = set.mutable_insert( dep_interfaces, dep[HaddockInfo].interface_file ) + transitive_deps.update(dep[HaddockInfo].transitive_deps) static_haddock_outputs = [ ctx.actions.declare_file(f, sibling=haddock_interface) @@ -118,7 +91,7 @@ def _haskell_doc_aspect_impl(target, ctx): ] self_outputs = [ - doc_dir, + html_dir, haddock_interface, hoogle_file] + static_haddock_outputs + module_htmls @@ -153,7 +126,9 @@ def _haskell_doc_aspect_impl(target, ctx): return [HaddockInfo( outputs = depset(self_outputs), interface_file = haddock_interface, - doc_dir = doc_dir, + html_dir = html_dir, + package_id = pkg_id, + transitive_deps = transitive_deps, )] haskell_doc_aspect = aspect( @@ -169,11 +144,128 @@ haskell_doc_aspect = aspect( ) def _haskell_doc_rule_impl(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 + deps_dict_original = {} interface_files = depset() + all_caches = set.empty() + for dep in ctx.attr.deps: if HaddockInfo in dep: interface_files = depset(transitive = [interface_files, dep[HaddockInfo].outputs]) - return [DefaultInfo(files = interface_files)] + deps_dict_original.update(dep[HaddockInfo].transitive_deps) + if HaskellBuildInfo in dep: + set.mutable_union( + all_caches, + dep[HaskellBuildInfo].package_caches + ) + + # Copy docs of Bazel deps into predefined locations under the root doc + # directory. + + deps_dict_copied = {} + doc_root_path = "" + + for package_id in deps_dict_original: + html_dir = deps_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) + + deps_dict_copied[package_id] = output_dir + + ctx.actions.run( + inputs = [ + tools(ctx).cp, + tools(ctx).mkdir, + html_dir, + ], + outputs = [output_dir], + executable = ctx.file._copy_dep_haddock, + env = { + "RULES_HASKELL_CP": tools(ctx).cp.path, + "RULES_HASKELL_MKDIR": tools(ctx).mkdir.path, + "RULES_HASKELL_HTML_DIR": html_dir.path, + "RULES_HASKELL_DOC_DIR": doc_root_path, + "RULES_HASKELL_TARGET_DIR": output_dir.path, + }, + ) + + # Do one more Haddock call to generate the unified index + + args = ctx.actions.args() + args.add([ + "-o", doc_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 deps_dict_copied: + copied_html_dir = deps_dict_copied[package_id].path + args.add("--read-interface=./{0},{1}".format( + package_id, + paths.join( + copied_html_dir, + package_id + ".haddock", + ) + )) + else: + # Include only direct dependencies. + for dep in ctx.attr.deps: + if HaddockInfo in dep: + package_id = dep[HaddockInfo].package_id + copied_html_dir = deps_dict_copied[package_id].path + args.add("--read-interface=./{0},{1}".format( + package_id, + paths.join( + copied_html_dir, + package_id + ".haddock", + ) + )) + + for cache in set.to_list(all_caches): + args.add(["--optghc=-package-db={0}".format(cache.dirname)]) + + static_haddock_outputs = [ + ctx.actions.declare_file(paths.join(doc_root_raw, f)) + for f in [ + "doc-index.html", + "haddock-util.js", + "hslogo-16.png", + "index.html", + "minus.gif", + "ocean.css", + "plus.gif", + "synopsis.png", + ] + ] + + ctx.actions.run( + inputs = depset(transitive = [ + set.to_depset(all_caches), + depset(deps_dict_copied.values()), + ]), + outputs = static_haddock_outputs, + progress_message = "Creating unified Haddock index {0}".format(ctx.attr.name), + executable = tools(ctx).haddock, + arguments = [args], + ) + + return [DefaultInfo( + files = depset(deps_dict_copied.values() + static_haddock_outputs), + )] haskell_doc = rule( _haskell_doc_rule_impl, @@ -182,6 +274,14 @@ haskell_doc = rule( 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.", + ), + "_copy_dep_haddock": attr.label( + allow_single_file = True, + default = Label("@io_tweag_rules_haskell//haskell:copy-dep-haddock.sh"), + ), }, toolchains = ["@io_tweag_rules_haskell//haskell:toolchain"], ) @@ -190,7 +290,7 @@ haskell_doc = rule( 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. Currently linking to `prebuilt_deps` is not supported. +linking. Example: ```bzl diff --git a/haskell/providers.bzl b/haskell/providers.bzl index 6b6bc1e5a..888ba9818 100644 --- a/haskell/providers.bzl +++ b/haskell/providers.bzl @@ -38,7 +38,9 @@ HaddockInfo = provider( fields = { "outputs": "All interesting outputs produced by Haddock.", "interface_file": "Haddock interface file.", - "doc_dir": "Directory where all the documentation files live.", + "html_dir": "Directory where all the documentation files live.", + "package_id": "Package id, usually of the form name-version.", + "transitive_deps": "Dictionary from package id to html dir for transitive Bazel dependencies.", } ) diff --git a/haskell/toolchain.bzl b/haskell/toolchain.bzl index b932a289a..56d4e6ab7 100644 --- a/haskell/toolchain.bzl +++ b/haskell/toolchain.bzl @@ -127,6 +127,8 @@ def _haskell_toolchain_impl(ctx): "ln", "grep", "tee", + "mkdir", + "cp", ] for target in targets_w: diff --git a/tests/BUILD b/tests/BUILD index 711855518..878dda7d0 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -113,19 +113,18 @@ rule_test( rule_test( name = "test-haddock", generates = [ - "doc-testsZShaddockZShaddock-lib-b-1.0.0", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/LibB.html", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/doc-index.html", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/testsZShaddockZShaddock-lib-b-1.0.0.haddock", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/testsZShaddockZShaddock-lib-b-1.0.0.txt", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/haddock-util.js", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/hslogo-16.png", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/index.html", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/minus.gif", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/ocean.css", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/plus.gif", - "doc-testsZShaddockZShaddock-lib-b-1.0.0/synopsis.png", - ], + "haddock/doc-index.html", + "haddock/haddock-util.js", + "haddock/hslogo-16.png", + "haddock/index.html", + "haddock/minus.gif", + "haddock/ocean.css", + "haddock/plus.gif", + "haddock/synopsis.png", + "haddock/testsZShaddockZShaddock-lib-a-1.0.0", + "haddock/testsZShaddockZShaddock-lib-b-1.0.0", + "haddock/testsZShaddockZShaddock-lib-deep-1.0.0" + ], rule = "//tests/haddock", size = "small", ) diff --git a/tests/haddock/BUILD b/tests/haddock/BUILD index d9d41353c..a325bfec6 100644 --- a/tests/haddock/BUILD +++ b/tests/haddock/BUILD @@ -32,4 +32,11 @@ haskell_library( haskell_doc( name = "haddock", deps = [":haddock-lib-b"], + index_transitive_deps = False, +) + +haskell_doc( + name = "haddock-transitive", + deps = [":haddock-lib-b"], + index_transitive_deps = True, )