You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The public docs() and docs_bundle() macros live in docs.bzl.
Each source-bearing bundle has a local Needs export built from its own sources.
Standalone bundle exports currently generate a baseline Sphinx configuration
with the bundle name, an empty project_url, and the default metamodel. The root
bundle can use its project's configuration, but its local export does not receive
the explicit docs(metamodel = ...) argument. Bundle-local inventories also
deliberately export an empty project_url, even when Sphinx knows a project URL.
The proposal makes selected settings from docs() available to bundle-local
exports through internal targets associated with a named docs() invocation.
Configuration belongs to the project that defines and publishes the bundle.
Mounting that bundle into another
project does not change its standalone export's settings or publication metadata.
The combined project build continues to use the mounting project's configuration.
Initially, the shared settings are project, project_url, metamodel, and deps.
Publication metadata also includes the bundle's full placement path within its
defining project's documentation tree.
Public interface
Settings remain arguments of the named docs() invocation established by name_concept.md. Its public owner label is //package:<name>;
its canonical incremental run target is //package:<name>.incremental.
Bundles refer to the named docs() invocation through a new docs argument.
Users do not refer to configuration targets. Internally, the macro resolves the
owner to <name>.__internal__.docs_config and other helpers. Neither a public docs_config target nor a separate configuration macro is introduced.
The default owner of a bundle is the root docs() invocation:
# //components/engine/BUILDload("@score_docs_as_code//:docs.bzl", "docs_bundle")
docs_bundle(
name="docs_bundle",
source_dir="docs",
visibility= ["//visibility:public"],
# docs defaults to //:docs in this bundle's repository.
)
The default is the declaring repository's //:docs, including when that
repository is an external Bzlmod dependency. It must not resolve to the macro
implementation's repository or to the repository mounting the bundle. There is
no search for an owner in ancestor packages.
For a project declared with docs(name = "site", ...) in //project, select
that invocation explicitly:
The root bundle generated by docs() always selects that invocation, including
when it has a custom name or is declared in a subdirectory. Selecting an owner on
an aggregating bundle does not implicitly change its children's owner selections.
Use docs = None for a standalone export without an owning publication:
Use the selected owner's shared settings and placement.
Missing owner or its generated helpers
Fail dependency analysis; declare the named docs() invocation, select another owner, or use docs = None.
Bundle absent from selected owner's mount tree
Fail with a diagnostic identifying the bundle and owner; do not guess a publication path.
Owner omits project_url
Fail the configured local export with a diagnostic requesting an explicit docs(project_url = ...).
Owner omits project
Use the bundle name for its local export.
Owner omits metamodel
Use the shipped default metamodel.
Owner omits deps
Use the standard documentation dependencies.
docs = None
Retain standalone baseline settings and dependencies, and an empty exported URL.
Bundle has no direct sources
Continue to create no local Needs export.
Placement and required-value checks apply when a configured local export is
analyzed. Merely composing source bundles does not require their export
configuration to be available.
References to the owner
Use the prerequisite's <name>.<role> family: the root bundle is :<name>.bundle and the project-wide inventory is :<name>.needs_json.
This proposal adds the internal helpers <name>.__internal__.docs_config and <name>.__internal__.sphinx_build; it does not introduce another naming scheme
or another set of compatibility aliases.
The docs argument accepts a label identifying an actual docs() invocation,
not an arbitrary alias to its runnable target. Normalize it in the declaring
repository before deriving sibling helper labels, preserving its repository and
package. Consumers never need to spell .__internal__. names.
Existing conf.py support
Keep the current conf.py support and precedence for project-wide builds. In
particular, a project's conf.py remains authoritative for its project name and
URL when present. The shared configuration only represents docs() arguments;
it does not execute or extract values from conf.py.
Bundle-local exports take shared fields from docs() arguments. Child exports
use the baseline configuration plus those fields. The root local export retains
its existing project configuration for other settings. Authors must keep shared
arguments aligned with overlapping values in a legacy conf.py. Supplying a URL
only in conf.py is insufficient for a configured local export.
Removing conf.py support is a future direction, outside this proposal. Python
dependencies explicitly supplied through docs(deps = ...) are shared with child
exports. Other Sphinx settings and external Needs imports are not automatically
inherited. Making a Python package available does not automatically enable it as
a Sphinx extension.
Dependency graph and implementation
Macros declare targets during Bazel's loading phase. Rules consume dependency
providers during analysis. Adding fields to a child's provider makes information
available to its consumers; it does not inject a parent's settings into that
child. Dependency cycles are invalid. See the Bazel rules documentation.
Use separate source-bundle and export targets. In this diagram, an arrow means
"depends on":
The source-bundle targets must not depend on the internal configuration or local
Needs exports. This allows the configuration target to inspect the composed source
tree without introducing a cycle. Building a local export may analyze the owner's
source-bundle graph, but must not run the owner's complete Sphinx build or other
bundles' local exports.
The public owner label is an API-level reference to the macro invocation. The
export's generated rules depend on the derived internal helpers, not on the
owner's runnable documentation target. Depending on the runnable target would
pull in unrelated build/runtime inputs and could introduce a cycle.
Internal naming means these helpers are implementation details, not that they
can have package-private visibility: local exports in other packages need to
depend on them. Give them visibility to packages in the defining repository.
Cross-repository consumers keep referencing the owner invocation defined in the
bundle's own repository; they do not select a different repository as its owner.
Configuration and placement providers
Introduce an internal DocsConfigInfo provider with the project name, project
URL, optional metamodel File, resolved Python dependency information for deps,
and a mapping from bundle labels to their full placement paths. The configuration
rule depends on the root source bundle, the optional metamodel target, and the
shared Python dependencies. Its outputs must not forward the entire owner's
source tree as inputs to every local export.
Extend source-entry metadata in bzl/bundle_rules.bzl with
the declaring bundle's Bazel label. Preserve that identity when entries are
rebased through nested bundles. Use label identity, rather than filesystem paths
or target-name strings, to look up a bundle's placement.
The configuration rule derives placements from the owner's composed entries.
For example, a parent mounted at components containing a child mounted at engine gives that child the path components/engine. attach_to controls
navigation and does not contribute to this path. The owner's root bundle has an
empty placement path. Preserve the existing rejection of a source directory
included through more than one bundle path.
Only the selected defining project's placement determines a local export's
metadata. A downstream mount at dependencies/example does not prefix or replace
that publication path.
Sphinx integration
An analysis-phase adapter consumes DocsConfigInfo and the exporting bundle's
label to generate Sphinx configuration inputs. Macros only wire these targets
together; they do not attempt to read providers.
Declare an owner Sphinx runtime at <name>.__internal__.sphinx_build, using the
owner's deps plus the standard documentation dependencies. Apply the existing
dependency completion and consistency checks when declaring that runtime. Resolve
relative dependency labels in the owner's package and repository, not in each
child's package.
Let the local Needs wrapper accept this existing Sphinx executable instead of
unconditionally creating a new binary with baseline dependencies. Reuse the owner
runtime for configured local exports and the project-wide Needs build. A
standalone export continues to create its own baseline runtime. Keep executable
providers and transitive runfiles intact; serializing a list of dependency labels
into generated Python configuration would not make those packages importable.
The runtime must not depend on configuration, source-bundle composition, or any
Needs export. Export-specific inputs such as the metamodel remain action inputs.
Apply the shared settings to both child and root local exports. Preserve the
root's existing configuration for settings outside the shared fields. Keep local
exports limited to direct sources, using the existing source adapters and local
export policy. This change does not add cross-bundle Needs imports.
Represent the metamodel as a real dependency throughout the adapter and Sphinx
action. Expose it through the required action inputs and runtime data rather than
passing only a path string. Generated metamodels and files from external
repositories must work under sandboxed execution. Configuration adapters must
also preserve the source-root layout expected by the existing Sphinx rules.
Exported URLs and compatibility
Configured bundle-local inventories export the owner URL and placement
separately:
These are top-level inventory fields. The root local export uses an empty bundle_mount_path. An unconfigured standalone export keeps an empty project_url and an empty placement path.
Keep document names inside local inventories relative to their bundle. Update
both inventory import paths in external_needs.py to construct
the base URL from the owner URL, the existing main publication directory, and
the bundle mount path, in that order:
Normalize separator boundaries without dropping the project URL's path prefix.
Do not place main after the bundle path or prefix document names a second time.
Generalizing the existing main publication convention is outside this proposal.
New consumers treat a missing bundle_mount_path as an empty path, preserving
existing project inventories. Older consumers do not understand the placement
field, so mount-aware local inventories require an updated consumer. This changes
the current deliberate suppression of the owner URL in local exports.
For migration, bundles belonging to subprojects select their named owner
explicitly. Standalone bundles either become part of an owner's publication tree
or set docs = None. Projects relying on conf.py supply shared values through docs() as well. Existing source composition remains independent of these local
export changes.
Acceptance scenarios
Implementation coverage should exercise behavior through the existing public
macro integration suite and focused exporter/importer tests:
A custom metamodel is enforced in project-wide, root-local, and child-local
exports, including a failure that the shipped default would not detect.
Generated and external metamodel files are available inside sandboxed builds.
A Python dependency supplied only through the owner's deps is importable in
project-wide, root-local, and child-local Sphinx runtimes, including its
transitive dependencies and runtime data. Relative labels resolve at the owner.
The existing dependency consistency checks still apply, and standalone exports
retain the standard dependencies.
Direct and nested mounts produce the expected metadata and external links;
root placement, trailing URL separators, and bundle-relative document names
are covered.
A bundle loaded through Bzlmod resolves its default configuration in its own
repository. A consuming project's settings and different mount location do
not change the defining project's export metadata.
A named subproject owner reference works, and each docs() root selects its
own invocation automatically. Bundle declarations never reference internal
target names.
Two named invocations in one package keep their shared settings, runtimes, and
placement metadata separate. Target naming and legacy aliases are covered by
the independent naming prerequisite.
Missing configuration, missing placement, and missing explicit owner URL fail
configured local exports; docs = None preserves standalone behavior.
Source-only composition still works without evaluating local export
configuration, and aggregators still have no local export.
The dependency graph is cycle-free. Building one local export does not execute
the owner's full documentation build or sibling local exports and exports only
its own Needs.
Both inventory import paths handle the new placement field and old inventories
without it. Existing project-wide conf.py precedence remains intact.
For this concept-only change, review examples against the current source and
check Markdown structure, copyright, and whitespace. Bazel builds are not needed
until the implementation changes.
Status: proposed. This document describes a future implementation; the new
configuration interface is not implemented yet.
Prerequisite: named documentation target families. Implement
that independent naming and compatibility migration before this proposal.
Problem and intended behavior
The public
docs()anddocs_bundle()macros live in docs.bzl.Each source-bearing bundle has a local Needs export built from its own sources.
Standalone bundle exports currently generate a baseline Sphinx configuration
with the bundle name, an empty
project_url, and the default metamodel. The rootbundle can use its project's configuration, but its local export does not receive
the explicit
docs(metamodel = ...)argument. Bundle-local inventories alsodeliberately export an empty
project_url, even when Sphinx knows a project URL.The proposal makes selected settings from
docs()available to bundle-localexports through internal targets associated with a named
docs()invocation.Configuration belongs to the project that defines and publishes the bundle.
Mounting that bundle into another
project does not change its standalone export's settings or publication metadata.
The combined project build continues to use the mounting project's configuration.
Initially, the shared settings are
project,project_url,metamodel, anddeps.Publication metadata also includes the bundle's full placement path within its
defining project's documentation tree.
Public interface
Settings remain arguments of the named
docs()invocation established byname_concept.md. Its public owner label is
//package:<name>;its canonical incremental run target is
//package:<name>.incremental.Bundles refer to the named
docs()invocation through a newdocsargument.Users do not refer to configuration targets. Internally, the macro resolves the
owner to
<name>.__internal__.docs_configand other helpers. Neither a publicdocs_configtarget nor a separate configuration macro is introduced.The default owner of a bundle is the root
docs()invocation:The default is the declaring repository's
//:docs, including when thatrepository is an external Bzlmod dependency. It must not resolve to the macro
implementation's repository or to the repository mounting the bundle. There is
no search for an owner in ancestor packages.
For a project declared with
docs(name = "site", ...)in//project, selectthat invocation explicitly:
The root bundle generated by
docs()always selects that invocation, includingwhen it has a custom name or is declared in a subdirectory. Selecting an owner on
an aggregating bundle does not implicitly change its children's owner selections.
Use
docs = Nonefor a standalone export without an owning publication:Owner selection has the following contract:
docs()invocation, select another owner, or usedocs = None.project_urldocs(project_url = ...).projectmetamodeldepsdocs = NonePlacement and required-value checks apply when a configured local export is
analyzed. Merely composing source bundles does not require their export
configuration to be available.
References to the owner
Use the prerequisite's
<name>.<role>family: the root bundle is:<name>.bundleand the project-wide inventory is:<name>.needs_json.This proposal adds the internal helpers
<name>.__internal__.docs_configand<name>.__internal__.sphinx_build; it does not introduce another naming schemeor another set of compatibility aliases.
The
docsargument accepts a label identifying an actualdocs()invocation,not an arbitrary alias to its runnable target. Normalize it in the declaring
repository before deriving sibling helper labels, preserving its repository and
package. Consumers never need to spell
.__internal__.names.Existing
conf.pysupportKeep the current
conf.pysupport and precedence for project-wide builds. Inparticular, a project's
conf.pyremains authoritative for its project name andURL when present. The shared configuration only represents
docs()arguments;it does not execute or extract values from
conf.py.Bundle-local exports take shared fields from
docs()arguments. Child exportsuse the baseline configuration plus those fields. The root local export retains
its existing project configuration for other settings. Authors must keep shared
arguments aligned with overlapping values in a legacy
conf.py. Supplying a URLonly in
conf.pyis insufficient for a configured local export.Removing
conf.pysupport is a future direction, outside this proposal. Pythondependencies explicitly supplied through
docs(deps = ...)are shared with childexports. Other Sphinx settings and external Needs imports are not automatically
inherited. Making a Python package available does not automatically enable it as
a Sphinx extension.
Dependency graph and implementation
Macros declare targets during Bazel's loading phase. Rules consume dependency
providers during analysis. Adding fields to a child's provider makes information
available to its consumers; it does not inject a parent's settings into that
child. Dependency cycles are invalid. See the
Bazel rules documentation.
Use separate source-bundle and export targets. In this diagram, an arrow means
"depends on":
flowchart TD Export["Bundle-local Needs export"] --> Adapter["Bundle Sphinx configuration adapter"] Export --> Sources["Bundle's own sources"] Export --> Runtime["Owner :name.__internal__.sphinx_build"] Runtime --> Deps["Owner deps + standard documentation dependencies"] Adapter --> Config["Owner :name.__internal__.docs_config"] Config --> Deps Config --> Root["Owner :name.bundle"] Root --> Child["Child source-bundle targets"] Sources --> ChildThe source-bundle targets must not depend on the internal configuration or local
Needs exports. This allows the configuration target to inspect the composed source
tree without introducing a cycle. Building a local export may analyze the owner's
source-bundle graph, but must not run the owner's complete Sphinx build or other
bundles' local exports.
The public owner label is an API-level reference to the macro invocation. The
export's generated rules depend on the derived internal helpers, not on the
owner's runnable documentation target. Depending on the runnable target would
pull in unrelated build/runtime inputs and could introduce a cycle.
Internal naming means these helpers are implementation details, not that they
can have package-private visibility: local exports in other packages need to
depend on them. Give them visibility to packages in the defining repository.
Cross-repository consumers keep referencing the owner invocation defined in the
bundle's own repository; they do not select a different repository as its owner.
Configuration and placement providers
Introduce an internal
DocsConfigInfoprovider with the project name, projectURL, optional metamodel
File, resolved Python dependency information fordeps,and a mapping from bundle labels to their full placement paths. The configuration
rule depends on the root source bundle, the optional metamodel target, and the
shared Python dependencies. Its outputs must not forward the entire owner's
source tree as inputs to every local export.
Extend source-entry metadata in bzl/bundle_rules.bzl with
the declaring bundle's Bazel label. Preserve that identity when entries are
rebased through nested bundles. Use label identity, rather than filesystem paths
or target-name strings, to look up a bundle's placement.
The configuration rule derives placements from the owner's composed entries.
For example, a parent mounted at
componentscontaining a child mounted atenginegives that child the pathcomponents/engine.attach_tocontrolsnavigation and does not contribute to this path. The owner's root bundle has an
empty placement path. Preserve the existing rejection of a source directory
included through more than one bundle path.
Only the selected defining project's placement determines a local export's
metadata. A downstream mount at
dependencies/exampledoes not prefix or replacethat publication path.
Sphinx integration
An analysis-phase adapter consumes
DocsConfigInfoand the exporting bundle'slabel to generate Sphinx configuration inputs. Macros only wire these targets
together; they do not attempt to read providers.
Declare an owner Sphinx runtime at
<name>.__internal__.sphinx_build, using theowner's
depsplus the standard documentation dependencies. Apply the existingdependency completion and consistency checks when declaring that runtime. Resolve
relative dependency labels in the owner's package and repository, not in each
child's package.
Let the local Needs wrapper accept this existing Sphinx executable instead of
unconditionally creating a new binary with baseline dependencies. Reuse the owner
runtime for configured local exports and the project-wide Needs build. A
standalone export continues to create its own baseline runtime. Keep executable
providers and transitive runfiles intact; serializing a list of dependency labels
into generated Python configuration would not make those packages importable.
The runtime must not depend on configuration, source-bundle composition, or any
Needs export. Export-specific inputs such as the metamodel remain action inputs.
Apply the shared settings to both child and root local exports. Preserve the
root's existing configuration for settings outside the shared fields. Keep local
exports limited to direct sources, using the existing source adapters and local
export policy. This change does not add cross-bundle Needs imports.
Represent the metamodel as a real dependency throughout the adapter and Sphinx
action. Expose it through the required action inputs and runtime data rather than
passing only a path string. Generated metamodels and files from external
repositories must work under sandboxed execution. Configuration adapters must
also preserve the source-root layout expected by the existing Sphinx rules.
Exported URLs and compatibility
Configured bundle-local inventories export the owner URL and placement
separately:
{ "project_url": "https://example.org/example", "bundle_mount_path": "components/engine" }These are top-level inventory fields. The root local export uses an empty
bundle_mount_path. An unconfigured standalone export keeps an emptyproject_urland an empty placement path.Keep document names inside local inventories relative to their bundle. Update
both inventory import paths in
external_needs.py to construct
the base URL from the owner URL, the existing
mainpublication directory, andthe bundle mount path, in that order:
For
detailsin the example bundle, the resulting link is:Normalize separator boundaries without dropping the project URL's path prefix.
Do not place
mainafter the bundle path or prefix document names a second time.Generalizing the existing
mainpublication convention is outside this proposal.New consumers treat a missing
bundle_mount_pathas an empty path, preservingexisting project inventories. Older consumers do not understand the placement
field, so mount-aware local inventories require an updated consumer. This changes
the current deliberate suppression of the owner URL in local exports.
For migration, bundles belonging to subprojects select their named owner
explicitly. Standalone bundles either become part of an owner's publication tree
or set
docs = None. Projects relying onconf.pysupply shared values throughdocs()as well. Existing source composition remains independent of these localexport changes.
Acceptance scenarios
Implementation coverage should exercise behavior through the existing public
macro integration suite and focused exporter/importer tests:
exports, including a failure that the shipped default would not detect.
depsis importable inproject-wide, root-local, and child-local Sphinx runtimes, including its
transitive dependencies and runtime data. Relative labels resolve at the owner.
retain the standard dependencies.
root placement, trailing URL separators, and bundle-relative document names
are covered.
repository. A consuming project's settings and different mount location do
not change the defining project's export metadata.
docs()root selects itsown invocation automatically. Bundle declarations never reference internal
target names.
placement metadata separate. Target naming and legacy aliases are covered by
the independent naming prerequisite.
configured local exports;
docs = Nonepreserves standalone behavior.configuration, and aggregators still have no local export.
the owner's full documentation build or sibling local exports and exports only
its own Needs.
without it. Existing project-wide
conf.pyprecedence remains intact.For this concept-only change, review examples against the current source and
check Markdown structure, copyright, and whitespace. Bazel builds are not needed
until the implementation changes.