Skip to content

Named documentation target families #806

Description

@AlexanderLanin

Named documentation target families

Status: proposed. This is an independent prerequisite for
sharing project configuration with bundles. It changes naming and
target references without introducing shared configuration or changing what the
documentation builds contain.

Earlier approach: PR #648

PR #648 explored named
docs() invocations by deriving target names in one helper, updating internal
references, and distinguishing virtual environments and metrics outputs. It also
updated external-inventory parsing, non-Bazel discovery, and PlantUML runfiles
lookup. These are useful implementation touchpoints for this prerequisite.

The earlier approach kept default targets unprefixed and used underscore prefixes
for custom names. This proposal instead uses dotted canonical targets for every
invocation and aliases for old public labels. Its suffix-based inventory lookup
and target-derived path reconstruction do not solve alias-to-artifact resolution;
use the artifact metadata described below. Its former docs_sources and combo
targets also predate the current bundle composition model and are not restored.

The PR required custom names to be repeated as docs_target_name in conf.py for
IDE execution. This proposal keeps the invocation identity in Bazel and carries
it into generated runtime/IDE metadata. That identity must include the package
and repository, not just the target name, so subdirectory projects work as well.

The review discussion
distinguishes multiple independent documentation builds from publishing multiple
sites. This prerequisite enables those builds and provides stable invocation
identities. It does not change publishing policy or replace docs_bundle() for
composing one documentation site.

Goal and public API

docs() currently declares a collection of targets with unrelated names, such as
docs, docs_bundle, needs_json, and live_preview. Give the invocation a
stable identity and put its generated targets in one namespace:

load("@score_docs_as_code//:docs.bzl", "docs")

docs(
    name = "docs",
    source_dir = "docs",
    project = "Example",
    project_url = "https://example.org/example",
)

Add optional name = "docs" after the existing parameters to preserve positional
calls. name is the Bazel identity; project remains the human-readable Sphinx
project name. Use a single target-name component without / for name so the
namespace and its generated directories stay within the declaring package.

All generated implementation targets use either <name>.<role> for supported
entry points or <name>.__internal__.<role> for implementation details. The
default invocation uses the same dotted scheme as any custom invocation.

The unqualified :<name> is an alias to :<name>.incremental. It remains a convenient
run command and provides the public label identifying the abstract docs()
invocation for future APIs. Thus //project:site identifies
docs(name = "site", ...), while //project:site.bundle selects its mountable
source bundle. This naming prerequisite does not add an owner argument to
docs_bundle().

Run incremental documentation generation with bazel run //:docs.incremental.
Reserve <name>.build for a future, separate documentation target intended for
bazel build; this prerequisite does not introduce that target.

Canonical targets and compatibility aliases

For docs(name = "docs"), generate these canonical entry points and aliases:

Role Canonical target Compatibility alias
Incremental documentation command :docs.incremental :docs
Root source bundle :docs.bundle :docs_bundle
Project-wide Needs inventory :docs.needs_json :needs_json
Single Needs JSON file :docs.needs_json_file :needs_json_file
Metrics JSON file :docs.metrics_json :metrics_json
Source links :docs.sourcelinks_json :sourcelinks_json
Documentation checks :docs.check :docs_check
Link checks :docs.link_check :docs_link_check
Live preview :docs.live_preview :live_preview
IDE environment :docs.ide_support :ide_support
Traceability gate :docs.traceability_gate :traceability_gate

Use native.alias for compatibility; aliases forward to canonical targets and
do not declare duplicate build actions. Preserve the existing public visibility
and manual-tag policy on aliases and the corresponding canonical targets. Keep
the traceability gate's existing delegation to the shared tool under its new
canonical name.

A custom invocation, such as docs(name = "site"), produces :site.incremental,
:site.bundle, :site.needs_json, and the other roles from the same table. It
also produces the owner alias :site. Only the default name = "docs" invocation
creates the legacy aliases, so named invocations can coexist without claiming
the same compatibility labels. Keep those aliases for this migration without
adding a removal deadline.

Use canonical labels for all generated dependencies, $(location ...)
expressions, environment values, and examples added as part of the implementation.
Compatibility aliases serve existing callers; generated rules must not depend
on them.

Internal targets

Move fixed private helpers such as _docs_generated_config, _docs_mounts, and
_external_docs_runfiles beneath the invocation's .__internal__. namespace.
For example, use docs.__internal__.generated_config and
docs.__internal__.mounts_manifest. The build executable can itself be
docs.incremental; it no longer needs the fixed _score_docs_cli name to avoid a
filesystem collision with the docs/ source directory.

Bundle-local and Needs-build helpers retain their existing owner-relative naming
convention, now based on the canonical owning target. For example:

