xref: /aosp_15_r20/external/bazelbuild-rules_python/sphinxdocs/private/sphinx.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1*60517a1eSAndroid Build Coastguard Worker# Copyright 2023 The Bazel Authors. All rights reserved.
2*60517a1eSAndroid Build Coastguard Worker#
3*60517a1eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*60517a1eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*60517a1eSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*60517a1eSAndroid Build Coastguard Worker#
7*60517a1eSAndroid Build Coastguard Worker#    http://www.apache.org/licenses/LICENSE-2.0
8*60517a1eSAndroid Build Coastguard Worker#
9*60517a1eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*60517a1eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*60517a1eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*60517a1eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*60517a1eSAndroid Build Coastguard Worker# limitations under the License.
14*60517a1eSAndroid Build Coastguard Worker
15*60517a1eSAndroid Build Coastguard Worker"""Implementation of sphinx rules."""
16*60517a1eSAndroid Build Coastguard Worker
17*60517a1eSAndroid Build Coastguard Workerload("@bazel_skylib//lib:paths.bzl", "paths")
18*60517a1eSAndroid Build Coastguard Workerload("@bazel_skylib//rules:build_test.bzl", "build_test")
19*60517a1eSAndroid Build Coastguard Workerload("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
20*60517a1eSAndroid Build Coastguard Workerload("//python:py_binary.bzl", "py_binary")
21*60517a1eSAndroid Build Coastguard Workerload("//python/private:util.bzl", "add_tag", "copy_propagating_kwargs")  # buildifier: disable=bzl-visibility
22*60517a1eSAndroid Build Coastguard Workerload(":sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo")
23*60517a1eSAndroid Build Coastguard Worker
24*60517a1eSAndroid Build Coastguard Worker_SPHINX_BUILD_MAIN_SRC = Label("//sphinxdocs/private:sphinx_build.py")
25*60517a1eSAndroid Build Coastguard Worker_SPHINX_SERVE_MAIN_SRC = Label("//sphinxdocs/private:sphinx_server.py")
26*60517a1eSAndroid Build Coastguard Worker
27*60517a1eSAndroid Build Coastguard Worker_SphinxSourceTreeInfo = provider(
28*60517a1eSAndroid Build Coastguard Worker    doc = "Information about source tree for Sphinx to build.",
29*60517a1eSAndroid Build Coastguard Worker    fields = {
30*60517a1eSAndroid Build Coastguard Worker        "source_dir_runfiles_path": """
31*60517a1eSAndroid Build Coastguard Worker:type: str
32*60517a1eSAndroid Build Coastguard Worker
33*60517a1eSAndroid Build Coastguard WorkerRunfiles-root relative path of the root directory for the source files.
34*60517a1eSAndroid Build Coastguard Worker""",
35*60517a1eSAndroid Build Coastguard Worker        "source_root": """
36*60517a1eSAndroid Build Coastguard Worker:type: str
37*60517a1eSAndroid Build Coastguard Worker
38*60517a1eSAndroid Build Coastguard WorkerExec-root relative path of the root directory for the source files (which are in DefaultInfo.files)
39*60517a1eSAndroid Build Coastguard Worker""",
40*60517a1eSAndroid Build Coastguard Worker    },
41*60517a1eSAndroid Build Coastguard Worker)
42*60517a1eSAndroid Build Coastguard Worker
43*60517a1eSAndroid Build Coastguard Worker_SphinxRunInfo = provider(
44*60517a1eSAndroid Build Coastguard Worker    doc = "Information for running the underlying Sphinx command directly",
45*60517a1eSAndroid Build Coastguard Worker    fields = {
46*60517a1eSAndroid Build Coastguard Worker        "per_format_args": """
47*60517a1eSAndroid Build Coastguard Worker:type: dict[str, struct]
48*60517a1eSAndroid Build Coastguard Worker
49*60517a1eSAndroid Build Coastguard WorkerA dict keyed by output format name. The values are a struct with attributes:
50*60517a1eSAndroid Build Coastguard Worker* args: a `list[str]` of args to run this format's build
51*60517a1eSAndroid Build Coastguard Worker* env: a `dict[str, str]` of environment variables to set for this format's build
52*60517a1eSAndroid Build Coastguard Worker""",
53*60517a1eSAndroid Build Coastguard Worker        "source_tree": """
54*60517a1eSAndroid Build Coastguard Worker:type: Target
55*60517a1eSAndroid Build Coastguard Worker
56*60517a1eSAndroid Build Coastguard WorkerTarget with the source tree files
57*60517a1eSAndroid Build Coastguard Worker""",
58*60517a1eSAndroid Build Coastguard Worker        "sphinx": """
59*60517a1eSAndroid Build Coastguard Worker:type: Target
60*60517a1eSAndroid Build Coastguard Worker
61*60517a1eSAndroid Build Coastguard WorkerThe sphinx-build binary to run.
62*60517a1eSAndroid Build Coastguard Worker""",
63*60517a1eSAndroid Build Coastguard Worker        "tools": """
64*60517a1eSAndroid Build Coastguard Worker:type: list[Target]
65*60517a1eSAndroid Build Coastguard Worker
66*60517a1eSAndroid Build Coastguard WorkerAdditional tools Sphinx needs
67*60517a1eSAndroid Build Coastguard Worker""",
68*60517a1eSAndroid Build Coastguard Worker    },
69*60517a1eSAndroid Build Coastguard Worker)
70*60517a1eSAndroid Build Coastguard Worker
71*60517a1eSAndroid Build Coastguard Workerdef sphinx_build_binary(name, py_binary_rule = py_binary, **kwargs):
72*60517a1eSAndroid Build Coastguard Worker    """Create an executable with the sphinx-build command line interface.
73*60517a1eSAndroid Build Coastguard Worker
74*60517a1eSAndroid Build Coastguard Worker    The `deps` must contain the sphinx library and any other extensions Sphinx
75*60517a1eSAndroid Build Coastguard Worker    needs at runtime.
76*60517a1eSAndroid Build Coastguard Worker
77*60517a1eSAndroid Build Coastguard Worker    Args:
78*60517a1eSAndroid Build Coastguard Worker        name: {type}`str` name of the target. The name "sphinx-build" is the
79*60517a1eSAndroid Build Coastguard Worker            conventional name to match what Sphinx itself uses.
80*60517a1eSAndroid Build Coastguard Worker        py_binary_rule: {type}`callable` A `py_binary` compatible callable
81*60517a1eSAndroid Build Coastguard Worker            for creating the target. If not set, the regular `py_binary`
82*60517a1eSAndroid Build Coastguard Worker            rule is used. This allows using the version-aware rules, or
83*60517a1eSAndroid Build Coastguard Worker            other alternative implementations.
84*60517a1eSAndroid Build Coastguard Worker        **kwargs: {type}`dict` Additional kwargs to pass onto `py_binary`. The `srcs` and
85*60517a1eSAndroid Build Coastguard Worker            `main` attributes must not be specified.
86*60517a1eSAndroid Build Coastguard Worker    """
87*60517a1eSAndroid Build Coastguard Worker    add_tag(kwargs, "@rules_python//sphinxdocs:sphinx_build_binary")
88*60517a1eSAndroid Build Coastguard Worker    py_binary_rule(
89*60517a1eSAndroid Build Coastguard Worker        name = name,
90*60517a1eSAndroid Build Coastguard Worker        srcs = [_SPHINX_BUILD_MAIN_SRC],
91*60517a1eSAndroid Build Coastguard Worker        main = _SPHINX_BUILD_MAIN_SRC,
92*60517a1eSAndroid Build Coastguard Worker        **kwargs
93*60517a1eSAndroid Build Coastguard Worker    )
94*60517a1eSAndroid Build Coastguard Worker
95*60517a1eSAndroid Build Coastguard Workerdef sphinx_docs(
96*60517a1eSAndroid Build Coastguard Worker        name,
97*60517a1eSAndroid Build Coastguard Worker        *,
98*60517a1eSAndroid Build Coastguard Worker        srcs = [],
99*60517a1eSAndroid Build Coastguard Worker        deps = [],
100*60517a1eSAndroid Build Coastguard Worker        renamed_srcs = {},
101*60517a1eSAndroid Build Coastguard Worker        sphinx,
102*60517a1eSAndroid Build Coastguard Worker        config,
103*60517a1eSAndroid Build Coastguard Worker        formats,
104*60517a1eSAndroid Build Coastguard Worker        strip_prefix = "",
105*60517a1eSAndroid Build Coastguard Worker        extra_opts = [],
106*60517a1eSAndroid Build Coastguard Worker        tools = [],
107*60517a1eSAndroid Build Coastguard Worker        **kwargs):
108*60517a1eSAndroid Build Coastguard Worker    """Generate docs using Sphinx.
109*60517a1eSAndroid Build Coastguard Worker
110*60517a1eSAndroid Build Coastguard Worker    Generates targets:
111*60517a1eSAndroid Build Coastguard Worker    * `<name>`: The output of this target is a directory for each
112*60517a1eSAndroid Build Coastguard Worker      format Sphinx creates. This target also has a separate output
113*60517a1eSAndroid Build Coastguard Worker      group for each format. e.g. `--output_group=html` will only build
114*60517a1eSAndroid Build Coastguard Worker      the "html" format files.
115*60517a1eSAndroid Build Coastguard Worker    * `<name>.serve`: A binary that locally serves the HTML output. This
116*60517a1eSAndroid Build Coastguard Worker      allows previewing docs during development.
117*60517a1eSAndroid Build Coastguard Worker    * `<name>.run`: A binary that directly runs the underlying Sphinx command
118*60517a1eSAndroid Build Coastguard Worker      to build the docs. This is a debugging aid.
119*60517a1eSAndroid Build Coastguard Worker
120*60517a1eSAndroid Build Coastguard Worker    Args:
121*60517a1eSAndroid Build Coastguard Worker        name: {type}`Name` name of the docs rule.
122*60517a1eSAndroid Build Coastguard Worker        srcs: {type}`list[label]` The source files for Sphinx to process.
123*60517a1eSAndroid Build Coastguard Worker        deps: {type}`list[label]` of {obj}`sphinx_docs_library` targets.
124*60517a1eSAndroid Build Coastguard Worker        renamed_srcs: {type}`dict[label, dict]` Doc source files for Sphinx that
125*60517a1eSAndroid Build Coastguard Worker            are renamed. This is typically used for files elsewhere, such as top
126*60517a1eSAndroid Build Coastguard Worker            level files in the repo.
127*60517a1eSAndroid Build Coastguard Worker        sphinx: {type}`label` the Sphinx tool to use for building
128*60517a1eSAndroid Build Coastguard Worker            documentation. Because Sphinx supports various plugins, you must
129*60517a1eSAndroid Build Coastguard Worker            construct your own binary with the necessary dependencies. The
130*60517a1eSAndroid Build Coastguard Worker            {obj}`sphinx_build_binary` rule can be used to define such a binary, but
131*60517a1eSAndroid Build Coastguard Worker            any executable supporting the `sphinx-build` command line interface
132*60517a1eSAndroid Build Coastguard Worker            can be used (typically some `py_binary` program).
133*60517a1eSAndroid Build Coastguard Worker        config: {type}`label` the Sphinx config file (`conf.py`) to use.
134*60517a1eSAndroid Build Coastguard Worker        formats: (list of str) the formats (`-b` flag) to generate documentation
135*60517a1eSAndroid Build Coastguard Worker            in. Each format will become an output group.
136*60517a1eSAndroid Build Coastguard Worker        strip_prefix: {type}`str` A prefix to remove from the file paths of the
137*60517a1eSAndroid Build Coastguard Worker            source files. e.g., given `//docs:foo.md`, stripping `docs/` makes
138*60517a1eSAndroid Build Coastguard Worker            Sphinx see `foo.md` in its generated source directory. If not
139*60517a1eSAndroid Build Coastguard Worker            specified, then {any}`native.package_name` is used.
140*60517a1eSAndroid Build Coastguard Worker        extra_opts: {type}`list[str]` Additional options to pass onto Sphinx building.
141*60517a1eSAndroid Build Coastguard Worker            On each provided option, a location expansion is performed.
142*60517a1eSAndroid Build Coastguard Worker            See {any}`ctx.expand_location`.
143*60517a1eSAndroid Build Coastguard Worker        tools: {type}`list[label]` Additional tools that are used by Sphinx and its plugins.
144*60517a1eSAndroid Build Coastguard Worker            This just makes the tools available during Sphinx execution. To locate
145*60517a1eSAndroid Build Coastguard Worker            them, use {obj}`extra_opts` and `$(location)`.
146*60517a1eSAndroid Build Coastguard Worker        **kwargs: {type}`dict` Common attributes to pass onto rules.
147*60517a1eSAndroid Build Coastguard Worker    """
148*60517a1eSAndroid Build Coastguard Worker    add_tag(kwargs, "@rules_python//sphinxdocs:sphinx_docs")
149*60517a1eSAndroid Build Coastguard Worker    common_kwargs = copy_propagating_kwargs(kwargs)
150*60517a1eSAndroid Build Coastguard Worker
151*60517a1eSAndroid Build Coastguard Worker    internal_name = "_{}".format(name.lstrip("_"))
152*60517a1eSAndroid Build Coastguard Worker
153*60517a1eSAndroid Build Coastguard Worker    _sphinx_source_tree(
154*60517a1eSAndroid Build Coastguard Worker        name = internal_name + "/_sources",
155*60517a1eSAndroid Build Coastguard Worker        srcs = srcs,
156*60517a1eSAndroid Build Coastguard Worker        deps = deps,
157*60517a1eSAndroid Build Coastguard Worker        renamed_srcs = renamed_srcs,
158*60517a1eSAndroid Build Coastguard Worker        config = config,
159*60517a1eSAndroid Build Coastguard Worker        strip_prefix = strip_prefix,
160*60517a1eSAndroid Build Coastguard Worker        **common_kwargs
161*60517a1eSAndroid Build Coastguard Worker    )
162*60517a1eSAndroid Build Coastguard Worker    _sphinx_docs(
163*60517a1eSAndroid Build Coastguard Worker        name = name,
164*60517a1eSAndroid Build Coastguard Worker        sphinx = sphinx,
165*60517a1eSAndroid Build Coastguard Worker        formats = formats,
166*60517a1eSAndroid Build Coastguard Worker        source_tree = internal_name + "/_sources",
167*60517a1eSAndroid Build Coastguard Worker        extra_opts = extra_opts,
168*60517a1eSAndroid Build Coastguard Worker        tools = tools,
169*60517a1eSAndroid Build Coastguard Worker        **kwargs
170*60517a1eSAndroid Build Coastguard Worker    )
171*60517a1eSAndroid Build Coastguard Worker
172*60517a1eSAndroid Build Coastguard Worker    html_name = internal_name + "_html"
173*60517a1eSAndroid Build Coastguard Worker    native.filegroup(
174*60517a1eSAndroid Build Coastguard Worker        name = html_name,
175*60517a1eSAndroid Build Coastguard Worker        srcs = [name],
176*60517a1eSAndroid Build Coastguard Worker        output_group = "html",
177*60517a1eSAndroid Build Coastguard Worker        **common_kwargs
178*60517a1eSAndroid Build Coastguard Worker    )
179*60517a1eSAndroid Build Coastguard Worker
180*60517a1eSAndroid Build Coastguard Worker    py_binary(
181*60517a1eSAndroid Build Coastguard Worker        name = name + ".serve",
182*60517a1eSAndroid Build Coastguard Worker        srcs = [_SPHINX_SERVE_MAIN_SRC],
183*60517a1eSAndroid Build Coastguard Worker        main = _SPHINX_SERVE_MAIN_SRC,
184*60517a1eSAndroid Build Coastguard Worker        data = [html_name],
185*60517a1eSAndroid Build Coastguard Worker        args = [
186*60517a1eSAndroid Build Coastguard Worker            "$(execpath {})".format(html_name),
187*60517a1eSAndroid Build Coastguard Worker        ],
188*60517a1eSAndroid Build Coastguard Worker        **common_kwargs
189*60517a1eSAndroid Build Coastguard Worker    )
190*60517a1eSAndroid Build Coastguard Worker    sphinx_run(
191*60517a1eSAndroid Build Coastguard Worker        name = name + ".run",
192*60517a1eSAndroid Build Coastguard Worker        docs = name,
193*60517a1eSAndroid Build Coastguard Worker    )
194*60517a1eSAndroid Build Coastguard Worker
195*60517a1eSAndroid Build Coastguard Worker    build_test(
196*60517a1eSAndroid Build Coastguard Worker        name = name + "_build_test",
197*60517a1eSAndroid Build Coastguard Worker        targets = [name],
198*60517a1eSAndroid Build Coastguard Worker        **kwargs  # kwargs used to pick up target_compatible_with
199*60517a1eSAndroid Build Coastguard Worker    )
200*60517a1eSAndroid Build Coastguard Worker
201*60517a1eSAndroid Build Coastguard Workerdef _sphinx_docs_impl(ctx):
202*60517a1eSAndroid Build Coastguard Worker    source_tree_info = ctx.attr.source_tree[_SphinxSourceTreeInfo]
203*60517a1eSAndroid Build Coastguard Worker    source_dir_path = source_tree_info.source_root
204*60517a1eSAndroid Build Coastguard Worker    inputs = ctx.attr.source_tree[DefaultInfo].files
205*60517a1eSAndroid Build Coastguard Worker
206*60517a1eSAndroid Build Coastguard Worker    per_format_args = {}
207*60517a1eSAndroid Build Coastguard Worker    outputs = {}
208*60517a1eSAndroid Build Coastguard Worker    for format in ctx.attr.formats:
209*60517a1eSAndroid Build Coastguard Worker        output_dir, args_env = _run_sphinx(
210*60517a1eSAndroid Build Coastguard Worker            ctx = ctx,
211*60517a1eSAndroid Build Coastguard Worker            format = format,
212*60517a1eSAndroid Build Coastguard Worker            source_path = source_dir_path,
213*60517a1eSAndroid Build Coastguard Worker            output_prefix = paths.join(ctx.label.name, "_build"),
214*60517a1eSAndroid Build Coastguard Worker            inputs = inputs,
215*60517a1eSAndroid Build Coastguard Worker        )
216*60517a1eSAndroid Build Coastguard Worker        outputs[format] = output_dir
217*60517a1eSAndroid Build Coastguard Worker        per_format_args[format] = args_env
218*60517a1eSAndroid Build Coastguard Worker    return [
219*60517a1eSAndroid Build Coastguard Worker        DefaultInfo(files = depset(outputs.values())),
220*60517a1eSAndroid Build Coastguard Worker        OutputGroupInfo(**{
221*60517a1eSAndroid Build Coastguard Worker            format: depset([output])
222*60517a1eSAndroid Build Coastguard Worker            for format, output in outputs.items()
223*60517a1eSAndroid Build Coastguard Worker        }),
224*60517a1eSAndroid Build Coastguard Worker        _SphinxRunInfo(
225*60517a1eSAndroid Build Coastguard Worker            sphinx = ctx.attr.sphinx,
226*60517a1eSAndroid Build Coastguard Worker            source_tree = ctx.attr.source_tree,
227*60517a1eSAndroid Build Coastguard Worker            tools = ctx.attr.tools,
228*60517a1eSAndroid Build Coastguard Worker            per_format_args = per_format_args,
229*60517a1eSAndroid Build Coastguard Worker        ),
230*60517a1eSAndroid Build Coastguard Worker    ]
231*60517a1eSAndroid Build Coastguard Worker
232*60517a1eSAndroid Build Coastguard Worker_sphinx_docs = rule(
233*60517a1eSAndroid Build Coastguard Worker    implementation = _sphinx_docs_impl,
234*60517a1eSAndroid Build Coastguard Worker    attrs = {
235*60517a1eSAndroid Build Coastguard Worker        "extra_opts": attr.string_list(
236*60517a1eSAndroid Build Coastguard Worker            doc = "Additional options to pass onto Sphinx. These are added after " +
237*60517a1eSAndroid Build Coastguard Worker                  "other options, but before the source/output args.",
238*60517a1eSAndroid Build Coastguard Worker        ),
239*60517a1eSAndroid Build Coastguard Worker        "formats": attr.string_list(doc = "Output formats for Sphinx to create."),
240*60517a1eSAndroid Build Coastguard Worker        "source_tree": attr.label(
241*60517a1eSAndroid Build Coastguard Worker            doc = "Directory of files for Sphinx to process.",
242*60517a1eSAndroid Build Coastguard Worker            providers = [_SphinxSourceTreeInfo],
243*60517a1eSAndroid Build Coastguard Worker        ),
244*60517a1eSAndroid Build Coastguard Worker        "sphinx": attr.label(
245*60517a1eSAndroid Build Coastguard Worker            executable = True,
246*60517a1eSAndroid Build Coastguard Worker            cfg = "exec",
247*60517a1eSAndroid Build Coastguard Worker            mandatory = True,
248*60517a1eSAndroid Build Coastguard Worker            doc = "Sphinx binary to generate documentation.",
249*60517a1eSAndroid Build Coastguard Worker        ),
250*60517a1eSAndroid Build Coastguard Worker        "tools": attr.label_list(
251*60517a1eSAndroid Build Coastguard Worker            cfg = "exec",
252*60517a1eSAndroid Build Coastguard Worker            doc = "Additional tools that are used by Sphinx and its plugins.",
253*60517a1eSAndroid Build Coastguard Worker        ),
254*60517a1eSAndroid Build Coastguard Worker        "_extra_defines_flag": attr.label(default = "//sphinxdocs:extra_defines"),
255*60517a1eSAndroid Build Coastguard Worker        "_extra_env_flag": attr.label(default = "//sphinxdocs:extra_env"),
256*60517a1eSAndroid Build Coastguard Worker        "_quiet_flag": attr.label(default = "//sphinxdocs:quiet"),
257*60517a1eSAndroid Build Coastguard Worker    },
258*60517a1eSAndroid Build Coastguard Worker)
259*60517a1eSAndroid Build Coastguard Worker
260*60517a1eSAndroid Build Coastguard Workerdef _run_sphinx(ctx, format, source_path, inputs, output_prefix):
261*60517a1eSAndroid Build Coastguard Worker    output_dir = ctx.actions.declare_directory(paths.join(output_prefix, format))
262*60517a1eSAndroid Build Coastguard Worker
263*60517a1eSAndroid Build Coastguard Worker    run_args = []  # Copy of the args to forward along to debug runner
264*60517a1eSAndroid Build Coastguard Worker    args = ctx.actions.args()  # Args passed to the action
265*60517a1eSAndroid Build Coastguard Worker
266*60517a1eSAndroid Build Coastguard Worker    args.add("--show-traceback")  # Full tracebacks on error
267*60517a1eSAndroid Build Coastguard Worker    run_args.append("--show-traceback")
268*60517a1eSAndroid Build Coastguard Worker    args.add("--builder", format)
269*60517a1eSAndroid Build Coastguard Worker    run_args.extend(("--builder", format))
270*60517a1eSAndroid Build Coastguard Worker
271*60517a1eSAndroid Build Coastguard Worker    if ctx.attr._quiet_flag[BuildSettingInfo].value:
272*60517a1eSAndroid Build Coastguard Worker        # Not added to run_args because run_args is for debugging
273*60517a1eSAndroid Build Coastguard Worker        args.add("--quiet")  # Suppress stdout informational text
274*60517a1eSAndroid Build Coastguard Worker
275*60517a1eSAndroid Build Coastguard Worker    # Build in parallel, if possible
276*60517a1eSAndroid Build Coastguard Worker    # Don't add to run_args: parallel building breaks interactive debugging
277*60517a1eSAndroid Build Coastguard Worker    args.add("--jobs", "auto")
278*60517a1eSAndroid Build Coastguard Worker    args.add("--fresh-env")  # Don't try to use cache files. Bazel can't make use of them.
279*60517a1eSAndroid Build Coastguard Worker    run_args.append("--fresh-env")
280*60517a1eSAndroid Build Coastguard Worker    args.add("--write-all")  # Write all files; don't try to detect "changed" files
281*60517a1eSAndroid Build Coastguard Worker    run_args.append("--write-all")
282*60517a1eSAndroid Build Coastguard Worker
283*60517a1eSAndroid Build Coastguard Worker    for opt in ctx.attr.extra_opts:
284*60517a1eSAndroid Build Coastguard Worker        expanded = ctx.expand_location(opt)
285*60517a1eSAndroid Build Coastguard Worker        args.add(expanded)
286*60517a1eSAndroid Build Coastguard Worker        run_args.append(expanded)
287*60517a1eSAndroid Build Coastguard Worker
288*60517a1eSAndroid Build Coastguard Worker    extra_defines = ctx.attr._extra_defines_flag[_FlagInfo].value
289*60517a1eSAndroid Build Coastguard Worker    args.add_all(extra_defines, before_each = "--define")
290*60517a1eSAndroid Build Coastguard Worker    for define in extra_defines:
291*60517a1eSAndroid Build Coastguard Worker        run_args.extend(("--define", define))
292*60517a1eSAndroid Build Coastguard Worker
293*60517a1eSAndroid Build Coastguard Worker    args.add(source_path)
294*60517a1eSAndroid Build Coastguard Worker    args.add(output_dir.path)
295*60517a1eSAndroid Build Coastguard Worker
296*60517a1eSAndroid Build Coastguard Worker    env = dict([
297*60517a1eSAndroid Build Coastguard Worker        v.split("=", 1)
298*60517a1eSAndroid Build Coastguard Worker        for v in ctx.attr._extra_env_flag[_FlagInfo].value
299*60517a1eSAndroid Build Coastguard Worker    ])
300*60517a1eSAndroid Build Coastguard Worker
301*60517a1eSAndroid Build Coastguard Worker    tools = []
302*60517a1eSAndroid Build Coastguard Worker    for tool in ctx.attr.tools:
303*60517a1eSAndroid Build Coastguard Worker        tools.append(tool[DefaultInfo].files_to_run)
304*60517a1eSAndroid Build Coastguard Worker
305*60517a1eSAndroid Build Coastguard Worker    ctx.actions.run(
306*60517a1eSAndroid Build Coastguard Worker        executable = ctx.executable.sphinx,
307*60517a1eSAndroid Build Coastguard Worker        arguments = [args],
308*60517a1eSAndroid Build Coastguard Worker        inputs = inputs,
309*60517a1eSAndroid Build Coastguard Worker        outputs = [output_dir],
310*60517a1eSAndroid Build Coastguard Worker        tools = tools,
311*60517a1eSAndroid Build Coastguard Worker        mnemonic = "SphinxBuildDocs",
312*60517a1eSAndroid Build Coastguard Worker        progress_message = "Sphinx building {} for %{{label}}".format(format),
313*60517a1eSAndroid Build Coastguard Worker        env = env,
314*60517a1eSAndroid Build Coastguard Worker    )
315*60517a1eSAndroid Build Coastguard Worker    return output_dir, struct(args = run_args, env = env)
316*60517a1eSAndroid Build Coastguard Worker
317*60517a1eSAndroid Build Coastguard Workerdef _sphinx_source_tree_impl(ctx):
318*60517a1eSAndroid Build Coastguard Worker    # Sphinx only accepts a single directory to read its doc sources from.
319*60517a1eSAndroid Build Coastguard Worker    # Because plain files and generated files are in different directories,
320*60517a1eSAndroid Build Coastguard Worker    # we need to merge the two into a single directory.
321*60517a1eSAndroid Build Coastguard Worker    source_prefix = ctx.label.name
322*60517a1eSAndroid Build Coastguard Worker    sphinx_source_files = []
323*60517a1eSAndroid Build Coastguard Worker
324*60517a1eSAndroid Build Coastguard Worker    # Materialize a file under the `_sources` dir
325*60517a1eSAndroid Build Coastguard Worker    def _relocate(source_file, dest_path = None):
326*60517a1eSAndroid Build Coastguard Worker        if not dest_path:
327*60517a1eSAndroid Build Coastguard Worker            dest_path = source_file.short_path.removeprefix(ctx.attr.strip_prefix)
328*60517a1eSAndroid Build Coastguard Worker        dest_file = ctx.actions.declare_file(paths.join(source_prefix, dest_path))
329*60517a1eSAndroid Build Coastguard Worker        ctx.actions.symlink(
330*60517a1eSAndroid Build Coastguard Worker            output = dest_file,
331*60517a1eSAndroid Build Coastguard Worker            target_file = source_file,
332*60517a1eSAndroid Build Coastguard Worker            progress_message = "Symlinking Sphinx source %{input} to %{output}",
333*60517a1eSAndroid Build Coastguard Worker        )
334*60517a1eSAndroid Build Coastguard Worker        sphinx_source_files.append(dest_file)
335*60517a1eSAndroid Build Coastguard Worker        return dest_file
336*60517a1eSAndroid Build Coastguard Worker
337*60517a1eSAndroid Build Coastguard Worker    # Though Sphinx has a -c flag, we move the config file into the sources
338*60517a1eSAndroid Build Coastguard Worker    # directory to make the config more intuitive because some configuration
339*60517a1eSAndroid Build Coastguard Worker    # options are relative to the config location, not the sources directory.
340*60517a1eSAndroid Build Coastguard Worker    source_conf_file = _relocate(ctx.file.config)
341*60517a1eSAndroid Build Coastguard Worker    sphinx_source_dir_path = paths.dirname(source_conf_file.path)
342*60517a1eSAndroid Build Coastguard Worker
343*60517a1eSAndroid Build Coastguard Worker    for src in ctx.attr.srcs:
344*60517a1eSAndroid Build Coastguard Worker        if SphinxDocsLibraryInfo in src:
345*60517a1eSAndroid Build Coastguard Worker            fail((
346*60517a1eSAndroid Build Coastguard Worker                "In attribute srcs: target {src} is misplaced here: " +
347*60517a1eSAndroid Build Coastguard Worker                "sphinx_docs_library targets belong in the deps attribute."
348*60517a1eSAndroid Build Coastguard Worker            ).format(src = src))
349*60517a1eSAndroid Build Coastguard Worker
350*60517a1eSAndroid Build Coastguard Worker    for orig_file in ctx.files.srcs:
351*60517a1eSAndroid Build Coastguard Worker        _relocate(orig_file)
352*60517a1eSAndroid Build Coastguard Worker
353*60517a1eSAndroid Build Coastguard Worker    for src_target, dest in ctx.attr.renamed_srcs.items():
354*60517a1eSAndroid Build Coastguard Worker        src_files = src_target.files.to_list()
355*60517a1eSAndroid Build Coastguard Worker        if len(src_files) != 1:
356*60517a1eSAndroid Build Coastguard Worker            fail("A single file must be specified to be renamed. Target {} " +
357*60517a1eSAndroid Build Coastguard Worker                 "generate {} files: {}".format(
358*60517a1eSAndroid Build Coastguard Worker                     src_target,
359*60517a1eSAndroid Build Coastguard Worker                     len(src_files),
360*60517a1eSAndroid Build Coastguard Worker                     src_files,
361*60517a1eSAndroid Build Coastguard Worker                 ))
362*60517a1eSAndroid Build Coastguard Worker        _relocate(src_files[0], dest)
363*60517a1eSAndroid Build Coastguard Worker
364*60517a1eSAndroid Build Coastguard Worker    for t in ctx.attr.deps:
365*60517a1eSAndroid Build Coastguard Worker        info = t[SphinxDocsLibraryInfo]
366*60517a1eSAndroid Build Coastguard Worker        for entry in info.transitive.to_list():
367*60517a1eSAndroid Build Coastguard Worker            for original in entry.files:
368*60517a1eSAndroid Build Coastguard Worker                new_path = entry.prefix + original.short_path.removeprefix(entry.strip_prefix)
369*60517a1eSAndroid Build Coastguard Worker                _relocate(original, new_path)
370*60517a1eSAndroid Build Coastguard Worker
371*60517a1eSAndroid Build Coastguard Worker    return [
372*60517a1eSAndroid Build Coastguard Worker        DefaultInfo(
373*60517a1eSAndroid Build Coastguard Worker            files = depset(sphinx_source_files),
374*60517a1eSAndroid Build Coastguard Worker        ),
375*60517a1eSAndroid Build Coastguard Worker        _SphinxSourceTreeInfo(
376*60517a1eSAndroid Build Coastguard Worker            source_root = sphinx_source_dir_path,
377*60517a1eSAndroid Build Coastguard Worker            source_dir_runfiles_path = paths.dirname(source_conf_file.short_path),
378*60517a1eSAndroid Build Coastguard Worker        ),
379*60517a1eSAndroid Build Coastguard Worker    ]
380*60517a1eSAndroid Build Coastguard Worker
381*60517a1eSAndroid Build Coastguard Worker_sphinx_source_tree = rule(
382*60517a1eSAndroid Build Coastguard Worker    implementation = _sphinx_source_tree_impl,
383*60517a1eSAndroid Build Coastguard Worker    attrs = {
384*60517a1eSAndroid Build Coastguard Worker        "config": attr.label(
385*60517a1eSAndroid Build Coastguard Worker            allow_single_file = True,
386*60517a1eSAndroid Build Coastguard Worker            mandatory = True,
387*60517a1eSAndroid Build Coastguard Worker            doc = "Config file for Sphinx",
388*60517a1eSAndroid Build Coastguard Worker        ),
389*60517a1eSAndroid Build Coastguard Worker        "deps": attr.label_list(
390*60517a1eSAndroid Build Coastguard Worker            providers = [SphinxDocsLibraryInfo],
391*60517a1eSAndroid Build Coastguard Worker        ),
392*60517a1eSAndroid Build Coastguard Worker        "renamed_srcs": attr.label_keyed_string_dict(
393*60517a1eSAndroid Build Coastguard Worker            allow_files = True,
394*60517a1eSAndroid Build Coastguard Worker            doc = "Doc source files for Sphinx that are renamed. This is " +
395*60517a1eSAndroid Build Coastguard Worker                  "typically used for files elsewhere, such as top level " +
396*60517a1eSAndroid Build Coastguard Worker                  "files in the repo.",
397*60517a1eSAndroid Build Coastguard Worker        ),
398*60517a1eSAndroid Build Coastguard Worker        "srcs": attr.label_list(
399*60517a1eSAndroid Build Coastguard Worker            allow_files = True,
400*60517a1eSAndroid Build Coastguard Worker            doc = "Doc source files for Sphinx.",
401*60517a1eSAndroid Build Coastguard Worker        ),
402*60517a1eSAndroid Build Coastguard Worker        "strip_prefix": attr.string(doc = "Prefix to remove from input file paths."),
403*60517a1eSAndroid Build Coastguard Worker    },
404*60517a1eSAndroid Build Coastguard Worker)
405*60517a1eSAndroid Build Coastguard Worker_FlagInfo = provider(
406*60517a1eSAndroid Build Coastguard Worker    doc = "Provider for a flag value",
407*60517a1eSAndroid Build Coastguard Worker    fields = ["value"],
408*60517a1eSAndroid Build Coastguard Worker)
409*60517a1eSAndroid Build Coastguard Worker
410*60517a1eSAndroid Build Coastguard Workerdef _repeated_string_list_flag_impl(ctx):
411*60517a1eSAndroid Build Coastguard Worker    return _FlagInfo(value = ctx.build_setting_value)
412*60517a1eSAndroid Build Coastguard Worker
413*60517a1eSAndroid Build Coastguard Workerrepeated_string_list_flag = rule(
414*60517a1eSAndroid Build Coastguard Worker    implementation = _repeated_string_list_flag_impl,
415*60517a1eSAndroid Build Coastguard Worker    build_setting = config.string_list(flag = True, repeatable = True),
416*60517a1eSAndroid Build Coastguard Worker)
417*60517a1eSAndroid Build Coastguard Worker
418*60517a1eSAndroid Build Coastguard Workerdef sphinx_inventory(*, name, src, **kwargs):
419*60517a1eSAndroid Build Coastguard Worker    """Creates a compressed inventory file from an uncompressed on.
420*60517a1eSAndroid Build Coastguard Worker
421*60517a1eSAndroid Build Coastguard Worker    The Sphinx inventory format isn't formally documented, but is understood
422*60517a1eSAndroid Build Coastguard Worker    to be:
423*60517a1eSAndroid Build Coastguard Worker
424*60517a1eSAndroid Build Coastguard Worker    ```
425*60517a1eSAndroid Build Coastguard Worker    # Sphinx inventory version 2
426*60517a1eSAndroid Build Coastguard Worker    # Project: <project name>
427*60517a1eSAndroid Build Coastguard Worker    # Version: <version string>
428*60517a1eSAndroid Build Coastguard Worker    # The remainder of this file is compressed using zlib
429*60517a1eSAndroid Build Coastguard Worker    name domain:role 1 relative-url display name
430*60517a1eSAndroid Build Coastguard Worker    ```
431*60517a1eSAndroid Build Coastguard Worker
432*60517a1eSAndroid Build Coastguard Worker    Where:
433*60517a1eSAndroid Build Coastguard Worker      * `<project name>` is a string. e.g. `Rules Python`
434*60517a1eSAndroid Build Coastguard Worker      * `<version string>` is a string e.g. `1.5.3`
435*60517a1eSAndroid Build Coastguard Worker
436*60517a1eSAndroid Build Coastguard Worker    And there are one or more `name domain:role ...` lines
437*60517a1eSAndroid Build Coastguard Worker      * `name`: the name of the symbol. It can contain special characters,
438*60517a1eSAndroid Build Coastguard Worker        but not spaces.
439*60517a1eSAndroid Build Coastguard Worker      * `domain:role`: The `domain` is usually a language, e.g. `py` or `bzl`.
440*60517a1eSAndroid Build Coastguard Worker        The `role` is usually the type of object, e.g. `class` or `func`. There
441*60517a1eSAndroid Build Coastguard Worker        is no canonical meaning to the values, they are usually domain-specific.
442*60517a1eSAndroid Build Coastguard Worker      * `1` is a number. It affects search priority.
443*60517a1eSAndroid Build Coastguard Worker      * `relative-url` is a URL path relative to the base url in the
444*60517a1eSAndroid Build Coastguard Worker        confg.py intersphinx config.
445*60517a1eSAndroid Build Coastguard Worker      * `display name` is a string. It can contain spaces, or simply be
446*60517a1eSAndroid Build Coastguard Worker        the value `-` to indicate it is the same as `name`
447*60517a1eSAndroid Build Coastguard Worker
448*60517a1eSAndroid Build Coastguard Worker    :::{seealso}
449*60517a1eSAndroid Build Coastguard Worker    {bzl:obj}`//sphinxdocs/inventories` for inventories of Bazel objects.
450*60517a1eSAndroid Build Coastguard Worker    :::
451*60517a1eSAndroid Build Coastguard Worker
452*60517a1eSAndroid Build Coastguard Worker    Args:
453*60517a1eSAndroid Build Coastguard Worker        name: {type}`Name` name of the target.
454*60517a1eSAndroid Build Coastguard Worker        src: {type}`label` Uncompressed inventory text file.
455*60517a1eSAndroid Build Coastguard Worker        **kwargs: {type}`dict` additional kwargs of common attributes.
456*60517a1eSAndroid Build Coastguard Worker    """
457*60517a1eSAndroid Build Coastguard Worker    _sphinx_inventory(name = name, src = src, **kwargs)
458*60517a1eSAndroid Build Coastguard Worker
459*60517a1eSAndroid Build Coastguard Workerdef _sphinx_inventory_impl(ctx):
460*60517a1eSAndroid Build Coastguard Worker    output = ctx.actions.declare_file(ctx.label.name + ".inv")
461*60517a1eSAndroid Build Coastguard Worker    args = ctx.actions.args()
462*60517a1eSAndroid Build Coastguard Worker    args.add(ctx.file.src)
463*60517a1eSAndroid Build Coastguard Worker    args.add(output)
464*60517a1eSAndroid Build Coastguard Worker    ctx.actions.run(
465*60517a1eSAndroid Build Coastguard Worker        executable = ctx.executable._builder,
466*60517a1eSAndroid Build Coastguard Worker        arguments = [args],
467*60517a1eSAndroid Build Coastguard Worker        inputs = depset([ctx.file.src]),
468*60517a1eSAndroid Build Coastguard Worker        outputs = [output],
469*60517a1eSAndroid Build Coastguard Worker    )
470*60517a1eSAndroid Build Coastguard Worker    return [DefaultInfo(files = depset([output]))]
471*60517a1eSAndroid Build Coastguard Worker
472*60517a1eSAndroid Build Coastguard Worker_sphinx_inventory = rule(
473*60517a1eSAndroid Build Coastguard Worker    implementation = _sphinx_inventory_impl,
474*60517a1eSAndroid Build Coastguard Worker    attrs = {
475*60517a1eSAndroid Build Coastguard Worker        "src": attr.label(allow_single_file = True),
476*60517a1eSAndroid Build Coastguard Worker        "_builder": attr.label(
477*60517a1eSAndroid Build Coastguard Worker            default = "//sphinxdocs/private:inventory_builder",
478*60517a1eSAndroid Build Coastguard Worker            executable = True,
479*60517a1eSAndroid Build Coastguard Worker            cfg = "exec",
480*60517a1eSAndroid Build Coastguard Worker        ),
481*60517a1eSAndroid Build Coastguard Worker    },
482*60517a1eSAndroid Build Coastguard Worker)
483*60517a1eSAndroid Build Coastguard Worker
484*60517a1eSAndroid Build Coastguard Workerdef _sphinx_run_impl(ctx):
485*60517a1eSAndroid Build Coastguard Worker    run_info = ctx.attr.docs[_SphinxRunInfo]
486*60517a1eSAndroid Build Coastguard Worker
487*60517a1eSAndroid Build Coastguard Worker    builder = ctx.attr.builder
488*60517a1eSAndroid Build Coastguard Worker
489*60517a1eSAndroid Build Coastguard Worker    if builder not in run_info.per_format_args:
490*60517a1eSAndroid Build Coastguard Worker        builder = run_info.per_format_args.keys()[0]
491*60517a1eSAndroid Build Coastguard Worker
492*60517a1eSAndroid Build Coastguard Worker    args_info = run_info.per_format_args.get(builder)
493*60517a1eSAndroid Build Coastguard Worker    if not args_info:
494*60517a1eSAndroid Build Coastguard Worker        fail("Format {} not built by {}".format(
495*60517a1eSAndroid Build Coastguard Worker            builder,
496*60517a1eSAndroid Build Coastguard Worker            ctx.attr.docs.label,
497*60517a1eSAndroid Build Coastguard Worker        ))
498*60517a1eSAndroid Build Coastguard Worker
499*60517a1eSAndroid Build Coastguard Worker    args_str = []
500*60517a1eSAndroid Build Coastguard Worker    args_str.extend(args_info.args)
501*60517a1eSAndroid Build Coastguard Worker    args_str = "\n".join(["args+=('{}')".format(value) for value in args_info.args])
502*60517a1eSAndroid Build Coastguard Worker    if not args_str:
503*60517a1eSAndroid Build Coastguard Worker        args_str = "# empty custom args"
504*60517a1eSAndroid Build Coastguard Worker
505*60517a1eSAndroid Build Coastguard Worker    env_str = "\n".join([
506*60517a1eSAndroid Build Coastguard Worker        "sphinx_env+=({}='{}')".format(*item)
507*60517a1eSAndroid Build Coastguard Worker        for item in args_info.env.items()
508*60517a1eSAndroid Build Coastguard Worker    ])
509*60517a1eSAndroid Build Coastguard Worker    if not env_str:
510*60517a1eSAndroid Build Coastguard Worker        env_str = "# empty custom env"
511*60517a1eSAndroid Build Coastguard Worker
512*60517a1eSAndroid Build Coastguard Worker    executable = ctx.actions.declare_file(ctx.label.name)
513*60517a1eSAndroid Build Coastguard Worker    sphinx = run_info.sphinx
514*60517a1eSAndroid Build Coastguard Worker    ctx.actions.expand_template(
515*60517a1eSAndroid Build Coastguard Worker        template = ctx.file._template,
516*60517a1eSAndroid Build Coastguard Worker        output = executable,
517*60517a1eSAndroid Build Coastguard Worker        substitutions = {
518*60517a1eSAndroid Build Coastguard Worker            "%SETUP_ARGS%": args_str,
519*60517a1eSAndroid Build Coastguard Worker            "%SETUP_ENV%": env_str,
520*60517a1eSAndroid Build Coastguard Worker            "%SOURCE_DIR_EXEC_PATH%": run_info.source_tree[_SphinxSourceTreeInfo].source_root,
521*60517a1eSAndroid Build Coastguard Worker            "%SOURCE_DIR_RUNFILES_PATH%": run_info.source_tree[_SphinxSourceTreeInfo].source_dir_runfiles_path,
522*60517a1eSAndroid Build Coastguard Worker            "%SPHINX_EXEC_PATH%": sphinx[DefaultInfo].files_to_run.executable.path,
523*60517a1eSAndroid Build Coastguard Worker            "%SPHINX_RUNFILES_PATH%": sphinx[DefaultInfo].files_to_run.executable.short_path,
524*60517a1eSAndroid Build Coastguard Worker        },
525*60517a1eSAndroid Build Coastguard Worker        is_executable = True,
526*60517a1eSAndroid Build Coastguard Worker    )
527*60517a1eSAndroid Build Coastguard Worker    runfiles = ctx.runfiles(
528*60517a1eSAndroid Build Coastguard Worker        transitive_files = run_info.source_tree[DefaultInfo].files,
529*60517a1eSAndroid Build Coastguard Worker    ).merge(sphinx[DefaultInfo].default_runfiles).merge_all([
530*60517a1eSAndroid Build Coastguard Worker        tool[DefaultInfo].default_runfiles
531*60517a1eSAndroid Build Coastguard Worker        for tool in run_info.tools
532*60517a1eSAndroid Build Coastguard Worker    ])
533*60517a1eSAndroid Build Coastguard Worker    return [
534*60517a1eSAndroid Build Coastguard Worker        DefaultInfo(
535*60517a1eSAndroid Build Coastguard Worker            executable = executable,
536*60517a1eSAndroid Build Coastguard Worker            runfiles = runfiles,
537*60517a1eSAndroid Build Coastguard Worker        ),
538*60517a1eSAndroid Build Coastguard Worker    ]
539*60517a1eSAndroid Build Coastguard Worker
540*60517a1eSAndroid Build Coastguard Workersphinx_run = rule(
541*60517a1eSAndroid Build Coastguard Worker    implementation = _sphinx_run_impl,
542*60517a1eSAndroid Build Coastguard Worker    doc = """
543*60517a1eSAndroid Build Coastguard WorkerDirectly run the underlying Sphinx command `sphinx_docs` uses.
544*60517a1eSAndroid Build Coastguard Worker
545*60517a1eSAndroid Build Coastguard WorkerThis is primarily a debugging tool. It's useful for directly running the
546*60517a1eSAndroid Build Coastguard WorkerSphinx command so that debuggers can be attached or output more directly
547*60517a1eSAndroid Build Coastguard Workerinspected without Bazel interference.
548*60517a1eSAndroid Build Coastguard Worker""",
549*60517a1eSAndroid Build Coastguard Worker    attrs = {
550*60517a1eSAndroid Build Coastguard Worker        "builder": attr.string(
551*60517a1eSAndroid Build Coastguard Worker            doc = "The output format to make runnable.",
552*60517a1eSAndroid Build Coastguard Worker            default = "html",
553*60517a1eSAndroid Build Coastguard Worker        ),
554*60517a1eSAndroid Build Coastguard Worker        "docs": attr.label(
555*60517a1eSAndroid Build Coastguard Worker            doc = "The {obj}`sphinx_docs` target to make directly runnable.",
556*60517a1eSAndroid Build Coastguard Worker            providers = [_SphinxRunInfo],
557*60517a1eSAndroid Build Coastguard Worker        ),
558*60517a1eSAndroid Build Coastguard Worker        "_template": attr.label(
559*60517a1eSAndroid Build Coastguard Worker            allow_single_file = True,
560*60517a1eSAndroid Build Coastguard Worker            default = "//sphinxdocs/private:sphinx_run_template.sh",
561*60517a1eSAndroid Build Coastguard Worker        ),
562*60517a1eSAndroid Build Coastguard Worker    },
563*60517a1eSAndroid Build Coastguard Worker    executable = True,
564*60517a1eSAndroid Build Coastguard Worker)
565