Skip to content

Commit fe291bf

Browse files
committed
feat(sphinxdocs): make SphinxDocsLibraryInfo provider public
Custom rules that produce doc files for sphinx_docs currently must depend on the private sphinx_docs_library rule implementation, since SphinxDocsLibraryInfo lives under sphinxdocs/private. Expose it via //sphinxdocs:sphinx_docs_library_info.bzl so custom rules can supply docs without that dependency. * Define a SphinxDocsFileset provider for the entries of the transitive field, so it types as depset[SphinxDocsFileset] instead of depset[struct]. * Add create_sphinx_docs_library_info(), which builds the provider from direct files plus deps, so callers don't require any inside knowledge. * Add a custom_docs_library test rule and output tests verifying a non-sphinx_docs_library rule can supply docs to sphinx_docs through the public provider, both directly and via deps
1 parent 36c342a commit fe291bf

8 files changed

Lines changed: 184 additions & 28 deletions

File tree

sphinxdocs/docs/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ sphinx_stardocs(
5151
"//sphinxdocs:readthedocs",
5252
"//sphinxdocs:sphinx",
5353
"//sphinxdocs:sphinx_docs_library",
54+
"//sphinxdocs:sphinx_docs_library_info",
5455
"//sphinxdocs:sphinx_stardoc",
5556
"//sphinxdocs/private:sphinx_docs_library",
5657
],

sphinxdocs/sphinxdocs/BUILD.bazel

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ bzl_library(
6363
deps = ["//sphinxdocs/private:sphinx_docs_library_macro"],
6464
)
6565

