diff --git a/docs.bzl b/docs.bzl index 7f5ad0ced..2546aa44a 100644 --- a/docs.bzl +++ b/docs.bzl @@ -45,6 +45,71 @@ load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_venv") load("@docs_as_code_hub_env//:requirements.bzl", "all_requirements") load("@rules_python//sphinxdocs:sphinx.bzl", "sphinx_build_binary", "sphinx_docs") +# -- Host-platform transition for host-only documentation tooling -------------- +# +# The runnable doc targets (docs, ide_support, live_preview, ...) are pure host tools that run +# Python/Sphinx locally. Under a cross `--platforms` (e.g. QNX) they would be configured for the +# target platform and fail Python toolchain resolution. Instead of `target_compatible_with` (which +# would only make `bazel build //...` skip them), each is wrapped in a rule whose incoming transition +# pins `//command_line_option:platforms` to `--host_platform`, so they always build and run in the +# host config regardless of the top-level `--platforms`. + +def _to_host_platform_impl(settings, _attr): + return { + "//command_line_option:platforms": [ + settings["//command_line_option:host_platform"], + ], + } + +_to_host_platform = transition( + implementation = _to_host_platform_impl, + inputs = ["//command_line_option:host_platform"], + outputs = ["//command_line_option:platforms"], +) + +def _host_binary_impl(ctx): + # `actual` is a singleton list because of the outgoing transition. + target = ctx.attr.actual[0] + default_info = target[DefaultInfo] + + # Re-expose the wrapped binary under this target's name by symlinking its executable and + # forwarding its runfiles, so `bazel run` keeps working. + executable = ctx.actions.declare_file(ctx.label.name) + ctx.actions.symlink( + output = executable, + target_file = default_info.files_to_run.executable, + is_executable = True, + ) + runfiles = ctx.runfiles([executable]).merge(default_info.default_runfiles) + + providers = [DefaultInfo( + executable = executable, + files = depset([executable]), + runfiles = runfiles, + )] + + # Forward the wrapped binary's run environment (its `env` attr) so `bazel run` + # of the wrapper still applies it. + if RunEnvironmentInfo in target: + providers.append(target[RunEnvironmentInfo]) + return providers + +# Wrapper for runnable host tools (py_binary / py_venv / sphinx_build_binary). +_host_binary = rule( + implementation = _host_binary_impl, + executable = True, + attrs = { + "actual": attr.label( + mandatory = True, + doc = "The host-only target to build in the host configuration.", + cfg = _to_host_platform, + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, +) + def _rewrite_needs_json_to_docs_sources(labels): """Replace '@repo//:needs_json' -> '@repo//:docs_sources' for every item.""" out = [] @@ -63,8 +128,10 @@ def _rewrite_needs_json_to_sourcelinks(labels): s = str(x) if s.endswith("//:needs_json"): out.append(s.replace("//:needs_json", "//:sourcelinks_json")) + #Items which do not end up with '//:needs_json' shall not be appended to 'out'. #They are treated separately and are not related to source code linking. + return out def _merge_sourcelinks(name, sourcelinks, known_good = None): @@ -100,15 +167,19 @@ def _missing_requirements(deps): """Add Python hub dependencies if they are missing.""" found = [] missing = [] + def _target_to_packagename(target): return str(target).split("/")[-1].split(":")[0] + all_packages = [_target_to_packagename(pkg) for pkg in all_requirements] + def _find(pkg): for dep in deps: dep_pkg = _target_to_packagename(dep) if dep_pkg == pkg: return True return False + for pkg in all_packages: if _find(pkg): found.append(pkg) @@ -166,6 +237,11 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = sphinx_build_binary( name = "sphinx_build", + # `sphinx_build` is only consumed as a tool (see `needs_json`'s `sphinx` attr below), so Bazel + # already builds it in the exec (host) config -- no host transition needed. It is tagged + # `manual` so a cross-platform `bazel build //...` doesn't configure it for the target platform + # (which would fail Python toolchain resolution). + tags = ["manual"], visibility = ["//visibility:private"], data = data + metamodel_data, deps = deps, @@ -225,22 +301,32 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = docs_env["ACTION"] = "incremental" py_binary( - name = "docs", - tags = ["cli_help=Build documentation:\nbazel run //:docs"], + name = "docs_impl", + tags = ["manual"], srcs = [incremental_src], data = docs_data, deps = deps, - env = docs_env + env = docs_env, + ) + _host_binary( + name = "docs", + actual = ":docs_impl", + tags = ["cli_help=Build documentation:\nbazel run //:docs"], ) docs_sources_env["ACTION"] = "incremental" py_binary( - name = "docs_combo", - tags = ["cli_help=Build full documentation with all dependencies:\nbazel run //:docs_combo"], + name = "docs_combo_impl", + tags = ["manual"], srcs = [incremental_src], data = combo_data, deps = deps, - env = docs_sources_env + env = docs_sources_env, + ) + _host_binary( + name = "docs_combo", + actual = ":docs_combo_impl", + tags = ["cli_help=Build full documentation with all dependencies:\nbazel run //:docs_combo"], ) native.alias( @@ -251,52 +337,77 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = docs_env["ACTION"] = "linkcheck" py_binary( - name = "docs_link_check", - tags = ["cli_help=Verify Links inside Documentation:\nbazel run //:link_check\n (Note: this could take a long time)"], + name = "docs_link_check_impl", + tags = ["manual"], srcs = [incremental_src], data = docs_data, deps = deps, - env = docs_env + env = docs_env, + ) + _host_binary( + name = "docs_link_check", + actual = ":docs_link_check_impl", + tags = ["cli_help=Verify Links inside Documentation:\nbazel run //:link_check\n (Note: this could take a long time)"], ) docs_env["ACTION"] = "check" py_binary( - name = "docs_check", - tags = ["cli_help=Verify documentation:\nbazel run //:docs_check"], + name = "docs_check_impl", + tags = ["manual"], srcs = [incremental_src], data = docs_data, deps = deps, - env = docs_env + env = docs_env, + ) + _host_binary( + name = "docs_check", + actual = ":docs_check_impl", + tags = ["cli_help=Verify documentation:\nbazel run //:docs_check"], ) docs_env["ACTION"] = "live_preview" py_binary( - name = "live_preview", - tags = ["cli_help=Live preview documentation in the browser:\nbazel run //:live_preview"], + name = "live_preview_impl", + tags = ["manual"], srcs = [incremental_src], data = docs_data, deps = deps, - env = docs_env + env = docs_env, + ) + _host_binary( + name = "live_preview", + actual = ":live_preview_impl", + tags = ["cli_help=Live preview documentation in the browser:\nbazel run //:live_preview"], ) docs_sources_env["ACTION"] = "live_preview" py_binary( - name = "live_preview_combo_experimental", - tags = ["cli_help=Live preview full documentation with all dependencies in the browser:\nbazel run //:live_preview_combo_experimental"], + name = "live_preview_combo_experimental_impl", + tags = ["manual"], srcs = [incremental_src], data = combo_data, deps = deps, - env = docs_sources_env + env = docs_sources_env, + ) + _host_binary( + name = "live_preview_combo_experimental", + actual = ":live_preview_combo_experimental_impl", + tags = ["cli_help=Live preview full documentation with all dependencies in the browser:\nbazel run //:live_preview_combo_experimental"], ) py_venv( - name = "ide_support", - tags = ["cli_help=Create virtual environment (.venv_docs) for documentation support:\nbazel run //:ide_support"], + name = "ide_support_impl", + tags = ["manual"], venv_name = ".venv_docs", deps = deps, data = data, package_collisions = "warning", ) + _host_binary( + name = "ide_support", + actual = ":ide_support_impl", + tags = ["cli_help=Create virtual environment (.venv_docs) for documentation support:\nbazel run //:ide_support"], + ) sphinx_docs( name = "needs_json",