diff --git a/docs.bzl b/docs.bzl index 699c2370f..9a8f780e9 100644 --- a/docs.bzl +++ b/docs.bzl @@ -125,7 +125,7 @@ def _missing_requirements(deps): fail(msg) fail("This case should be unreachable?!") -def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = None): +def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = None, metamodel = None): """Creates all targets related to documentation. By using this function, you'll get any and all updates for documentation targets in one place. @@ -135,6 +135,9 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = data: Additional data files to include in the documentation build. deps: Additional dependencies for the documentation build. scan_code: List of code targets to scan for source code links. + known_good: Optional label to a "known good" JSON file for source links. + metamodel: Optional label to a metamodel.yaml file. When set, the extension loads this + file instead of the default metamodel shipped with score_metamodel. """ call_path = native.package_name() @@ -142,6 +145,14 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = if call_path != "": fail("docs() must be called from the root package. Current package: " + call_path) + metamodel_data = [] + metamodel_env = {} + metamodel_opts = [] + if metamodel != None: + metamodel_data = [metamodel] + metamodel_env = {"SCORE_METAMODEL_YAML": "$(location " + str(metamodel) + ")"} + metamodel_opts = ["--define=score_metamodel_yaml=$(location " + str(metamodel) + ")"] + module_deps = deps deps = deps + _missing_requirements(deps) deps = deps + [ @@ -152,7 +163,7 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = sphinx_build_binary( name = "sphinx_build", visibility = ["//visibility:private"], - data = data, + data = data + metamodel_data, deps = deps, ) @@ -187,19 +198,19 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = data_with_docs_sources = _rewrite_needs_json_to_docs_sources(data) additional_combo_sourcelinks = _rewrite_needs_json_to_sourcelinks(data) _merge_sourcelinks(name = "merged_sourcelinks", sourcelinks = [":sourcelinks_json"] + additional_combo_sourcelinks, known_good = known_good) - docs_data = data + [":sourcelinks_json"] - combo_data = data_with_docs_sources + [":merged_sourcelinks"] + docs_data = data + metamodel_data + [":sourcelinks_json"] + combo_data = data_with_docs_sources + metamodel_data + [":merged_sourcelinks"] docs_env = { "SOURCE_DIRECTORY": source_dir, "DATA": str(data), "SCORE_SOURCELINKS": "$(location :sourcelinks_json)", - } + } | metamodel_env docs_sources_env = { "SOURCE_DIRECTORY": source_dir, "DATA": str(data_with_docs_sources), "SCORE_SOURCELINKS": "$(location :merged_sourcelinks)", - } + } | metamodel_env if known_good: docs_env["KNOWN_GOOD_JSON"] = "$(location "+ known_good + ")" docs_sources_env["KNOWN_GOOD_JSON"] = "$(location "+ known_good + ")" @@ -293,10 +304,10 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good = "--jobs", "auto", "--define=external_needs_source=" + str(data), - ], + ] + metamodel_opts, formats = ["needs"], sphinx = ":sphinx_build", - tools = data, + tools = data + metamodel_data, visibility = ["//visibility:public"], # Persistent workers cause stale symlinks after dependency version # changes, corrupting the Bazel cache. diff --git a/docs/how-to/setup.md b/docs/how-to/setup.md index 253f88116..a00096658 100644 --- a/docs/how-to/setup.md +++ b/docs/how-to/setup.md @@ -68,8 +68,10 @@ The `docs()` macro accepts the following arguments: | Parameter | Description | Required | |-----------|-------------|----------| | `source_dir` | Directory of documentation source files (RST, MD) | Yes | -| `data` | List of `needs_json` targets that should be included in the documentation| No | - +| `data` | List of `needs_json` targets that should be included in the documentation | No | +| `deps` | Additional Bazel Python dependencies | No | +| `scan_code` | Source code targets to scan for traceability tags | No | +| `metamodel` | Label to a custom `metamodel.yaml` that replaces the default metamodel | No | ### 4. Copy conf.py diff --git a/docs/reference/bazel_macros.rst b/docs/reference/bazel_macros.rst index 7118f83f8..a801c4af2 100644 --- a/docs/reference/bazel_macros.rst +++ b/docs/reference/bazel_macros.rst @@ -60,6 +60,29 @@ Minimal example (root ``BUILD``) If you don't provide the necessary Sphinx packages, this function adds its own (but checks for conflicts). +- ``scan_code`` (list of bazel labels) + Source code targets to scan for traceability tags (``req-Id:`` annotations). + Used to generate the source-code-link JSON that maps tags back to source files. + +- ``metamodel`` (bazel label, optional) + Path to a custom ``metamodel.yaml`` file. + When set, the ``score_metamodel`` extension loads **this file instead of** the default metamodel. + The label is automatically added to the ``data`` and ``tools`` of every generated target + so the file is available in the Bazel sandbox at build time. + + Example: + + .. code-block:: python + + docs( + source_dir = "docs", + metamodel = "//:my_metamodel.yaml", + ) + + The custom ``metamodel.yaml`` must follow the same schema as the default one + (see :doc:`score_metamodel `). + When ``metamodel`` is omitted the default metamodel is used unchanged. + Edge cases ---------- diff --git a/src/extensions/score_metamodel/__init__.py b/src/extensions/score_metamodel/__init__.py index f0b90c8ee..39be78f19 100644 --- a/src/extensions/score_metamodel/__init__.py +++ b/src/extensions/score_metamodel/__init__.py @@ -228,11 +228,14 @@ def postprocess_need_links(needs_types_list: list[ScoreNeedType]): 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") config_setdefault(app.config, "needs_id_required", True) config_setdefault(app.config, "needs_id_regex", "^[A-Za-z0-9_-]{6,}") # load metamodel.yaml via ruamel.yaml - metamodel = load_metamodel_data() + raw_metamodel_path = app.config.score_metamodel_yaml + override_path = Path(raw_metamodel_path) if raw_metamodel_path else None + metamodel = load_metamodel_data(override_path) # Extend sphinx-needs config rather than overwriting app.config.needs_types += metamodel.needs_types diff --git a/src/extensions/score_metamodel/tests/test_metamodel_load.py b/src/extensions/score_metamodel/tests/test_metamodel_load.py index 9a660887a..f5676b870 100644 --- a/src/extensions/score_metamodel/tests/test_metamodel_load.py +++ b/src/extensions/score_metamodel/tests/test_metamodel_load.py @@ -25,6 +25,15 @@ def load_model_data(model_file: str) -> str: return f.read() +def test_load_metamodel_data_explicit_path(): + """When an explicit path is given, load_metamodel_data reads that file.""" + explicit_path = MODEL_DIR / "simple_model.yaml" + result = load_metamodel_data(yaml_path=explicit_path) + + assert len(result.needs_types) == 1 + assert result.needs_types[0]["directive"] == "type1" + + def test_load_metamodel_data(): model_data: str = load_model_data("simple_model.yaml") diff --git a/src/extensions/score_metamodel/yaml_parser.py b/src/extensions/score_metamodel/yaml_parser.py index f40556bbf..454a502c0 100644 --- a/src/extensions/score_metamodel/yaml_parser.py +++ b/src/extensions/score_metamodel/yaml_parser.py @@ -182,11 +182,16 @@ def _collect_all_custom_options( } -def load_metamodel_data() -> MetaModelData: +def load_metamodel_data(yaml_path: Path | None = None) -> MetaModelData: """ Load metamodel.yaml and prepare data fields as needed for sphinx-needs. + + Args: + yaml_path: Path to the metamodel YAML file. When None, the default + metamodel shipped with this extension is used. """ - yaml_path = Path(__file__).resolve().parent / "metamodel.yaml" + if yaml_path is None: + yaml_path = Path(__file__).resolve().parent / "metamodel.yaml" with open(yaml_path, encoding="utf-8") as f: data = cast(dict[str, Any], YAML().load(f)) diff --git a/src/incremental.py b/src/incremental.py index fa1c5abc2..3289865f0 100644 --- a/src/incremental.py +++ b/src/incremental.py @@ -84,6 +84,10 @@ def get_env(name: str) -> str: f"--define=external_needs_source={get_env('DATA')}", ] + metamodel_yaml = os.environ.get("SCORE_METAMODEL_YAML", "") + if metamodel_yaml: + base_arguments.append(f"--define=score_metamodel_yaml={metamodel_yaml}") + # configure sphinx build with GitHub user and repo from CLI if args.github_user and args.github_repo: base_arguments.append(f"-A=github_user={args.github_user}")