66+
bzl_library(
67+
name = "sphinx_docs_library_info",
68+
srcs = ["sphinx_docs_library_info.bzl"],
69+
visibility = ["//visibility:public"],
70+
deps = ["//sphinxdocs/private:sphinx_docs_library_info"],
71+
)
72+
6673
bzl_library(
6774
name = "sphinx_stardoc",
6875
srcs = ["sphinx_stardoc.bzl"],

sphinxdocs/sphinxdocs/private/sphinx_docs_library.bzl

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
11
"""Implementation of sphinx_docs_library."""
22

3-
load(":sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo")
3+
load(
4+
":sphinx_docs_library_info.bzl",
5+
"SphinxDocsLibraryInfo",
6+
"create_sphinx_docs_library_info",
7+
)
48

59
def _sphinx_docs_library_impl(ctx):
6-
strip_prefix = ctx.attr.strip_prefix or (ctx.label.package + "/")
7-
direct_entries = []
8-
if ctx.files.srcs:
9-
entry = struct(
10-
strip_prefix = strip_prefix,
11-
prefix = ctx.attr.prefix,
12-
files = ctx.files.srcs,
13-
)
14-
direct_entries.append(entry)
15-
1610
return [
17-
SphinxDocsLibraryInfo(
18-
strip_prefix = strip_prefix,
19-
prefix = ctx.attr.prefix,
11+
create_sphinx_docs_library_info(
2012
files = ctx.files.srcs,
21-
transitive = depset(
22-
direct = direct_entries,
23-
transitive = [t[SphinxDocsLibraryInfo].transitive for t in ctx.attr.deps],
24-
),
13+
prefix = ctx.attr.prefix,
14+
strip_prefix = ctx.attr.strip_prefix or (ctx.label.package + "/"),
15+
deps = ctx.attr.deps,
2516
),
2617
DefaultInfo(
2718
files = depset(ctx.files.srcs),
Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,82 @@
11
"""Provider for collecting doc files as libraries."""
2+
SphinxDocsFileset = provider(
3+
doc = "A set of doc files sharing the same path manipulation.",
4+
fields = {
5+
"files": """
6+
:type: tuple[File]
7+
8+
The documentation files. A tuple because depset elements must be immutable.
9+
""",
10+
"prefix": """
11+
:type: str
12+
13+
Prefix to prepend to file paths in `files`. Added after `strip_prefix` is removed.
14+
""",
15+
"strip_prefix": """
16+
:type: str
17+
18+
Prefix to remove from file paths in `files`. Removed before `prefix` is prepended.
19+
""",
20+
},
21+
)
222

323
SphinxDocsLibraryInfo = provider(
424
doc = "Information about a collection of doc files.",
525
fields = {
626
"files": """
7-
:type: depset[File]
27+
:type: list[File]
828
9-
The documentation files for the library.
29+
The direct documentation files for the library.
1030
""",
1131
"prefix": """
1232
:type: str
1333
14-
Prefix to prepend to file paths in `files`. It is added after `strip_prefix`
15-
is removed.
34+
Prefix to prepend to file paths in `files`. Added after `strip_prefix` is removed.
1635
""",
1736
"strip_prefix": """
1837
:type: str
1938
20-
Prefix to remove from file paths in `files`. It is removed before `prefix`
21-
is prepended.
39+
Prefix to remove from file paths in `files`. Removed before `prefix` is prepended.
2240
""",
2341
"transitive": """
24-
:type: depset[struct]
42+
:type: depset[SphinxDocsFileset]
43+
44+
This library's own files and those of its deps.
2545
26-
Depset of transitive library information. Each entry in the depset is a struct
27-
with fields matching the fields of this provider.
46+
The only field consumers read, so a rule must include its own
47+
{obj}`SphinxDocsFileset` here or its files are silently ignored. Use
48+
{obj}`create_sphinx_docs_library_info` to construct the provider correctly.
2849
""",
2950
},
3051
)
52+
53+
def create_sphinx_docs_library_info(*, files = [], prefix = "", strip_prefix = "", deps = []):
54+
"""Creates a {obj}`SphinxDocsLibraryInfo`, populating the `transitive` field.
55+
56+
Args:
57+
files: {type}`list[File]` the direct doc files.
58+
prefix: {type}`str` prefix to prepend to `files` paths. Not applied to `deps`.
59+
strip_prefix: {type}`str` prefix to remove from `files` paths. Not applied to `deps`.
60+
deps: {type}`list[Target]` targets with {obj}`SphinxDocsLibraryInfo` whose
61+
files are included as-is.
62+
63+
Returns:
64+
{type}`SphinxDocsLibraryInfo`
65+
"""
66+
direct = []
67+
if files:
68+
direct.append(SphinxDocsFileset(
69+
files = tuple(files),
70+
prefix = prefix,
71+
strip_prefix = strip_prefix,
72+
))
73+
74+
return SphinxDocsLibraryInfo(
75+
files = files,
76+
prefix = prefix,
77+
strip_prefix = strip_prefix,
78+
transitive = depset(
79+
direct = direct,
80+
transitive = [d[SphinxDocsLibraryInfo].transitive for d in deps],
81+
),
82+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Public entry point for SphinxDocsLibraryInfo.
2+
3+
Lets custom rules supply doc files to `sphinx_docs` without depending on the
4+
`sphinx_docs_library` rule implementation:
5+
6+
```starlark
7+
load(
8+
"@sphinxdocs//sphinxdocs:sphinx_docs_library_info.bzl",
9+
"create_sphinx_docs_library_info",
10+
)
11+
12+
def _my_docs_impl(ctx):
13+
return [create_sphinx_docs_library_info(
14+
files = ctx.files.srcs,
15+
prefix = "my_docs/",
16+
strip_prefix = ctx.label.package + "/",
17+
deps = ctx.attr.deps,
18+
)]
19+
```
20+
"""
21+
22+
load(
23+
"//sphinxdocs/private:sphinx_docs_library_info.bzl",
24+
_SphinxDocsFileset = "SphinxDocsFileset",
25+
_SphinxDocsLibraryInfo = "SphinxDocsLibraryInfo",
26+
_create_sphinx_docs_library_info = "create_sphinx_docs_library_info",
27+
)
28+
29+
# buildifier: disable=name-conventions
30+
SphinxDocsFileset = _SphinxDocsFileset
31+
32+
SphinxDocsLibraryInfo = _SphinxDocsLibraryInfo
33+
34+
create_sphinx_docs_library_info = _create_sphinx_docs_library_info

sphinxdocs/tests/sphinx_docs/BUILD.bazel

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
load("@bazel_skylib//rules:build_test.bzl", "build_test")
22
load("@rules_python//python:py_test.bzl", "py_test")
33
load("//sphinxdocs:sphinx.bzl", "sphinx_build_binary", "sphinx_docs")
4-
load(":defs.bzl", "gen_directory")
4+
load(":defs.bzl", "custom_docs_library", "gen_directory")
55

66
# We only build for Linux and Mac because:
77
# 1. The actual doc process only runs on Linux
@@ -28,6 +28,7 @@ sphinx_docs(
2828
sphinx = ":sphinx-build",
2929
strip_prefix = package_name() + "/",
3030
target_compatible_with = _TARGET_COMPATIBLE_WITH,
31+
deps = [":custom_docs"],
3132
)
3233

3334
genrule(
@@ -40,6 +41,27 @@ gen_directory(
4041
name = "generated_directory",
4142
)
4243

44+
custom_docs_library(
45+
name = "custom_docs",
46+
page_name = "custom_page",
47+
prefix = "custom/",
48+
deps = [
49+
":custom_docs_dep",
50+
":custom_docs_empty",
51+
],
52+
)
53+
54+
# The parent's prefix must not be applied to a dep's files.
55+
custom_docs_library(
56+
name = "custom_docs_dep",
57+
page_name = "custom_dep_page",
58+
prefix = "custom_dep/",
59+
)
60+
61+
custom_docs_library(
62+
name = "custom_docs_empty",
63+
)
64+
4365
sphinx_build_binary(
4466
name = "sphinx-build",
4567
tags = ["manual"], # Only needed as part of sphinx doc building

sphinxdocs/tests/sphinx_docs/defs.bzl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
11
"""Supporting code for tests."""
22

3+
load(
4+
"//sphinxdocs:sphinx_docs_library_info.bzl",
5+
"SphinxDocsLibraryInfo",
6+
"create_sphinx_docs_library_info",
7+
)
8+
9+
def _custom_docs_library_impl(ctx):
10+
files = []
11+
if ctx.attr.page_name:
12+
out = ctx.actions.declare_file(ctx.attr.page_name + ".md")
13+
ctx.actions.write(out, "# {}\n".format(ctx.attr.page_name))
14+
files.append(out)
15+
16+
return [
17+
create_sphinx_docs_library_info(
18+
files = files,
19+
prefix = ctx.attr.prefix,
20+
strip_prefix = ctx.label.package + "/",
21+
deps = ctx.attr.deps,
22+
),
23+
DefaultInfo(files = depset(files)),
24+
]
25+
26+
# Verifies a rule that isn't sphinx_docs_library can supply doc files to
27+
# sphinx_docs using only the public SphinxDocsLibraryInfo entry point.
28+
custom_docs_library = rule(
29+
implementation = _custom_docs_library_impl,
30+
attrs = {
31+
"deps": attr.label_list(providers = [SphinxDocsLibraryInfo]),
32+
# When unset, the rule produces no direct files, which exercises the
33+
# empty-files path of create_sphinx_docs_library_info.
34+
"page_name": attr.string(),
35+
"prefix": attr.string(),
36+
},
37+
)
38+
339
def _gen_directory_impl(ctx):
440
out = ctx.actions.declare_directory(ctx.label.name)
541

sphinxdocs/tests/sphinx_docs/sphinx_docs_output_test.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ def test_directory_artifact_relative_xref(self):
2323
break
2424
self.assertEqual("dir_page2.html", actual)
2525

26+
def test_custom_sphinx_docs_library_info_provider(self):
27+
page_path = importlib.resources.files(sphinx_docs).joinpath(
28+
"docs/_build/html/custom/custom_page.html"
29+
)
30+
self.assertTrue(os.path.exists(str(page_path)), f"Not found at {page_path}")
31+
32+
def test_custom_sphinx_docs_library_info_deps(self):
33+
# The dep's own prefix applies; the parent's prefix does not.
34+
page_path = importlib.resources.files(sphinx_docs).joinpath(
35+
"docs/_build/html/custom_dep/custom_dep_page.html"
36+
)
37+
self.assertTrue(os.path.exists(str(page_path)), f"Not found at {page_path}")
38+
2639

2740
if __name__ == "__main__":
2841
absltest.main()

0 commit comments

Comments
 (0)