diff --git a/docs.bzl b/docs.bzl index cdc3a735c..2bf73aa5f 100644 --- a/docs.bzl +++ b/docs.bzl @@ -181,6 +181,7 @@ def docs( source_dir = "docs", project = None, project_url = None, + module = "", data = [], deps = [], external_needs = [], @@ -199,6 +200,7 @@ def docs( source_dir: The source directory containing documentation files. Defaults to "docs". project: optional project name, prefer setting this here if you can avoid having a conf.py project_url: Optional project URL, prefer setting this here if you can avoid having a conf.py + module: Optional module name that is allowed in the feature part of requirement IDs. data: Additional data files to include in the documentation build. deps: Additional dependencies for the documentation build. external_needs: List of external needs targets to include in the documentation build. @@ -320,6 +322,7 @@ def docs( "TEST_SOURCES": str(test_sources), "DATA": str(data), "EXTERNAL_NEEDS_FILES": str(external_needs), + "REQUIRED_IN_ID": module, # `bazel run` starts from a runfiles tree, so this logical path is # resolved by score_mounts through ``RUNFILES_DIR``. "MOUNTS_MANIFEST": "$(rlocationpath :_mounts_manifest)" if bundles else "", @@ -409,7 +412,7 @@ def docs( "--define=external_needs_source=" + str(data + external_needs), "--define=score_sourcelinks_json=$(location :sourcelinks_json)", "--define=score_source_code_linker_plain_links=1", - ] + ( + ] + (["--define=required_in_id=" + module] if module else []) + ( # ``sphinx_docs`` is a sandboxed build action, so it needs the # action-input path rather than the runfiles-relative spelling. ["--define=mounts_manifest=$(location :_mounts_manifest)"] if bundles else [] diff --git a/docs/how-to/write_docs.rst b/docs/how-to/write_docs.rst index 3d0f9f0fa..994d64382 100644 --- a/docs/how-to/write_docs.rst +++ b/docs/how-to/write_docs.rst @@ -83,7 +83,7 @@ The feature part is validated by checking that at least one of the following is * A segment of the feature part (split on ``_`` and ``-``) appears in the document's directory path * The initials of the feature part's segments appear in the document's directory path -* The feature part contains a string explicitly allowed via ``required_in_id`` in ``conf.py`` +* The feature part contains the module name configured via ``module`` in ``docs()`` **Examples** — given a requirement in ``internals/safety/fmea/requirements.rst``: @@ -108,9 +108,10 @@ The feature part is validated by checking that at least one of the following is - ``blabla`` has no relation to the path ``internals/safety/fmea`` To explicitly allow a feature part that intentionally doesn't match the path -(e.g. in a single module repository), add a matching string to ``required_in_id`` in ``conf.py``: +(e.g. in a single module repository), pass the matching module name: -.. code-block:: python +.. code-block:: starlark - # conf.py - required_in_id = ["persistenc"] + docs( + module = "persistenc", + ) diff --git a/docs/reference/bazel_macros.rst b/docs/reference/bazel_macros.rst index f188c93f5..632c00e01 100644 --- a/docs/reference/bazel_macros.rst +++ b/docs/reference/bazel_macros.rst @@ -59,6 +59,10 @@ Minimal example (root ``BUILD``) and supplies the Docs-as-Code baseline version and extensions. If a ``conf.py`` exists, it remains authoritative and these values are not used. +- ``module`` (string, optional) + The module name allowed in the feature part of requirement IDs when it + intentionally does not match the document path. + - ``data`` (list of bazel labels) Extra runfiles / data targets that should be made available to the documentation targets. The items in ``data`` are added to the py_binaries and to the Sphinx tooling so they are diff --git a/src/extensions/score_metamodel/__init__.py b/src/extensions/score_metamodel/__init__.py index ee8f645b3..797596ba4 100644 --- a/src/extensions/score_metamodel/__init__.py +++ b/src/extensions/score_metamodel/__init__.py @@ -233,7 +233,7 @@ def _clear_needs_defaults(app: Sphinx): def setup(app: Sphinx) -> dict[str, str | bool]: app.add_config_value("external_needs_source", "", rebuild="env") app.add_config_value("score_metamodel_yaml", "", rebuild="env") - app.add_config_value("required_in_id", [], rebuild="env") + app.add_config_value("required_in_id", "", rebuild="env") config_setdefault(app.config, "needs_id_required", True) config_setdefault(app.config, "needs_id_regex", "^[A-Za-z0-9_-]{6,}") diff --git a/src/extensions/score_metamodel/checks/id_contains_feature.py b/src/extensions/score_metamodel/checks/id_contains_feature.py index 38eeee19e..1c7e419a6 100644 --- a/src/extensions/score_metamodel/checks/id_contains_feature.py +++ b/src/extensions/score_metamodel/checks/id_contains_feature.py @@ -58,11 +58,9 @@ def id_contains_feature(app: Sphinx, need: NeedItem, log: CheckLogger): for featurepart in featureparts if featureparts and featurepart and docname ) - allowed_parts_from_config = app.config.required_in_id - found_part_from_config = any( - part_from_config.lower() in feature.lower() - for part_from_config in allowed_parts_from_config - if allowed_parts_from_config + required_in_id = app.config.required_in_id + found_part_from_config = bool( + required_in_id and required_in_id.lower() in feature.lower() ) # allow abbreviation of the feature @@ -72,17 +70,13 @@ def id_contains_feature(app: Sphinx, need: NeedItem, log: CheckLogger): foundinitials = bool(initials) and docname and initials in docname.lower() if not (foundfeatpart or foundinitials or found_part_from_config): parts_display = ", ".join(f"'{p}'" for p in featureparts) - config_display = ( - ", ".join(f"'{p}'" for p in allowed_parts_from_config) - if allowed_parts_from_config - else "[]" - ) + config_display = f"'{required_in_id}'" if required_in_id else "not set" fix_options = [f"rename the feature part to match a segment of '{docname}'"] if initials: fix_options.append(f"use correct abbreviation '{initials}'") fix_options.append( - f"Add an allowed part to `required_in_id` in conf.py (currently: {config_display})" + f"Set `module` in docs() to an allowed part (currently: {config_display})" ) combined_msg = ( diff --git a/src/extensions/score_metamodel/tests/rst/conf.py b/src/extensions/score_metamodel/tests/rst/conf.py index a3de12782..2c2afd224 100644 --- a/src/extensions/score_metamodel/tests/rst/conf.py +++ b/src/extensions/score_metamodel/tests/rst/conf.py @@ -25,8 +25,8 @@ "sphinx_needs", "score_metamodel", ] -# Required to test this for the check in id_contains_feature -required_in_id = ["blabla"] +# Required to test this for the check in id_contains_feature. +required_in_id = "blabla" needs_external_needs = [ { "base_url": "https://eclipse-score.github.io/process_description/main/", diff --git a/src/incremental.py b/src/incremental.py index fdb692c4c..253314059 100644 --- a/src/incremental.py +++ b/src/incremental.py @@ -165,6 +165,10 @@ def _mounted_watch_dirs( f"--define=mounts_manifest={os.environ.get('MOUNTS_MANIFEST', '')}", ] + required_in_id = os.environ.get("REQUIRED_IN_ID", "") + if required_in_id: + base_arguments.append(f"--define=required_in_id={required_in_id}") + generated_config = os.environ.get("SPHINX_CONFIG_FILE", "") if generated_config: # Under ``bazel run`` this is a runfiles-relative path. Sphinx wants diff --git a/src/tests/docs_bzl/scenarios/external_needs/producer/BUILD b/src/tests/docs_bzl/scenarios/external_needs/producer/BUILD index 352903a86..630da41d7 100644 --- a/src/tests/docs_bzl/scenarios/external_needs/producer/BUILD +++ b/src/tests/docs_bzl/scenarios/external_needs/producer/BUILD @@ -18,5 +18,6 @@ load("//:docs.bzl", "docs") # rather than docs(metamodel=...), because the metamodel= wiring does not reach # the :needs_json target in the current docs.bzl (regression from #484). docs( + module = "producer", source_dir = "docs", ) diff --git a/src/tests/docs_bzl/scenarios/external_needs/producer/docs/conf.py b/src/tests/docs_bzl/scenarios/external_needs/producer/docs/conf.py index 5bdbc3015..8e7b4d43c 100644 --- a/src/tests/docs_bzl/scenarios/external_needs/producer/docs/conf.py +++ b/src/tests/docs_bzl/scenarios/external_needs/producer/docs/conf.py @@ -30,9 +30,3 @@ score_metamodel_yaml = os.path.join( os.path.dirname(os.path.abspath(__file__)), "metamodel.yaml" ) - -# The producer need id `test_req__producer__demo` has three "__"-parts. The -# id_contains_feature check requires the feature part ("producer") to appear in -# the docname or in required_in_id; the fixture lives in index.rst, so we declare -# it here. -required_in_id = ["producer"] diff --git a/src/tests/docs_bzl/scenarios/local_version_mismatch/BUILD b/src/tests/docs_bzl/scenarios/local_version_mismatch/BUILD index d6a65cf32..df9739123 100644 --- a/src/tests/docs_bzl/scenarios/local_version_mismatch/BUILD +++ b/src/tests/docs_bzl/scenarios/local_version_mismatch/BUILD @@ -14,5 +14,6 @@ load("//:docs.bzl", "docs") docs( + module = "local", source_dir = "docs", ) diff --git a/src/tests/docs_bzl/scenarios/local_version_mismatch/docs/conf.py b/src/tests/docs_bzl/scenarios/local_version_mismatch/docs/conf.py index e36e9b648..ccf8e8d11 100644 --- a/src/tests/docs_bzl/scenarios/local_version_mismatch/docs/conf.py +++ b/src/tests/docs_bzl/scenarios/local_version_mismatch/docs/conf.py @@ -17,4 +17,3 @@ project_url = "https://example.invalid/local-version-mismatch" extensions = ["score_sphinx_bundle"] score_metamodel_yaml = os.path.join(os.path.dirname(__file__), "metamodel.yaml") -required_in_id = ["local"] diff --git a/src/tests/docs_bzl/test_cross_module_compatibility.py b/src/tests/docs_bzl/test_cross_module_compatibility.py index c2d428bd8..7878363aa 100644 --- a/src/tests/docs_bzl/test_cross_module_compatibility.py +++ b/src/tests/docs_bzl/test_cross_module_compatibility.py @@ -34,7 +34,7 @@ def _write_cross_module_consumer( ) workspace.joinpath("BUILD").write_text( """load("@score_docs_as_code//:docs.bzl", "docs") -docs(source_dir = "docs", bundles = [{"bundle": "@score_docs_compatibility_fixture//:docs_bundle", "mount_at": "fixture"}]) +docs(source_dir = "docs", module = "host", bundles = [{"bundle": "@score_docs_compatibility_fixture//:docs_bundle", "mount_at": "fixture"}]) """, encoding="utf-8", ) @@ -47,7 +47,6 @@ def _write_cross_module_consumer( project_url = "https://example.invalid/cross-module-consumer" extensions = ["score_sphinx_bundle"] score_metamodel_yaml = os.path.join(os.path.dirname(__file__), "metamodel.yaml") -required_in_id = ["host", "fixture"] {compatibility_config} """, encoding="utf-8",