Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Self-contained haddocks with unified index #249

Merged
merged 1 commit into from
May 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions haskell/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
20 changes: 20 additions & 0 deletions haskell/copy-dep-haddock.sh
Original file line number Diff line number Diff line change
@@ -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 link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pattern we use elsewhere too, as an alternative to building a PATH that could introduce impurities. However, I'd be curious to know, what's the idiomatic way to do this in Bazel best practices? Are there established best practices already (in particular by ex-Blaze users) on how to feed scripts with their dependencies? Would it be better to use runfiles instead? Question for @judah.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, if a shell script depends on another binary or file, they'll declare it as a runfile and then reference it within the script using a hard-coded relative path. The path to the script's directory (and thus to the runfiles) is obtained using something like:
$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
which is more robust to symlinks.

However, for binaries like "mkdir" or "cp", all the other scripts I've seen have just called them directly, assuming they would be on the default path. For example:
https://github.com/bazelbuild/bazel/search?utf8=%E2%9C%93&q=mkdir&type=
I guess that only really becomes an issue when you're relying on Nix to supply those binaries.

Honestly, though, for something as simple as this script I'd feel it simpler to just call ctx.actions.run_shell. Both approaches seem defensible though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that only really becomes an issue when you're relying on Nix to supply those binaries.

Right. On NixOS, @mrkkrp has no alternative but to do what he did or to use runfiles. /bin/ just doesn't exist.


# Copy Haddocks of a dependency.

"$RULES_HASKELL_CP" -r "$RULES_HASKELL_HTML_DIR" "$RULES_HASKELL_TARGET_DIR"
184 changes: 142 additions & 42 deletions haskell/haddock.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -20,75 +20,48 @@ 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()
args.add([
"-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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand All @@ -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,
Expand All @@ -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"],
)
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion haskell/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
}
)

Expand Down
2 changes: 2 additions & 0 deletions haskell/toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ def _haskell_toolchain_impl(ctx):
"ln",
"grep",
"tee",
"mkdir",
"cp",
]

for target in targets_w:
Expand Down
25 changes: 12 additions & 13 deletions tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Expand Down
7 changes: 7 additions & 0 deletions tests/haddock/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)