docs.bundle.__internal__.needs_local
docs.bundle.__internal__.needs_sources
docs.needs_json.__internal__.sphinx_build

Private helpers do not receive compatibility aliases. Existing direct references
to internal target names, including repository tests, migrate to the new names.
Standalone docs_bundle(name = ...) declarations retain their current naming;
only the root bundle generated by docs() receives the new canonical name.

Output paths and integration

Namespace every invocation-owned helper, action output, and runtime environment,
not just public labels. Two named invocations with distinct source directories
must coexist in one package without target, generated-file, or virtual-environment
collisions. Duplicate names or collisions with user-declared targets remain Bazel
errors; do not invent alternate names automatically.

There is no compatibility requirement for existing output paths, including
generated configuration, extracted JSON files, build directories, runfiles, or
IDE environments. Compatibility aliases preserve target entry points only.
Use the same invocation-scoped layout for default and custom names, without
legacy-path exceptions or copies created solely to retain old paths.

Put generated configuration and extracted files under each invocation's
<name>.__internal__/ output directory, use .venv_<name> for IDE support, and
use _build_<name> for interactive builds, including separate stale-build
sentinels. Cleaning one invocation must not remove another invocation's outputs.
Pass the selected directories to runtime and traceability tools explicitly.
Ensure generated configuration is still staged at the Sphinx source-root location
expected by the existing source adapters. A source-provided conf.py keeps its
existing precedence and location.

An alias preserves a target reference, but it does not recreate an old target's
physical output directory or runfiles tree. In particular, renaming the Needs
build changes its target-derived output directory. Inventory consumers currently
recognize fixed target names and reconstruct paths from those names; adding
aliases alone would leave these consumers broken.

As part of this prerequisite, resolve inventory artifact locations from Bazel
files and carry those locations into runtime input metadata. Preserve the
repository/runfiles identity and distinguish directory-valued Needs exports from
single-file exports. Do not reconstruct an artifact path from the supplied alias
name. Support canonical and legacy labels, including cross-repository producers
that still use the previous target naming scheme. Update the legacy inventory
classification used by docs(data = ...) as well, so a canonical Needs target
does not become mounted bundle data accidentally.

Update runtime discovery, IDE setup, traceability tooling, and test helpers that
assume fixed targets or output directories. Keep commands through compatibility
aliases working, but migrate internal path-based assumptions to canonical outputs
or artifact discovery. Update documentation and scripts that refer to output
paths to the new layout. Direct Sphinx execution and the content of
Needs exports remain unchanged.

For IDE/Esbonio and direct Sphinx execution in a generated IDE environment, emit
metadata alongside that environment identifying the full owner label, its
canonical incremental target, and the corresponding runfiles location. Runtime
discovery must use the selected environment's metadata when Bazel runfiles are
not already available, rather than assuming root-package ide_support.runfiles
or asking authors to repeat the name in conf.py. Update PlantUML's use of that
discovery path too.

Any remaining fallback query for documentation data must address the owner's
canonical incremental target in its actual package, rather than querying
labels(data, //:docs) or another alias without a data attribute. These runtime
changes belong to the naming prerequisite and must not rely on the later shared
configuration proposal.

Delivery and acceptance

Implement this prerequisite independently of concept.md: first centralize
target-name derivation, move targets and helper references, add compatibility
aliases, and update artifact resolution and affected consumers. Do not add
docs_config, shared deps, or new publication metadata in this change.

Validate these behaviors:

  • An existing docs() declaration still works through every legacy public label;
    each alias resolves to its canonical counterpart and schedules no duplicate
    documentation action.
  • Canonical incremental/check commands, mounted root bundles, and both Needs export
    forms work for default and custom names.
  • The owner and legacy run aliases execute the incremental target. No
    <name>.build target is introduced by this prerequisite.
  • Two named invocations with distinct source directories in one package have
    independent targets, generated configurations, exports, and IDE environments.
  • Canonical and compatibility inventory labels resolve the same actual artifact,
    including external repositories and producers using the previous naming scheme.
  • IDE/Esbonio and direct Sphinx execution in a named project's IDE environment
    locate that project's runfiles, PlantUML, and external inventories without a
    duplicated conf.py name. Cover a project outside the repository root and a
    fallback query against the canonical incremental target.
  • Legacy data inventory handling, generated sources, metamodel inputs, source
    links, and traceability commands retain their behavior after names change.
  • Default and custom invocations use the same output-layout rules. Runtime tools,
    tests, and documentation use the new paths or artifact discovery without
    requiring any old output path to remain available.
  • Positional calls remain valid, and existing conf.py precedence and standalone
    docs_bundle() names remain unchanged.

Run the relevant public macro integration scenarios sequentially and focused
inventory/runtime tests when implementing this prerequisite. For this
document-only change, check links, example syntax, and whitespace.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions